Model Context Protocol
The Model Context Protocol (MCP) is a standard way to expose tools to LLM applications. An MCP server hosts tools; an MCP client discovers and calls them. Rig agents act as clients through rmcp, the official Rust MCP SDK: each MCP tool becomes a Rig tool the agent can call.
Enable the rmcp feature on rig (native targets only, not WASM), and add rmcp 2 with the client transport you need:
[dependencies]rig = { version = "0.44.0", features = ["rmcp"] }rmcp = { version = "2", features = ["client", "transport-streamable-http-client-reqwest"] }tokio = { version = "1", features = ["full"] }The integration lives in rig::tool::rmcp. It also re-exports the rmcp crate it was built against as rig::tool::rmcp::rmcp.
Static tools: list once, add to the agent
Section titled “Static tools: list once, add to the agent”The simplest setup fetches the server’s tools once. Wrap each rmcp tool definition in an McpTool (which keeps a handle to the server), turn it into a Rig DynamicTool, and add the tools to the agent:
use rig::prelude::*;use rig::providers::openai::{self, OpenAI};use rig::tool::DynamicTool;use rig::tool::rmcp::{McpTool, rmcp::service::ServerSink};
/// `server` is the peer of a connected MCP client (`client.peer().clone()`).async fn mcp_agent(server: ServerSink) -> anyhow::Result<Agent> { let tools = server .list_all_tools() .await? .into_iter() .map(|tool| DynamicTool::try_from(McpTool::from_mcp_server(tool, server.clone()))) .collect::<Result<Vec<_>, _>>()?;
Ok(AgentBuilder::new(OpenAI::from_env()?.completion(openai::GPT_5_5)) .preamble("You can use the tools of an MCP server.") .dynamic_tools(tools) .build())}rig::tool::rmcp::tools_from_server(tools, &server) does the wrapping step for a whole list. To get the ServerSink, connect a client with rmcp:
use rmcp::ServiceExt;use rmcp::model::{ClientCapabilities, ClientInfo, Implementation};use rmcp::transport::StreamableHttpClientTransport;
let client_info = ClientInfo::new( ClientCapabilities::default(), Implementation::new("my-agent", "0.1.0"),);let transport = StreamableHttpClientTransport::from_uri("http://localhost:8080");let client = client_info.serve(transport).await?;
let agent = mcp_agent(client.peer().clone()).await?;let answer = agent.prompt("What is 2 + 5?").await?.output();Keep client alive while the agent runs: dropping it closes the connection, and the tools stop working.
Live tools: follow the server’s tool list
Section titled “Live tools: follow the server’s tool list”MCP servers can change their tools at runtime and announce it with notifications/tools/list_changed. McpClientHandler is an rmcp client handler that registers the server’s tools in a shared Rig ToolServer and re-registers them whenever the list changes. Give the agent the same tool server handle:
use rig::prelude::*;use rig::providers::openai::{self, OpenAI};use rig::tool::rmcp::McpClientHandler;use rig::tool::server::ToolServer;use rmcp::model::{ClientCapabilities, ClientInfo, Implementation};
let tool_server = ToolServer::new().run();
let client_info = ClientInfo::new( ClientCapabilities::default(), Implementation::new("my-agent", "0.1.0"),);let handler = McpClientHandler::new(client_info, tool_server.clone());
// Connects, fetches the initial tool list, and keeps it in sync.let transport = rmcp::transport::StreamableHttpClientTransport::from_uri("http://localhost:8080");let mcp = handler.connect(transport).await?;
let agent = AgentBuilder::new(OpenAI::from_env()?.completion(openai::GPT_5_5)) .preamble("You can use the tools of an MCP server.") .tool_server_handle(tool_server) .build();
let answer = agent.prompt("What is 2 + 5?").max_turns(2).await?.output();A refresh only replaces or removes the registrations this handler made, so tools you registered yourself under other names are left alone.
Timeouts, metadata and results
Section titled “Timeouts, metadata and results”- Timeouts. Each MCP call has a 5-minute deadline by default (
DEFAULT_MCP_TOOL_TIMEOUT). Change it per tool withMcpTool::with_timeout(..)or for every tool of a handler withMcpClientHandler::with_timeout(..); passNonefor no deadline. A timed-out call fails like any other tool error and Rig tries to cancel it on the server. - Request metadata. To send MCP
_metawith a call without exposing it to the model, putMcpMeta(meta)in the call’sToolContext. - Results. The model sees the tool’s content. The full
CallToolResult, itsstructuredContentand its response_metaare published to the tool context’s results (McpCallToolResult,McpStructuredContent,McpResponseMeta) for your hooks and code to read. A result the server marksis_errorbecomes a failed tool call. - Disconnects. Each MCP tool carries a liveness check tied to its connection, so a tool registry can retire tools whose server went away.
Building your own MCP server
Section titled “Building your own MCP server”rmcp also builds servers. Enable its server features:
[dependencies]rmcp = { version = "2", features = ["server", "macros", "transport-streamable-http-server"] }schemars = "1"serde = { version = "1", features = ["derive"] }Tools are methods on a type that holds a ToolRouter. #[tool_router] collects the #[tool] methods, and #[tool_handler] wires the router into ServerHandler:
use rmcp::{ ServerHandler, handler::server::{router::tool::ToolRouter, wrapper::Parameters}, model::{CallToolResult, ContentBlock, ErrorData, ServerCapabilities, ServerInfo}, schemars, tool, tool_handler, tool_router,};
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]struct SumRequest { a: i32, b: i32,}
#[derive(Clone)]struct Calculator { tool_router: ToolRouter<Calculator>,}
#[tool_router]impl Calculator { fn new() -> Self { Self { tool_router: Self::tool_router() } }
#[tool(description = "Calculate the sum of two numbers")] fn sum(&self, Parameters(SumRequest { a, b }): Parameters<SumRequest>) -> Result<CallToolResult, ErrorData> { Ok(CallToolResult::success(vec![ContentBlock::text((a + b).to_string())])) }}
#[tool_handler(router = self.tool_router)]impl ServerHandler for Calculator { fn get_info(&self) -> ServerInfo { ServerInfo::new(ServerCapabilities::builder().enable_tools().build()) }}Serve it over HTTP with rmcp::transport::streamable_http_server::StreamableHttpService, mounted in hyper or axum.
The rmcp example in the Rig repository runs a server like this and an auto-updating MCP agent in one program.
See also
Section titled “See also”- Tools: how Rig tools,
DynamicTooland the tool server work - Agents
rig-rmcpon docs.rs andrmcpon docs.rs
