An official Rust Model Context Protocol SDK implementation with tokio async runtime.
Migrating to 1.x? See the migration guide for breaking changes and upgrade instructions.
This repository contains the following crates:
- rmcp: The core crate providing the RMCP protocol implementation - see rmcp
- rmcp-macros: A procedural macro crate for generating RMCP tool implementations - see rmcp-macros
For the full MCP specification, see modelcontextprotocol.io.
- Usage
- Tools
- Resources
- Prompts
- Sampling
- Roots
- Logging
- Completions
- Notifications
- Subscriptions
- Tasks
- Examples
- OAuth Support
- Related Resources
- Related Projects
- Development
rmcp = { version = "0.16.0", features = ["server"] }
## or dev channelrmcp = { git = "https://github.com/modelcontextprotocol/rust-sdk", branch = "main" }Basic dependencies:
Start a client
use rmcp::{ServiceExt, transport::{TokioChildProcess,ConfigureCommandExt}};use tokio::process::Command;#[tokio::main]asyncfnmain() -> Result<(),Box<dyn std::error::Error>>{let client = ().serve(TokioChildProcess::new(Command::new("npx").configure(|cmd| {
cmd.arg("-y").arg("@modelcontextprotocol/server-everything");}))?).await?;Ok(())}Build a transport
use tokio::io::{stdin, stdout};let transport = (stdin(),stdout());Build a service
You can easily build a service by using ServerHandler or ClientHandler.
let service = common::counter::Counter::new();Start the server
// this call will finish the initialization processlet server = service.serve(transport).await?;Interact with the server
Once the server is initialized, you can send requests or notifications:
// requestlet roots = server.list_roots().await?;// or send notification
server.notify_cancelled(...).await?;Waiting for service shutdown
let quit_reason = server.waiting().await?;// or cancel itlet quit_reason = server.cancel().await?;Tools let servers expose callable functions to clients. Each tool has a name, description, and a JSON Schema for its parameters. Clients discover tools via list_tools and invoke them via call_tool.
MCP Spec:Tools
The #[tool], #[tool_router], and #[tool_handler] macros handle all the wiring. For a tools-only server you can use #[tool_router(server_handler)] to skip the separate ServerHandler impl:
use rmcp::{handler::server::wrapper::Parameters, schemars, tool, tool_router,ServiceExt, transport::stdio};#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]structAddParams{a:i32,b:i32,}#[derive(Clone)]structCalculator;#[tool_router(server_handler)]implCalculator{#[tool(description = "Add two numbers")]fnadd(&self,Parameters(AddParams{ a, b }):Parameters<AddParams>) -> String{(a + b).to_string()}}#[tokio::main]asyncfnmain() -> anyhow::Result<()>{let service = Calculator.serve(stdio()).await?;
service.waiting().await?;Ok(())}The generated tool inputSchema and outputSchema are derived from the fields of T. The type name and documentation on T are ignored; only field names, field types, and field documentation are used.
When you need custom server metadata or multiple capabilities (tools + prompts), use explicit #[tool_handler]:
use rmcp::{handler::server::wrapper::Parameters, schemars, tool, tool_router, tool_handler,ServerHandler,ServiceExt};#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]structAddParams{a:i32,b:i32,}#[derive(Clone)]structCalculator;#[tool_router]implCalculator{#[tool(description = "Add two numbers")]fnadd(&self,Parameters(AddParams{ a, b }):Parameters<AddParams>) -> String{(a + b).to_string()}}#[tool_handler(name = "calculator", version = "1.0.0", instructions = "A simple calculator")]implServerHandlerforCalculator{}See crates/rmcp-macros for full macro documentation.
use rmcp::model::CallToolRequestParams;// List all toolslet tools = client.list_all_tools().await?;// Call a tool by namelet result = client.call_tool(CallToolRequestParams::new("add")).await?;Example:examples/servers/src/common/calculator.rs (server), examples/servers/src/calculator_stdio.rs (stdio runner)
Resources let servers expose data (files, database records, API responses) that clients can read. Each resource is identified by a URI and returns content as text or binary (base64-encoded) data. Resource templates allow servers to declare URI patterns with dynamic parameters.
MCP Spec:Resources
Implement list_resources(), read_resource(), and optionally list_resource_templates() on the ServerHandler trait. Enable the resources capability in get_info().
use rmcp::{ErrorDataasMcpError,RoleServer,ServerHandler,ServiceExt,
model::*,
service::RequestContext,
transport::stdio,};use serde_json::json;#[derive(Clone)]structMyServer;implServerHandlerforMyServer{fnget_info(&self) -> ServerInfo{ServerInfo::new(ServerCapabilities::builder().enable_resources().build(),)}asyncfnlist_resources(&self,_request:Option<PaginatedRequestParams>,_context:RequestContext<RoleServer>,) -> Result<ListResourcesResult,McpError>{Ok(ListResourcesResult{resources:vec![Resource::new("file:///config.json","config"),Resource::new("memo://insights","insights"),],next_cursor:None,meta:None,})}asyncfnread_resource(&self,request:ReadResourceRequestParams,_context:RequestContext<RoleServer>,) -> Result<ReadResourceResult,McpError>{match request.uri.as_str(){"file:///config.json" => Ok(ReadResourceResult::new(vec![ResourceContents::text(r#"{"key": "value"}"#,&request.uri),])),"memo://insights" => Ok(ReadResourceResult::new(vec![ResourceContents::text("Analysis results...",&request.uri),])),
_ => Err(McpError::resource_not_found("resource_not_found",Some(json!({"uri": request.uri })),)),}}asyncfnlist_resource_templates(&self,_request:Option<PaginatedRequestParams>,_context:RequestContext<RoleServer>,) -> Result<ListResourceTemplatesResult,McpError>{Ok(ListResourceTemplatesResult{resource_templates:vec![],next_cursor:None,meta:None,})}}use rmcp::model::{ReadResourceRequestParams};// List all resources (handles pagination automatically)let resources = client.list_all_resources().await?;// Read a specific resource by URIlet result = client.read_resource(ReadResourceRequestParams::new("file:///config.json"),).await?;// List resource templateslet templates = client.list_all_resource_templates().await?;Servers can notify clients when the resource list changes or when a specific resource is updated:
// Notify that the resource list has changed (clients should re-fetch)
context.peer.notify_resource_list_changed().await?;// Notify that a specific resource was updated
context.peer.notify_resource_updated(ResourceUpdatedNotificationParam::new("file:///config.json"),).await?;Clients handle these via ClientHandler:
implClientHandlerforMyClient{asyncfnon_resource_list_changed(&self,_context:NotificationContext<RoleClient>,){// Re-fetch the resource list}asyncfnon_resource_updated(&self,params:ResourceUpdatedNotificationParam,_context:NotificationContext<RoleClient>,){// Re-read the updated resource at params.uri}}Example:examples/servers/src/common/counter.rs (server), examples/clients/src/everything_stdio.rs (client)
Prompts are reusable message templates that servers expose to clients. They accept typed arguments and return conversation messages. The #[prompt] macro handles argument validation and routing automatically.
MCP Spec:Prompts
Use the #[prompt_router], #[prompt], and #[prompt_handler] macros to define prompts declaratively. Arguments are defined as structs deriving JsonSchema.
use rmcp::{ErrorDataasMcpError,RoleServer,ServerHandler,ServiceExt,
handler::server::{router::prompt::PromptRouter, wrapper::Parameters},
model::*,
prompt, prompt_handler, prompt_router,
schemars::JsonSchema,
service::RequestContext,
transport::stdio,};use serde::{Deserialize,Serialize};#[derive(Debug,Serialize,Deserialize,JsonSchema)]pubstructCodeReviewArgs{#[schemars(description = "Programming language of the code")]publanguage:String,#[schemars(description = "Focus areas for the review")]pubfocus_areas:Option<Vec<String>>,}#[derive(Clone)]pubstructMyServer{prompt_router:PromptRouter<Self>,}#[prompt_router]implMyServer{fnnew() -> Self{Self{prompt_router:Self::prompt_router()}}/// Simple prompt without parameters#[prompt(name = "greeting", description = "A simple greeting")]asyncfngreeting(&self) -> Vec<PromptMessage>{vec![PromptMessage::new_text(Role::User,"Hello! How can you help me today?",)]}/// Prompt with typed arguments#[prompt(name = "code_review", description = "Review code in a given language")]asyncfncode_review(&self,Parameters(args):Parameters<CodeReviewArgs>,) -> Result<GetPromptResult,McpError>{let focus = args.focus_areas.unwrap_or_else(|| vec!["correctness".into()]);Ok(GetPromptResult::new(vec![PromptMessage::new_text(Role::User,
format!("Review my {} code. Focus on: {}", args.language, focus.join(", ")),),]).with_description(format!("Code review for {}", args.language)))}}#[prompt_handler]implServerHandlerforMyServer{fnget_info(&self) -> ServerInfo{ServerInfo::new(ServerCapabilities::builder().enable_prompts().build())}}Prompt functions support several return types:
Vec<PromptMessage>-- simple message listGetPromptResult-- messages with an optional descriptionResult<T, McpError>-- either of the above, with error handling
use rmcp::model::GetPromptRequestParams;// List all promptslet prompts = client.list_all_prompts().await?;// Get a prompt with argumentslet result = client.get_prompt(GetPromptRequestParams{meta:None,name:"code_review".into(),arguments:Some(rmcp::object!({"language":"Rust","focus_areas":["performance","safety"]})),}).await?;// Server: notify that available prompts have changed
context.peer.notify_prompt_list_changed().await?;Example:examples/servers/src/prompt_stdio.rs (server), examples/clients/src/everything_stdio.rs (client)
Deprecated (SEP-2577): Sampling is deprecated and will be removed in a future release. It remains fully functional for now. See SEP-2577.
Sampling flips the usual direction: the server asks the client to run an LLM completion. The server sends a create_message request, the client processes it through its LLM, and returns the result.
MCP Spec:Sampling
Access the client's sampling capability through context.peer.create_message():
use rmcp::model::*;// Inside a ServerHandler method (e.g., call_tool):let response = context.peer.create_message(CreateMessageRequestParams::new(vec![SamplingMessage::user_text("Explain this error: connection refused")],150,).with_model_preferences(ModelPreferences::new().with_hints(vec![ModelHint::new("claude")]).with_cost_priority(0.3).with_speed_priority(0.8).with_intelligence_priority(0.7),).with_system_prompt("You are a helpful assistant.").with_include_context(ContextInclusion::None).with_temperature(0.7),).await?;// Extract the response textlet text = response.message.content.first().and_then(|c| c.as_text()).map(|t| &t.text);On the client side, implement ClientHandler::create_message(). This is where you'd call your actual LLM:
use rmcp::{ClientHandler, model::*, service::{RequestContext,RoleClient}};#[derive(Clone,Default)]structMyClient;implClientHandlerforMyClient{asyncfncreate_message(&self,params:CreateMessageRequestParams,_context:RequestContext<RoleClient>,) -> Result<CreateMessageResult,ErrorData>{// Forward to your LLM, or return a mock response:let response_text = call_your_llm(¶ms.messages).await;Ok(CreateMessageResult::new(SamplingMessage::assistant_text(response_text),"my-model".into(),).with_stop_reason(CreateMessageResult::STOP_REASON_END_TURN))}}Example:examples/servers/src/sampling_stdio.rs (server), examples/clients/src/sampling_stdio.rs (client)
Deprecated (SEP-2577): Roots is deprecated and will be removed in a future release. It remains fully functional for now. See SEP-2577.
Roots tell servers which directories or projects the client is working in. A root is a URI (typically file://) pointing to a workspace or repository. Servers can query roots to know where to look for files and how to scope their work.
MCP Spec:Roots
Ask the client for its root list, and handle change notifications:
use rmcp::{ServerHandler, model::*, service::{NotificationContext,RoleServer}};implServerHandlerforMyServer{// Query the client for its rootsasyncfncall_tool(&self,request:CallToolRequestParams,context:RequestContext<RoleServer>,) -> Result<CallToolResult,ErrorData>{let roots = context.peer.list_roots().await?;// Use roots.roots to understand workspace boundaries// ...}// Called when the client's root list changesasyncfnon_roots_list_changed(&self,_context:NotificationContext<RoleServer>,){// Re-fetch roots to stay current}}Clients declare roots capability and implement list_roots():
use rmcp::{ClientHandler, model::*};implClientHandlerforMyClient{asyncfnlist_roots(&self,_context:RequestContext<RoleClient>,) -> Result<ListRootsResult,ErrorData>{Ok(ListRootsResult::new(vec![Root::new("file:///home/user/project").with_name("My Project"),]))}}Clients notify the server when roots change:
// After adding or removing a workspace root:
client.notify_roots_list_changed().await?;Deprecated (SEP-2577): Logging is deprecated and will be removed in a future release. It remains fully functional for now. See SEP-2577.
Servers can send structured log messages to clients. The client sets a minimum severity level, and the server sends messages through the peer notification interface.
MCP Spec:Logging
Enable the logging capability, handle level changes from the client, and send log messages via the peer:
use rmcp::{ServerHandler, model::*, service::RequestContext};implServerHandlerforMyServer{fnget_info(&self) -> ServerInfo{ServerInfo::new(ServerCapabilities::builder().enable_logging().build(),)}// Client sets the minimum log levelasyncfnset_level(&self,request:SetLevelRequestParams,_context:RequestContext<RoleServer>,) -> Result<(),ErrorData>{// Store request.level and filter future log messages accordinglyOk(())}}// Send a log message from any handler with access to the peer:
context.peer.notify_logging_message(LoggingMessageNotificationParam::new(LoggingLevel::Info,
serde_json::json!({"message":"Processing completed","items_processed":42}),).with_logger("my-server"),).await?;Available log levels (from least to most severe): Debug, Info, Notice, Warning, Error, Critical, Alert, Emergency.
Clients handle incoming log messages via ClientHandler:
implClientHandlerforMyClient{asyncfnon_logging_message(&self,params:LoggingMessageNotificationParam,_context:NotificationContext<RoleClient>,){println!("[{}] {}: {}", params.level,
params.logger.unwrap_or_default(), params.data);}}Clients can also set the server's log level:
client.set_level(SetLevelRequestParams::new(LoggingLevel::Warning)).await?;Completions give auto-completion suggestions for prompt or resource template arguments. As a user fills in arguments, the client can ask the server for suggestions based on what's already been entered.
MCP Spec:Completions
Enable the completions capability and implement the complete() handler. Use request.context to inspect previously filled arguments:
use rmcp::{ErrorDataasMcpError,ServerHandler, model::*, service::RequestContext,RoleServer};implServerHandlerforMyServer{fnget_info(&self) -> ServerInfo{ServerInfo::new(ServerCapabilities::builder().enable_completions().enable_prompts().build(),)}asyncfncomplete(&self,request:CompleteRequestParams,_context:RequestContext<RoleServer>,) -> Result<CompleteResult,McpError>{let values = match&request.r#ref{Reference::Prompt(prompt_ref)if prompt_ref.name == "sql_query" => {match request.argument.name.as_str(){"operation" => vec!["SELECT","INSERT","UPDATE","DELETE"],"table" => vec!["users","orders","products"],"columns" => {// Adapt suggestions based on previously filled argumentsifletSome(ctx) = &request.context{ifletSome(op) = ctx.get_argument("operation"){match op.to_uppercase().as_str(){"SELECT" | "UPDATE" => {vec!["id","name","email","created_at"]}
_ => vec![],}}else{vec![]}}else{vec![]}}
_ => vec![],}}
_ => vec![],};// Filter by the user's partial inputlet filtered:Vec<String> = values.into_iter().map(String::from).filter(|v| v.to_lowercase().contains(&request.argument.value.to_lowercase())).collect();let completion = CompletionInfo::with_pagination(filtered,None,false).map_err(|e| McpError::internal_error(e,None))?;Ok(CompleteResult::new(completion))}}use rmcp::model::*;let result = client.complete(CompleteRequestParams::new(Reference::for_prompt("sql_query"),ArgumentInfo::new("operation","SEL"),)).await?;// result.completion.values contains suggestions like ["SELECT"]Example:examples/servers/src/completion_stdio.rs
Notifications are fire-and-forget messages -- no response is expected. They cover progress updates, cancellation, and lifecycle events. Both sides can send and receive them.
MCP Spec:Notifications
Servers can report progress during long-running operations:
use rmcp::model::*;// Inside a tool handler:for i in0..total_items {process_item(i).await;
context.peer.notify_progress(ProgressNotificationParam::new(ProgressToken(NumberOrString::Number(i asi64)),
i asf64,).with_total(total_items asf64).with_message(format!("Processing item {}/{}", i + 1, total_items)),).await?;}Either side can cancel an in-progress request:
// Send a cancellation
context.peer.notify_cancelled(CancelledNotificationParam::new(Some(the_request_id),Some("User requested cancellation".into()),)).await?;Handle cancellation in ServerHandler or ClientHandler:
implServerHandlerforMyServer{asyncfnon_cancelled(&self,params:CancelledNotificationParam,_context:NotificationContext<RoleServer>,){// Abort work for params.request_id}}Clients send initialized after the handshake completes:
// Sent automatically by rmcp during the serve() handshake.// Servers handle it via:implServerHandlerforMyServer{asyncfnon_initialized(&self,_context:NotificationContext<RoleServer>,){// Server is ready to receive requests}}When available tools, prompts, or resources change, tell the client:
context.peer.notify_tool_list_changed().await?;
context.peer.notify_prompt_list_changed().await?;
context.peer.notify_resource_list_changed().await?;Example:examples/servers/src/common/progress_demo.rs
Clients can subscribe to specific resources. When a subscribed resource changes, the server sends a notification and the client can re-read it.
MCP Spec:Resources - Subscriptions
Enable subscriptions in the resources capability and implement the subscribe() / unsubscribe() handlers:
use rmcp::{ErrorDataasMcpError,ServerHandler, model::*, service::RequestContext,RoleServer};use std::sync::Arc;use tokio::sync::Mutex;use std::collections::HashSet;#[derive(Clone)]structMyServer{subscriptions:Arc<Mutex<HashSet<String>>>,}implServerHandlerforMyServer{fnget_info(&self) -> ServerInfo{ServerInfo::new(ServerCapabilities::builder().enable_resources().enable_resources_subscribe().build(),)}asyncfnsubscribe(&self,request:SubscribeRequestParams,_context:RequestContext<RoleServer>,) -> Result<(),McpError>{self.subscriptions.lock().await.insert(request.uri);Ok(())}asyncfnunsubscribe(&self,request:UnsubscribeRequestParams,_context:RequestContext<RoleServer>,) -> Result<(),McpError>{self.subscriptions.lock().await.remove(&request.uri);Ok(())}}When a subscribed resource changes, notify the client:
// Check if the resource has subscribers, then notify
context.peer.notify_resource_updated(ResourceUpdatedNotificationParam::new("file:///config.json"),).await?;use rmcp::model::*;// Subscribe to updates for a resource
client.subscribe(SubscribeRequestParams::new("file:///config.json")).await?;// Unsubscribe when no longer needed
client.unsubscribe(UnsubscribeRequestParams::new("file:///config.json")).await?;Handle update notifications in ClientHandler:
implClientHandlerforMyClient{asyncfnon_resource_updated(&self,params:ResourceUpdatedNotificationParam,_context:NotificationContext<RoleClient>,){// Re-read the resource at params.uri}}rmcp supports the task-based tool invocation
flow defined in SEP-1319. Annotate a tool with execution(task_support = "required" | "optional")
and add #[task_handler] to your ServerHandler impl — enqueue_task, tasks/list, tasks/get,
tasks/result, and tasks/cancel are generated for you on top of an OperationProcessor.
#[tool( description = "Sum two numbers after a 2-second delay", execution(task_support = "required"))]asyncfnslow_sum(/* ... */) -> Result<CallToolResult,McpError>{/* ... */}#[tool_handler]#[task_handler]implServerHandlerforTaskDemo{}See servers_task_stdio and the matching
clients_task_stdio for a runnable end-to-end example.
See examples.
See Oauth_support for details.
- rmcp-actix-web - An
actix_webbackend forrmcp - rmcp-openapi - Transform OpenAPI definition endpoints into MCP tools
- goose - An open-source, extensible AI agent that goes beyond code suggestions
- apollo-mcp-server - MCP server that connects AI agents to GraphQL APIs via Apollo GraphOS
- rustfs-mcp - High-performance MCP server providing S3-compatible object storage operations for AI/LLM integration
- containerd-mcp-server - A containerd-based MCP server implementation
- rmcp-openapi-server - High-performance MCP server that exposes OpenAPI definition endpoints as MCP tools
- nvim-mcp - A MCP server to interact with Neovim
- terminator - AI-powered desktop automation MCP server with cross-platform support and >95% success rate
- stakpak-agent - Security-hardened terminal agent for DevOps with MCP over mTLS, streaming, secret tokenization, and async task management
- video-transcriber-mcp-rs - High-performance MCP server for transcribing videos from 1000+ platforms using whisper.cpp
- NexusCore MCP - Advanced malware analysis & dynamic instrumentation MCP server with Frida integration and stealth unpacking capabilities
- spreadsheet-mcp - Token-efficient MCP server for spreadsheet analysis with automatic region detection, recalculation, screenshot, and editing support for LLM agents
- hyper-mcp - A fast, secure MCP server that extends its capabilities through WebAssembly (WASM) plugins
- rudof-mcp - RDF validation and data processing MCP server with ShEx/SHACL validation, SPARQL queries, and format conversion. Supports stdio and streamable HTTP transports with full MCP capabilities (tools, prompts, resources, logging, completions, tasks)
- MCPMate - Desktop app for progressive MCP management: start with guided server import, then grow into multi-client profiles and Unify meta tools to keep tool exposure, token use, and runtime state under control, with more options for efficiency, cost, and reliability
- McpMux - Desktop app to configure MCP servers once at McpMux, connect every AI client (Cursor, Claude Desktop, VS Code, Windsurf) through a single encrypted local gateway with Spaces for project organization, FeatureSets to switch toolsets per client, and a built-in server registry
- systemprompt-template - Single-binary Rust runtime providing MCP governance — authentication, authorisation, rate-limiting, audit trails, and cost tracking for AI agents. Self-hosted, air-gap capable, 3,300+ req/s with sub-5ms governance overhead
- jilebi-mcp - an extensible MCP server through plugins in Javascript with a secure permissions model
See docs/CONTRIBUTE.MD to get some tips for contributing.
If you want to use dev container, see docs/DEVCONTAINER.md for instructions on using Dev Container for development.