Skip to content

Repository files navigation

RMCP

Crates.io Versiondocs.rsCILicense

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.

Table of Contents

Usage

Import the crate

rmcp = { version = "0.16.0", features = ["server"] }
## or dev channelrmcp = { git = "https://github.com/modelcontextprotocol/rust-sdk", branch = "main" }

Third Dependencies

Basic dependencies:

Build a Client

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 Server

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

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

Server-side

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.

Client-side

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

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

Server-side

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,})}}

Client-side

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?;

Notifications

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

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

Server-side

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 list
  • GetPromptResult -- messages with an optional description
  • Result<T, McpError> -- either of the above, with error handling

Client-side

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?;

Notifications

// 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)


Sampling

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

Server-side (requesting 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);

Client-side (handling sampling)

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(&params.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)


Roots

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

Server-side

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}}

Client-side

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?;

Logging

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

Server-side

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.

Client-side

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

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

Server-side

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))}}

Client-side

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

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

Progress 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?;}

Cancellation

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}}

Initialized notification

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}}

List-changed notifications

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


Subscriptions

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

Server-side

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?;

Client-side

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}}

Tasks (long-running tool invocations)

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.

Examples

See examples.

OAuth Support

See Oauth_support for details.

Related Resources

Related Projects

Extending rmcp

Built with rmcp

  • 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

Development

Tips for Contributors

See docs/CONTRIBUTE.MD to get some tips for contributing.

Using Dev Container

If you want to use dev container, see docs/DEVCONTAINER.md for instructions on using Dev Container for development.

About

The official Rust SDK for the Model Context Protocol

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
GitHub - actsalan/rust-sdk: The official Rust SDK for the Model Context Protocol · GitHub
Skip to content

Repository files navigation

RMCP

Crates.io Versiondocs.rsCILicense

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.

Table of Contents

Usage

Import the crate

rmcp = { version = "0.16.0", features = ["server"] }
## or dev channelrmcp = { git = "https://github.com/modelcontextprotocol/rust-sdk", branch = "main" }

Third Dependencies

Basic dependencies:

Build a Client

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 Server

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

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

Server-side

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.

Client-side

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

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

Server-side

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,})}}

Client-side

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?;

Notifications

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

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

Server-side

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 list
  • GetPromptResult -- messages with an optional description
  • Result<T, McpError> -- either of the above, with error handling

Client-side

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?;

Notifications

// 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)


Sampling

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

Server-side (requesting 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);

Client-side (handling sampling)

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(&params.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)


Roots

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

Server-side

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}}

Client-side

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?;

Logging

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

Server-side

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.

Client-side

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

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

Server-side

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))}}

Client-side

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

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

Progress 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?;}

Cancellation

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}}

Initialized notification

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}}

List-changed notifications

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


Subscriptions

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

Server-side

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?;

Client-side

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}}

Tasks (long-running tool invocations)

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.

Examples

See examples.

OAuth Support

See Oauth_support for details.

Related Resources

Related Projects

Extending rmcp

Built with rmcp

  • 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

Development

Tips for Contributors

See docs/CONTRIBUTE.MD to get some tips for contributing.

Using Dev Container

If you want to use dev container, see docs/DEVCONTAINER.md for instructions on using Dev Container for development.

About

The official Rust SDK for the Model Context Protocol

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - actsalan/rust-sdk: The official Rust SDK for the Model Context Protocol · GitHub
Skip to content

Repository files navigation

RMCP

Crates.io Versiondocs.rsCILicense

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.

Table of Contents

Usage

Import the crate

rmcp = { version = "0.16.0", features = ["server"] }
## or dev channelrmcp = { git = "https://github.com/modelcontextprotocol/rust-sdk", branch = "main" }

Third Dependencies

Basic dependencies:

Build a Client

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 Server

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

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

Server-side

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.

Client-side

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

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

Server-side

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,})}}

Client-side

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?;

Notifications

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

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

Server-side

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 list
  • GetPromptResult -- messages with an optional description
  • Result<T, McpError> -- either of the above, with error handling

Client-side

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?;

Notifications

// 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)


Sampling

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

Server-side (requesting 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);

Client-side (handling sampling)

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(&params.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)


Roots

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

Server-side

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}}

Client-side

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?;

Logging

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

Server-side

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.

Client-side

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

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

Server-side

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))}}

Client-side

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

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

Progress 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?;}

Cancellation

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}}

Initialized notification

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}}

List-changed notifications

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


Subscriptions

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

Server-side

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?;

Client-side

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}}

Tasks (long-running tool invocations)

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.

Examples

See examples.

OAuth Support

See Oauth_support for details.

Related Resources

Related Projects

Extending rmcp

Built with rmcp

  • 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

Development

Tips for Contributors

See docs/CONTRIBUTE.MD to get some tips for contributing.

Using Dev Container

If you want to use dev container, see docs/DEVCONTAINER.md for instructions on using Dev Container for development.

About

The official Rust SDK for the Model Context Protocol

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - actsalan/rust-sdk: The official Rust SDK for the Model Context Protocol · GitHub
Skip to content

Repository files navigation

RMCP

Crates.io Versiondocs.rsCILicense

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.

Table of Contents

Usage

Import the crate

rmcp = { version = "0.16.0", features = ["server"] }
## or dev channelrmcp = { git = "https://github.com/modelcontextprotocol/rust-sdk", branch = "main" }

Third Dependencies

Basic dependencies:

Build a Client

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 Server

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

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

Server-side

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.

Client-side

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

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

Server-side

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,})}}

Client-side

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?;

Notifications

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

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

Server-side

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 list
  • GetPromptResult -- messages with an optional description
  • Result<T, McpError> -- either of the above, with error handling

Client-side

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?;

Notifications

// 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)


Sampling

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

Server-side (requesting 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);

Client-side (handling sampling)

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(&params.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)


Roots

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

Server-side

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}}

Client-side

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?;

Logging

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

Server-side

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.

Client-side

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

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

Server-side

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))}}

Client-side

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

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

Progress 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?;}

Cancellation

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}}

Initialized notification

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}}

List-changed notifications

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


Subscriptions

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

Server-side

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?;

Client-side

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}}

Tasks (long-running tool invocations)

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.

Examples

See examples.

OAuth Support

See Oauth_support for details.

Related Resources

Related Projects

Extending rmcp

Built with rmcp

  • 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

Development

Tips for Contributors

See docs/CONTRIBUTE.MD to get some tips for contributing.

Using Dev Container

If you want to use dev container, see docs/DEVCONTAINER.md for instructions on using Dev Container for development.

About

The official Rust SDK for the Model Context Protocol

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' GitHub - actsalan/rust-sdk: The official Rust SDK for the Model Context Protocol · GitHub
Skip to content

Repository files navigation

RMCP

Crates.io Versiondocs.rsCILicense

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.

Table of Contents

Usage

Import the crate

rmcp = { version = "0.16.0", features = ["server"] }
## or dev channelrmcp = { git = "https://github.com/modelcontextprotocol/rust-sdk", branch = "main" }

Third Dependencies

Basic dependencies:

Build a Client

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 Server

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

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

Server-side

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.

Client-side

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

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

Server-side

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,})}}

Client-side

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?;

Notifications

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

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

Server-side

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 list
  • GetPromptResult -- messages with an optional description
  • Result<T, McpError> -- either of the above, with error handling

Client-side

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?;

Notifications

// 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)


Sampling

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

Server-side (requesting 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);

Client-side (handling sampling)

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(&params.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)


Roots

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

Server-side

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}}

Client-side

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?;

Logging

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

Server-side

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.

Client-side

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

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

Server-side

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))}}

Client-side

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

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

Progress 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?;}

Cancellation

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}}

Initialized notification

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}}

List-changed notifications

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


Subscriptions

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

Server-side

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?;

Client-side

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}}

Tasks (long-running tool invocations)

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.

Examples

See examples.

OAuth Support

See Oauth_support for details.

Related Resources

Related Projects

Extending rmcp

Built with rmcp

  • 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

Development

Tips for Contributors

See docs/CONTRIBUTE.MD to get some tips for contributing.

Using Dev Container

If you want to use dev container, see docs/DEVCONTAINER.md for instructions on using Dev Container for development.

About

The official Rust SDK for the Model Context Protocol

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - actsalan/rust-sdk: The official Rust SDK for the Model Context Protocol · GitHub
Skip to content

Repository files navigation

RMCP

Crates.io Versiondocs.rsCILicense

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.

Table of Contents

Usage

Import the crate

rmcp = { version = "0.16.0", features = ["server"] }
## or dev channelrmcp = { git = "https://github.com/modelcontextprotocol/rust-sdk", branch = "main" }

Third Dependencies

Basic dependencies:

Build a Client

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 Server

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

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

Server-side

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.

Client-side

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

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

Server-side

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,})}}

Client-side

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?;

Notifications

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

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

Server-side

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 list
  • GetPromptResult -- messages with an optional description
  • Result<T, McpError> -- either of the above, with error handling

Client-side

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?;

Notifications

// 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)


Sampling

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

Server-side (requesting 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);

Client-side (handling sampling)

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(&params.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)


Roots

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

Server-side

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}}

Client-side

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?;

Logging

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

Server-side

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.

Client-side

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

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

Server-side

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))}}

Client-side

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

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

Progress 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?;}

Cancellation

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}}

Initialized notification

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}}

List-changed notifications

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


Subscriptions

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

Server-side

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?;

Client-side

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}}

Tasks (long-running tool invocations)

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.

Examples

See examples.

OAuth Support

See Oauth_support for details.

Related Resources

Related Projects

Extending rmcp

Built with rmcp

  • 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

Development

Tips for Contributors

See docs/CONTRIBUTE.MD to get some tips for contributing.

Using Dev Container

If you want to use dev container, see docs/DEVCONTAINER.md for instructions on using Dev Container for development.

About

The official Rust SDK for the Model Context Protocol

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - actsalan/rust-sdk: The official Rust SDK for the Model Context Protocol · GitHub
Skip to content

Repository files navigation

RMCP

Crates.io Versiondocs.rsCILicense

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.

Table of Contents

Usage

Import the crate

rmcp = { version = "0.16.0", features = ["server"] }
## or dev channelrmcp = { git = "https://github.com/modelcontextprotocol/rust-sdk", branch = "main" }

Third Dependencies

Basic dependencies:

Build a Client

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 Server

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

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

Server-side

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.

Client-side

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

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

Server-side

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,})}}

Client-side

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?;

Notifications

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

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

Server-side

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 list
  • GetPromptResult -- messages with an optional description
  • Result<T, McpError> -- either of the above, with error handling

Client-side

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?;

Notifications

// 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)


Sampling

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

Server-side (requesting 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);

Client-side (handling sampling)

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(&params.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)


Roots

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

Server-side

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}}

Client-side

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?;

Logging

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

Server-side

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.

Client-side

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

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

Server-side

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))}}

Client-side

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

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

Progress 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?;}

Cancellation

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}}

Initialized notification

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}}

List-changed notifications

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


Subscriptions

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

Server-side

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?;

Client-side

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}}

Tasks (long-running tool invocations)

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.

Examples

See examples.

OAuth Support

See Oauth_support for details.

Related Resources

Related Projects

Extending rmcp

Built with rmcp

  • 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

Development

Tips for Contributors

See docs/CONTRIBUTE.MD to get some tips for contributing.

Using Dev Container

If you want to use dev container, see docs/DEVCONTAINER.md for instructions on using Dev Container for development.

About

The official Rust SDK for the Model Context Protocol

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); GitHub - actsalan/rust-sdk: The official Rust SDK for the Model Context Protocol · GitHub
Skip to content

Repository files navigation

RMCP

Crates.io Versiondocs.rsCILicense

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.

Table of Contents

Usage

Import the crate

rmcp = { version = "0.16.0", features = ["server"] }
## or dev channelrmcp = { git = "https://github.com/modelcontextprotocol/rust-sdk", branch = "main" }

Third Dependencies

Basic dependencies:

Build a Client

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 Server

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

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

Server-side

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.

Client-side

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

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

Server-side

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,})}}

Client-side

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?;

Notifications

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

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

Server-side

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 list
  • GetPromptResult -- messages with an optional description
  • Result<T, McpError> -- either of the above, with error handling

Client-side

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?;

Notifications

// 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)


Sampling

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

Server-side (requesting 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);

Client-side (handling sampling)

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(&params.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)


Roots

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

Server-side

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}}

Client-side

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?;

Logging

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

Server-side

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.

Client-side

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

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

Server-side

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))}}

Client-side

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

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

Progress 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?;}

Cancellation

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}}

Initialized notification

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}}

List-changed notifications

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


Subscriptions

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

Server-side

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?;

Client-side

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}}

Tasks (long-running tool invocations)

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.

Examples

See examples.

OAuth Support

See Oauth_support for details.

Related Resources

Related Projects

Extending rmcp

Built with rmcp

  • 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

Development

Tips for Contributors

See docs/CONTRIBUTE.MD to get some tips for contributing.

Using Dev Container

If you want to use dev container, see docs/DEVCONTAINER.md for instructions on using Dev Container for development.

About

The official Rust SDK for the Model Context Protocol

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages