agentkit-mcp
Model Context Protocol integration for agentkit, built on top of the official rmcp Rust SDK.
This crate covers:
- stdio and Streamable HTTP transports (driven by rmcp)
- multi-server lifecycle, discovery, and catalog diffing via
McpServerManager - adapters that surface MCP servers as agentkit tools and capabilities
- pluggable client-side responders for
sampling/createMessage,elicitation/create, androots/list - a broadcast subscription for server-pushed events: progress, logging, resource updates, list-changed, cancellation
- auth replay for MCP operations that fail with an authentication challenge
The wire-protocol types — CallToolResult, ReadResourceResult, GetPromptResult, Content, RawContent, ToolAnnotations, Prompt, etc. — are re-exported from rmcp directly. There is no parallel agentkit-side type vocabulary to maintain.
Why this matters
Re-exporting rmcp::model keeps agentkit-mcp in lockstep with the MCP spec — new fields, content variants, capability flags, server-initiated requests, and notification payloads land in agentkit the moment rmcp ships them. No second source of truth to drift.
- Spec: modelcontextprotocol.io
- Rust SDK:
rmcpon crates.io
The same applies to transports: any future rmcp transport is reachable through McpConnection::from_running_service_with_events without touching the built-in McpTransportBinding enum.
Configuring and connecting MCP servers
Register one or more MCP server configurations with McpServerManager, then connect them. Each connected server is represented by an McpServerHandle that holds the live connection and the discovery snapshot.
use ;
#
# async
Use connect_all_settled().await when startup should be best effort: it attempts every registered server concurrently, installs successful connections into the manager, and returns each failed server with its own McpError.
For slow or unresponsive servers, register with with_server_options(config, McpServerOptions::new().with_timeout(duration)). The timeout bounds connection establishment — transport setup and the MCP initialize handshake — together with initial discovery, and bounds refresh discovery on its own; exceeding it returns McpError::Timeout.
Discovering tools
After connecting, each server's capabilities are available through its discovery snapshot. The tools/resources/prompts fields hold the raw rmcp types — pattern-match on them directly for output_schema, annotations, mime_type, and friends.
use ;
#
# async
Using MCP tools in an agent
The tool registry and capability provider produced by McpServerManager plug straight into the agentkit agent loop. Tools are namespaced as mcp_<server_id>_<tool_name> by default.
use ;
#
# async
Pick a different convention — strip the prefix, replace it with dots, anything — by installing an McpToolNamespace::Custom strategy:
use ;
let manager = new.with_namespace;
Streamable HTTP transport
For modern remote MCP servers exposed over HTTP, use the Streamable HTTP transport. The bearer token (or any custom header) is set declaratively on the binding — rmcp drives the JSON/SSE response handling, session header propagation, and resumption with Last-Event-ID.
use ;
#
# async
To rotate the bearer at runtime, drive an AuthResolution::Provided { credentials, .. } through McpServerManager::resolve_auth (or McpConnection::resolve_auth directly). The next operation reconnects with the new credentials.
Sampling, elicitation, and roots
Servers can issue requests back into the client: sampling/createMessage (ask the host LLM to generate), elicitation/create (ask the user for input), roots/list (enumerate workspace roots in scope). Wire in trait implementations to handle each:
use Arc;
use async_trait;
use ;
;
;
let manager = new.with_handler_config;
McpElicitationResponder follows the same shape. The handler advertises the corresponding ClientCapabilities entry only when a responder is installed — servers that probe client.capabilities.sampling will see the host opt in.
Subscribing to server events
McpConnection::subscribe_events returns a tokio::sync::broadcast::Receiver<McpServerEvent> that surfaces every push notification the server sends:
Progress— keyed by theprogress_tokenthe client issued in a requestLogging—notifications/message; throttled byset_logging_levelResourceUpdated— for URIs the client subscribed to viasubscribe_resourceToolListChanged/ResourceListChanged/PromptListChangedCancelled— server-initiated cancellation of an in-flight request
use ;
# async
Catalog list-changed events are also delivered through the legacy McpServerNotification mpsc receiver consumed by McpServerManager::refresh_changed_catalogs. The two channels coexist: events for live UI/observability, mpsc for re-discovery.
Lifecycle management
use ;
#
# async
manager.refresh_changed_catalogs() drains pending list-changed notifications across every connection and re-runs discovery for each affected server, returning the diffs as McpCatalogEvents.
Custom transports
When you need a transport rmcp supports but McpTransportBinding does not (in-memory pipes, websockets, custom IO), build the rmcp RunningService directly and adopt it:
use ;
use ServiceExt;
# async