The official Rust SDK for the Model Context Protocol
# Add to your Claude Code skills
git clone https://github.com/modelcontextprotocol/rust-sdkGuides for using mcp servers skills like rust-sdk.
Last scanned: 8/4/2026
{
"issues": [],
"status": "PASSED",
"scannedAt": "2026-08-04T06:27:21.486Z",
"npmAuditRan": true,
"pipAuditRan": true,
"promptInjectionRan": true
}rust-sdk is an open-source mcp servers skill for AI coding assistants such as Claude Code, Codex CLI, and ChatGPT, built by modelcontextprotocol. The official Rust SDK for the Model Context Protocol. It has 3,745 GitHub stars.
Yes. rust-sdk passed SkillsLLM's automated security scan — a dependency vulnerability audit plus prompt-injection heuristics — with no high-severity issues. You can read the full report in the Security Report section on this page.
Clone the repository with "git clone https://github.com/modelcontextprotocol/rust-sdk" and add it to your Claude Code skills directory (see the Installation section above).
rust-sdk is primarily written in Rust. It is open-source under modelcontextprotocol on GitHub, so you can review or fork the full source.
Yes. SkillsLLM lists many other MCP Servers skills you can browse and compare side by side. Open the MCP Servers category from the badge at the top of this page, or use the Related Skills and comparison links further down to weigh rust-sdk against similar tools.
No comments yet. Be the first to share your thoughts!
Top skills in this category by stars
An official Rust Model Context Protocol SDK implementation with tokio async runtime.
Migrating to 3.x? See the migration guide for breaking changes and upgrade instructions.
This repository contains the following crates:
This SDK implements the stable MCP 2026-07-28 specification while
remaining fully compatible with the 2025-11-25 release and earlier
versions. Features introduced in 2026-07-28 — server discovery & negotiation,
transport-neutral subscriptions, long-running tasks, response caching,
multi-round-trip requests, and standard HTTP routing headers — are documented
below. For the full MCP specification, see
modelcontextprotocol.io.
Add the latest published version with cargo:
cargo add rmcp --features server
Or use the dev channel:
cargo add rmcp --features server --git https://github.com/modelcontextprotocol/rust-sdk --branch main
Basic dependencies:
use rmcp::{ServiceExt, transport::{TokioChildProcess, ConfigureCommandExt}};
use tokio::process::Command;
#[tokio::main]
async fn main() -> 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(())
}
serve() uses the legacy MCP lifecycle: the client sends initialize, receives
the negotiated server information, and then sends notifications/initialized.
Use ClientServiceExt::serve_with_lifecycle to
select another lifecycle explicitly:
use rmcp::{ClientInfo, ClientLifecycleMode, ClientServiceExt, ProtocolVersion};
// Start directly with server/discover and include client metadata on every request.
let client = ClientInfo::default()
.serve_with_lifecycle(
transport,
ClientLifecycleMode::Discover {
preferred_versions: vec![ProtocolVersion::V_2026_07_28],
},
)
.await?;
// Or probe the discover lifecycle and fall back when a legacy server reports
// that server/discover is not implemented.
let client = ClientInfo::default()
.serve_with_lifecycle(
transport,
ClientLifecycleMode::Auto {
preferred_versions: vec![ProtocolVersion::V_2026_07_28],
legacy_version: Some(ProtocolVersion::V_2025_11_25),
},
)
.await?;
ClientLifecycleMode::Initialize is equivalent to the existing serve() behavior.
Discover startup does not send notifications/initialized; discovery completes
startup, and each subsequent request carries its protocol version, client
information, and capabilities in _meta.
use tokio::io::{stdin, stdout};
let transport = (stdin(), stdout());
You can easily build a service by using ServerHandler or ClientHandler.
let service = common::counter::Counter::new();
// this call will finish the initialization process
let server = service.serve(transport).await?;
Once the server is initialized, you can send requests or notifications:
// request
let roots = server.list_roots().await?;
// or send notification
server.notify_cancelled(...).await?;
let quit_reason = server.waiting().await?;
// or cancel it
let quit_reason = server.cancel().await?;
Tools let servers expose callable functions to clients. Each tool has a name, description, and a JSON Schema for its parameters. Clients discover tools via list_tools and invoke them via call_tool.
MCP Spec: Tools
The #[tool], #[tool_router], and #[tool_handler] macros handle all the wiring. For a tools-only server you can use #[tool_router(server_handler)] to skip the separate ServerHandler impl:
use rmcp::{handler::server::wrapper::Parameters, schemars, tool, tool_router, ServiceExt, transport::stdio};
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
struct AddParams {
a: i32,
b: i32,
}
#[derive(Clone)]
struct Calculator;
#[tool_router(server_handler)]
impl Calculator {
#[tool(description = "Add two numbers")]
fn add(&self, Parameters(AddParams { a, b }): Parameters<AddParams>) -> String {
(a + b).to_string()
}
}
#[tokio::main]
async fn main() -> 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.
2026-07-28(SEP-2106):outputSchemamay now be any JSON Schema type (not justobject), and a tool result'sstructuredContentmay be any JSON value (string, array, number, …) rather than only an object. Existing object-typed tools are unaffected.
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)]
struct AddParams {
a: i32,
b: i32,
}
#[derive(Clone)]
struct Calculator;
#[tool_router]
impl Calculator {
#[tool(description = "Add two numbers")]
fn add(&self, Parameters(AddParams { a, b }): Parameters<AddParams>) -> String {
(a + b).to_string()
}
}
#[tool_handler(name = "calculator", version = "1.0.0", instructions = "A simple calculator")]
impl ServerHandler for Calculator {}
See crates/rmcp-macros for full macro documentation.
Beyond a plain String, tools can return images, audio, embedded resources, and
mixed content. Build a CallToolResult from a Vec<ContentBlock>:
use rmcp::model::{CallToolResult, ContentBlock, ResourceContents};
#[tool(description = "Render a chart")]
async fn chart(&self) -> Result<CallToolResult, McpError> {
let png_base64 = render_png(); // base64-encoded image bytes
let wav_base64 = render_wav(); // base64-encoded audio bytes
Ok(CallToolResult::success(vec![
// Text
ContentBlock::text("Here is your chart:"),
// Image — base64 data + MIME type
ContentBlock::image(png_base64, "image/png"),
// Audio — base64 data + MIME type
ContentBlock::audio(wav_base64, "audio/wav"),
// Embedded resource — inline text (or ResourceContents::blob for binary)
ContentBlock::resource(ResourceContents::text(
"chart source data",
"chart://last/data.csv",
)),
]))
}
# fn render_png() -> String { String::new() }
# fn render_wav() -> String { String::new() }
Image and audio data are base64 strings with a MIME type. For embedded
resources, ResourceContents::text(..) inlines text and
ResourceContents::blob(base64, uri) inlines binary.
Two failure modes, chosen by whose problem it is:
Ok(CallToolResult::error(vec![...])). The tool ran but
failed in a way the caller should see (no rows matched, upstream 500). The
client renders your content, so the message reaches the user. Use this for
almost every "the tool ran and didn't work" case.Err(McpError) with a JSON-RPC