agentic_mcp 0.1.1 copy "agentic_mcp: ^0.1.1" to clipboard
agentic_mcp: ^0.1.1 copied to clipboard

A Model Context Protocol client and server for Dart. Remote MCP tools become ordinary agent tools, over stdio, HTTP or in-process transports.

example/agentic_mcp_example.dart

// Demonstrates both directions of MCP: publishing your own tools as a server,
// and consuming a server's tools as ordinary framework tools an agent runs.
//
// Run it with:
//
//     dart run example/agentic_mcp_example.dart
//
// It runs offline. Client and server are connected by an in-process transport,
// which is exactly what a plugin architecture inside one app would use; swap in
// `McpHttpTransport` or `StdioTransport` and nothing else changes.
import 'package:agentic_agents/agentic_agents.dart';
import 'package:agentic_core/agentic_core.dart';
import 'package:agentic_llm/agentic_llm.dart';
import 'package:agentic_llm/testing.dart';
import 'package:agentic_mcp/agentic_mcp.dart';
import 'package:agentic_tools/agentic_tools.dart';

Future<void> main() async {
  // ---------------------------------------------------------------------------
  // 1. Publish an application's tools over MCP.
  // ---------------------------------------------------------------------------
  print('--- serving ---');
  final published = ToolRegistry()
    ..register(
      FunctionTool.text(
        name: 'weather',
        description: 'Returns the current weather for a city.',
        parameters: JsonSchema.object(
          properties: <String, JsonSchema>{
            'city': JsonSchema.string(description: 'The city to look up.'),
          },
          required: const <String>{'city'},
        ),
        handler: (invocation) async {
          final city = invocation.require<String>('city');
          return '$city: 18°C, light rain.';
        },
      ),
    )
    ..register(
      FunctionTool.text(
        name: 'shutdown_datacentre',
        description: 'Powers down a datacentre.',
        isReadOnly: false,
        requiresApproval: true,
        handler: (invocation) async => 'lights out',
      ),
    );

  final (clientSide, serverSide) = InMemoryTransport.pair();
  final server = McpServer(
    transport: serverSide,
    registry: published,
    serverInfo: const McpImplementation(name: 'ops-tools', version: '1.0.0'),
    instructions: 'Ask about weather before scheduling anything outdoors.',
  );
  await server.start();

  print('registry   : ${published.all.names.join(', ')}');
  print('published  : ${server.publishedTools.map((s) => s.name).join(', ')}');
  print(
    'withheld   : shutdown_datacentre — it needs a person to approve it, and\n'
    '             there is no person on the far end of a socket.',
  );

  // ---------------------------------------------------------------------------
  // 2. Connect a client and negotiate the session.
  // ---------------------------------------------------------------------------
  print('\n--- connecting ---');
  final bus = BroadcastEventBus();
  final context = AgenticContext.root(events: bus);
  final client = McpClient(transport: clientSide, context: context);

  final session = await client.initialize();
  print('server     : ${session.server} (${session.protocolVersion})');
  print('capabilities: ${session.capabilities}');
  print('instructions: ${session.instructions}');

  // ---------------------------------------------------------------------------
  // 3. Its tools become ordinary tools. Nothing above this line knows about MCP.
  // ---------------------------------------------------------------------------
  print('\n--- adopting the tools ---');
  final registry = ToolRegistry();
  final names = await registerMcpTools(
    client,
    registry,
    prefix: 'ops',
    context: context,
  );
  print('registered : ${names.join(', ')}');

  final spec = registry.specOf('ops_weather')!;
  print('description: ${spec.description}');
  print('read-only  : ${spec.isReadOnly}   approval: ${spec.requiresApproval}');

  // ---------------------------------------------------------------------------
  // 4. An agent uses them without knowing where they came from.
  // ---------------------------------------------------------------------------
  print('\n--- an agent uses them ---');
  final agent = ToolCallingAgent(
    model: FakeChatModel(
      turns: <FakeTurn>[
        FakeTurn.answer(
          ChatResponse(
            message: Message.assistant(
              '',
              toolCalls: <ToolCallPart>[
                ToolCallPart(
                  id: 'call-1',
                  name: 'ops_weather',
                  arguments: const <String, Object?>{'city': 'Lisbon'},
                ),
              ],
            ),
            modelId: 'scripted',
            finishReason: FinishReason.toolCalls,
          ),
        ),
        FakeTurn.answer(
          ChatResponse(
            message: Message.assistant('It is 18°C and raining in Lisbon.'),
            modelId: 'scripted',
          ),
        ),
      ],
    ),
    tools: registry.all,
    info: AgentInfo(name: 'planner', description: 'Plans outdoor work.'),
  );

  final answer = await agent.run(
    AgentInput.text('What is the weather in Lisbon?'),
    context: context,
  );
  print('answer     : ${answer.text}');
  print('iterations : ${answer.iterations}');

  // ---------------------------------------------------------------------------
  // 5. A tool that fails is a failure the model can see, not a dead run.
  // ---------------------------------------------------------------------------
  print('\n--- failure handling ---');
  final failing = await registry
      .resolve('ops_weather')
      .call(
        ToolInvocation(
          callId: 'call-2',
          toolName: 'ops_weather',
          // No city: the executor would normally reject this, but calling the tool
          // directly shows what the server does with it.
          arguments: const <String, Object?>{},
          context: context,
        ),
      );
  print('isError    : ${failing.isError}');
  print('content    : ${failing.content}');

  // ---------------------------------------------------------------------------
  // 6. The server tells the client when its tools change.
  // ---------------------------------------------------------------------------
  print('\n--- live updates ---');
  client.notifications.listen((n) => print('notification: ${n.method}'));

  published.register(
    FunctionTool.text(
      name: 'forecast',
      description: 'Returns a five-day forecast.',
      handler: (invocation) async => 'Rain, then more rain.',
    ),
  );
  await server.notifyToolsChanged();
  await Future<void>.delayed(Duration.zero);

  final refreshed = await client.listTools();
  print('now offers : ${refreshed.map((t) => t.name).join(', ')}');

  // ---------------------------------------------------------------------------
  // 7. Observability.
  // ---------------------------------------------------------------------------
  print('\n--- observability ---');
  for (final event in bus.replayBuffer.whereType<McpEvent>()) {
    print(event);
  }

  await bus.dispose();
  await client.dispose();
  await server.dispose();
}
2
likes
160
points
132
downloads

Documentation

Documentation
API reference

Publisher

unverified uploader

Weekly Downloads

A Model Context Protocol client and server for Dart. Remote MCP tools become ordinary agent tools, over stdio, HTTP or in-process transports.

Repository (GitHub)
View/report issues
Contributing

Topics

#ai #mcp #model-context-protocol #tools #agentic

License

MIT (license)

Dependencies

agentic_core, agentic_llm, agentic_tools, http, meta

More

Packages that depend on agentic_mcp