📦modelcontextprotocol / rust-sdk
← 返回首页
The official Rust SDK for the Model Context Protocol
⚖️ NOASSERTION#rust#mcp#mcp-server#mcp-client🌐 docs.rs📅 创建于 2025-02-18🔄 更新于 2026-10-28

📄 仓库 README

在 GitHub 查看 ↗
来自 GitHub 仓库自述文件 · 版权归原作者所有

RMCP

Crates.io Version docs.rs CI License

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:

  • 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

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.

Table of Contents

Usage

Import the crate

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

Third Dependencies

Basic dependencies: - tokio - serde Json Schema generation (version 2020-12): - schemars

Build a Client

Start a client ```rust, ignore use rmcp::{ServiceExt, transport::{TokioChildProcess, ConfigureCommandExt}}; use tokio::process::Command; #[tokio::main] async fn main() -> Result<(), Box> { let client = ().serve(TokioChildProcess::new(Command::new("npx").configure(|cmd| { cmd.arg("-y").arg("@modelcontextprotocol/server-everything"); }))?).await?; Ok(()) }
</details>

### Client lifecycle modes

`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`](https://github.com/modelcontextprotocol/rust-sdk/blob/HEAD/crates/rmcp/src/service/client.rs) to
select another lifecycle explicitly:

```rust, ignore
use rmcp::{ClientLifecycleMode, ClientServiceExt, ProtocolVersion, model::ClientConfig};

// Start directly with server/discover and include client metadata on every request.
let client = ClientConfig::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 or does not respond within 10 seconds.
let client = ClientConfig::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`. ### Build a Server
Build a transport ```rust, ignore use tokio::io::{stdin, stdout}; let transport = (stdin(), stdout());

</details>

<details>
<summary>Build a service</summary>

You can easily build a service by using [`ServerHandler`](https://github.com/modelcontextprotocol/rust-sdk/blob/HEAD/crates/rmcp/src/handler/server.rs) or [`ClientHandler`](https://github.com/modelcontextprotocol/rust-sdk/blob/HEAD/crates/rmcp/src/handler/client.rs).

```rust, ignore
let service = common::counter::Counter::new();
Start the server ```rust, ignore // this call will finish the initialization process let server = service.serve(transport).await?;
</details>

<details>
<summary>Interact with the server</summary>

Once the server is initialized, you can send requests or notifications:

```rust, ignore
// request
let roots = server.list_roots().await?;

// or send notification
server.notify_cancelled(...).await?;
Waiting for service shutdown ```rust, ignore let quit_reason = server.waiting().await?; // or cancel it let quit_reason = server.cancel().await?;
</details>

---

## 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](https://modelcontextprotocol.io/specification/2026-07-28/server/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:

```rust,ignore
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):** `outputSchema` may now be any JSON Schema type > (not just `object`), and a tool result's `structuredContent` may 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]`: ```rust,ignore 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) -> 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`](https://github.com/modelcontextprotocol/rust-sdk/blob/HEAD/crates/rmcp-macros/README.md) for full macro documentation.

#### Tool result content types

Beyond a plain `String`, tools can return images, audio, embedded resources, and
mixed content. Build a `CallToolResult` from a `Vec<ContentBlock>`:

```rust,ignore
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. #### Error handling Two failure modes, chosen by **whose problem it is**: - **Tool-level error** — `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. - **Protocol error** — `Err(McpError)` with a JSON-RPC code (e.g. `McpError::invalid_params(..)`). Use this when the server can't route or process the request at all; clients render these opaquely, so the caller does **not** see your message. ```rust,ignore use rmcp::model::{CallToolResult, ContentBlock}; use rmcp::ErrorData as McpError; #[tool(description = "Look up a record")] async fn lookup(&self, Parameters(args): Parameters)

📈 近 7 天 Star 趋势

每日新增 Star(柱)与累计 Star(线)· 数据来自 GitHub

🏆 排名变化趋势

在各榜单中的排名历史(数字越小越靠前)

📋 历史上榜记录

每次快照的详细数据
日期 榜单 排名 周期新增 总 STAR