From 133cd1794879b641bda47f1f72ec3dad0c5063eb Mon Sep 17 00:00:00 2001 From: Alex Hancock Date: Thu, 23 Jul 2026 11:29:26 -0400 Subject: [PATCH] docs: update for 2026-07-28 version --- README.md | 227 +++++++- crates/rmcp/README.md | 2 +- docs/OAUTH_SUPPORT.md | 4 +- docs/readme/README.zh-cn.md | 1026 ----------------------------------- 4 files changed, 211 insertions(+), 1048 deletions(-) delete mode 100644 docs/readme/README.zh-cn.md diff --git a/README.md b/README.md index 57f69c5bb..0863fc634 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,3 @@ -
-简体中文 -
- # RMCP [![Crates.io Version](https://img.shields.io/crates/v/rmcp)](https://crates.io/crates/rmcp) [![docs.rs](https://img.shields.io/docsrs/rmcp)](https://docs.rs/rmcp/latest/rmcp) @@ -17,7 +13,13 @@ This repository contains the following crates: - [rmcp](crates/rmcp): The core crate providing the RMCP protocol implementation - see [rmcp](crates/rmcp/README.md) - [rmcp-macros](crates/rmcp-macros): A procedural macro crate for generating RMCP tool implementations - see [rmcp-macros](crates/rmcp-macros/README.md) -For the full MCP specification, see [modelcontextprotocol.io](https://modelcontextprotocol.io/specification/2025-11-25). +This SDK tracks the MCP **`2026-07-28`** draft (the current development spec) +while remaining fully compatible with the stable **`2025-11-25`** release and +earlier versions. New `2026-07-28` features — server discovery & negotiation, +transport-neutral subscriptions, long-running tasks, response caching, +multi-round-trip requests, and standard HTTP routing headers — are documented +below alongside the stable feature set. For the full MCP specification, see +[modelcontextprotocol.io](https://modelcontextprotocol.io/specification/draft). ## Table of Contents @@ -31,8 +33,11 @@ For the full MCP specification, see [modelcontextprotocol.io](https://modelconte - [Completions](#completions) - [Notifications](#notifications) - [Subscriptions](#subscriptions) +- [Multi-Round-Trip Requests](#multi-round-trip-requests) - [Tasks](#tasks-long-running-tool-invocations) - [Caching](#caching) +- [Standard HTTP Headers](#standard-http-headers) +- [Stateless Streamable HTTP](#stateless-streamable-http) - [Examples](#examples) - [OAuth Support](#oauth-support) - [Related Resources](#related-resources) @@ -43,10 +48,16 @@ For the full MCP specification, see [modelcontextprotocol.io](https://modelconte ### Import the crate -```toml -rmcp = { version = "0.16.0", features = ["server"] } -## or dev channel -rmcp = { git = "https://github.com/modelcontextprotocol/rust-sdk", branch = "main" } +Add the latest published version with cargo: + +```sh +cargo add rmcp --features server +``` + +Or use the dev channel: + +```sh +cargo add rmcp --features server --git https://github.com/modelcontextprotocol/rust-sdk --branch main ``` ### Third Dependencies @@ -174,7 +185,7 @@ 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](https://modelcontextprotocol.io/specification/2025-11-25/server/tools) +**MCP Spec:** [Tools](https://modelcontextprotocol.io/specification/draft/server/tools) ### Server-side @@ -210,6 +221,11 @@ async fn main() -> anyhow::Result<()> { 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 @@ -258,7 +274,7 @@ let result = client.call_tool(CallToolRequestParams::new("add")).await?; 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](https://modelcontextprotocol.io/specification/2025-11-25/server/resources) +**MCP Spec:** [Resources](https://modelcontextprotocol.io/specification/draft/server/resources) ### Server-side @@ -393,7 +409,7 @@ impl ClientHandler for MyClient { 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](https://modelcontextprotocol.io/specification/2025-11-25/server/prompts) +**MCP Spec:** [Prompts](https://modelcontextprotocol.io/specification/draft/server/prompts) ### Server-side @@ -507,7 +523,7 @@ context.peer.notify_prompt_list_changed().await?; 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](https://modelcontextprotocol.io/specification/2025-11-25/client/sampling) +**MCP Spec:** [Sampling](https://modelcontextprotocol.io/specification/draft/client/sampling) ### Server-side (requesting sampling) @@ -579,7 +595,7 @@ impl ClientHandler for MyClient { 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](https://modelcontextprotocol.io/specification/2025-11-25/client/roots) +**MCP Spec:** [Roots](https://modelcontextprotocol.io/specification/draft/client/roots) ### Server-side @@ -644,7 +660,7 @@ client.notify_roots_list_changed().await?; 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](https://modelcontextprotocol.io/specification/2025-11-25/server/utilities/logging) +**MCP Spec:** [Logging](https://modelcontextprotocol.io/specification/draft/server/utilities/logging) ### Server-side @@ -717,7 +733,7 @@ client.set_level(SetLevelRequestParams::new(LoggingLevel::Warning)).await?; Completions give auto-completion suggestions for prompt or resource template arguments. As a user fills in arguments, the client can ask the server for suggestions based on what's already been entered. -**MCP Spec:** [Completions](https://modelcontextprotocol.io/specification/2025-11-25/server/utilities/completion) +**MCP Spec:** [Completions](https://modelcontextprotocol.io/specification/draft/server/utilities/completion) ### Server-side @@ -799,7 +815,7 @@ let result = client.complete(CompleteRequestParams::new( 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](https://modelcontextprotocol.io/specification/2025-11-25/basic/notifications) +**MCP Spec:** [Notifications](https://modelcontextprotocol.io/specification/draft/basic#notifications) ### Progress notifications @@ -970,6 +986,81 @@ and [client](examples/clients/src/subscriptions_streamhttp.rs) examples. --- +## Multi-Round-Trip Requests + +Protocol `2026-07-28` adds Multi-Round-Trip Requests (MRTR, SEP-2322): a server +can answer a `tools/call`, `prompts/get`, or `resources/read` with an +`InputRequiredResult` instead of a final result, asking the client to fulfill +one or more embedded server requests (elicitation, sampling, or roots) and then +retry. The exchange is stateless — the server carries its progress in an opaque +`requestState` that the client echoes back verbatim. + +**MCP Spec:** [Multiple Round-Trip Requests](https://modelcontextprotocol.io/specification/draft/server/tools#multiple-round-trip-requests) + +### Server-side + +Return an `InputRequiredResult` via the outcome enum for the method +(`CallToolResponse`, `GetPromptResponse`, or `ReadResourceResponse`). The SDK +only forwards it to peers that negotiated `2026-07-28` or newer — older peers +get a protocol error instead. + +```rust, ignore +async fn call_tool(&self, request: CallToolRequestParams, _ctx: RequestContext) + -> Result +{ + match request.request_state { + // First round: ask the client for input, seal progress into requestState. + None => { + let mut input_requests = InputRequests::new(); + input_requests.insert("city".into(), InputRequest::Elicitation(elicit_city())); + let sealed = self.codec.seal_json(&json!({ "awaiting": "city" }))?; + Ok(InputRequiredResult::new(Some(input_requests), Some(sealed)).into()) + } + // Retry round: verify the echoed state, read the responses, finish. + Some(sealed) => { + let _state = self.codec.open_json(&sealed) + .map_err(|_| ErrorData::invalid_params("tampered request state", None))?; + let city = request.input_responses.as_ref() + .and_then(|r| r.get("city")); + Ok(CallToolResult::success(vec![ContentBlock::text("It is sunny.")]).into()) + } + } +} +``` + +> **`requestState` is untrusted.** The client echoes it back verbatim, so a +> stateless server that stores meaningful data in it MUST verify integrity +> first. Enable the `request-state` feature and use `RequestStateCodec` to seal +> and open it (HMAC-tagged), or keep state server-side and use `requestState` +> only as an opaque handle. + +### Client-side + +The high-level `call_tool`, `get_prompt`, and `read_resource` helpers drive MRTR +automatically: they fulfill each embedded request through the local +`ClientHandler` and retry, up to `DEFAULT_MRTR_MAX_ROUNDS` (10). + +```rust, ignore +// Auto mode: the SDK fulfills embedded requests and retries for you. +let result = client.call_tool(CallToolRequestParams::new("weather")).await?; + +// Choose a custom round cap. +let result = client + .call_tool_with_mrtr_max_rounds(CallToolRequestParams::new("weather"), 3) + .await?; + +// Manual mode: get the intermediate InputRequiredResult and drive rounds yourself. +match client.call_tool_once(CallToolRequestParams::new("weather")).await? { + CallToolResponse::InputRequired(input_required) => { /* fulfill + retry */ } + CallToolResponse::Complete(result) => { /* done */ } + _ => {} +} +``` + +**Example:** [`examples/servers/src/mrtr.rs`](examples/servers/src/mrtr.rs) (end-to-end server + client) + +--- + ## Tasks (long-running tool invocations) `rmcp` implements the [MCP Tasks extension](https://modelcontextprotocol.io/extensions/tasks/overview) @@ -1050,6 +1141,104 @@ peer.clear_response_cache().await; > last cached response (even if expired) as `Ok(..)` instead of an error. Set > `with_serve_stale_on_error(false)` if callers must observe fetch failures. +## Standard HTTP Headers + +Protocol `2026-07-28` standardizes a set of Streamable HTTP request headers +(SEP-2243) so proxies and gateways can route MCP traffic without parsing the +JSON body: `Mcp-Method`, `Mcp-Name`, and `Mcp-Param-*`. `rmcp` emits and +validates these automatically once a connection negotiates `2026-07-28` or +newer — no call-site changes are required, and older negotiated versions are +untouched. + +**MCP Spec:** [Header standardization](https://modelcontextprotocol.io/specification/draft/basic/transports#header) + +- `Mcp-Method` — the JSON-RPC method (e.g. `tools/call`). +- `Mcp-Name` — the target name, sourced from `params.name` (`tools/call`, + `prompts/get`), `params.uri` (`resources/*`), or `params.taskId` (`tasks/*`). +- `Mcp-Param-*` — selected `tools/call` arguments, promoted from the tool's + input schema. + +To promote a tool argument into a routing header, annotate the top-level schema +property with `x-mcp-header`: + +```rust, ignore +// A `region` argument surfaces as the `Mcp-Param-Region` request header. +let schema = serde_json::json!({ + "type": "object", + "properties": { + "region": { "type": "string", "x-mcp-header": "Region" } + } +}); +``` + +Annotations must be non-empty RFC 9110 tokens, case-insensitively unique, and +applied only to top-level primitive (`string`/`integer`/`boolean`) properties. +Values that cannot travel as a bare header (leading/trailing whitespace, +control/non-ASCII characters) are transparently Base64-wrapped as +`=?base64??=`. + +--- + +## Stateless Streamable HTTP + +Per SEP-2567, `rmcp` serves the `2026-07-28` draft statelessly **automatically**: +no `Mcp-Session-Id`, no standalone GET/DELETE stream, and no `Last-Event-ID` +resumption. The `legacy_session_mode` flag below only controls behavior for +*legacy* protocol versions (`< 2026-07-28`). + +**MCP Spec:** [Transports](https://modelcontextprotocol.io/specification/draft/basic/transports) + +### Server-side + +A default server already serves `2026-07-28` clients statelessly. To also serve +*legacy* clients without sessions, disable `legacy_session_mode` (formerly +`stateful_mode`; builder `with_stateful_mode`). Optionally set +`with_json_response(true)` so simple request/response tools reply with a single +`application/json` body instead of an SSE stream (the server still falls back to +`text/event-stream` if a handler emits a notification or server request first): + +```rust, ignore +use rmcp::transport::streamable_http_server::{ + StreamableHttpService, StreamableHttpServerConfig, + session::local::LocalSessionManager, +}; + +let config = StreamableHttpServerConfig::default() + .with_legacy_session_mode(false) // stateless for legacy versions too + .with_json_response(true); // plain JSON replies for simple tools + +let service = StreamableHttpService::new( + || Ok(Counter::new()), // a fresh handler per request + LocalSessionManager::default().into(), + config, +); + +// `StreamableHttpService` is a Tower service — mount it on any router. +let router = axum::Router::new().nest_service("/mcp", service); +``` + +> Because there is no per-session state, the `service_factory` runs per request. +> Keep shared state (DB pools, caches) in a `Clone` handle captured by the +> closure; don't rely on in-memory state surviving between requests. + +### Client-side + +The Streamable HTTP client transport allows stateless operation by default +(`allow_stateless: true`), so no configuration is needed to talk to a stateless +server — it simply omits the session header when the server doesn't issue one: + +```rust, ignore +use rmcp::transport::StreamableHttpClientTransport; + +// Defaults are stateless-friendly. +let transport = StreamableHttpClientTransport::from_uri("http://localhost:8000/mcp"); +let client = ClientInfo::default().serve(transport).await?; +``` + +**Example:** [`examples/servers/src/counter_streamhttp.rs`](examples/servers/src/counter_streamhttp.rs) (server), [`examples/clients/src/streamable_http.rs`](examples/clients/src/streamable_http.rs) (client) + +--- + ## Examples See [examples](examples/README.md). @@ -1060,8 +1249,8 @@ See [Oauth_support](docs/OAUTH_SUPPORT.md) for details. ## Related Resources -- [MCP Specification](https://modelcontextprotocol.io/specification/2025-11-25) -- [Schema](https://github.com/modelcontextprotocol/specification/blob/main/schema/2025-11-25/schema.ts) +- [MCP Specification](https://modelcontextprotocol.io/specification/draft) +- [Schema](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/schema/draft/schema.ts) ## Related Projects diff --git a/crates/rmcp/README.md b/crates/rmcp/README.md index c133e40e8..742b819e3 100644 --- a/crates/rmcp/README.md +++ b/crates/rmcp/README.md @@ -11,7 +11,7 @@ -The official Rust SDK for the [Model Context Protocol](https://modelcontextprotocol.io/specification/2025-11-25). Build MCP servers that expose tools, resources, and prompts to AI assistants — or build clients that connect to them. +The official Rust SDK for the [Model Context Protocol](https://modelcontextprotocol.io/specification/draft). Build MCP servers that expose tools, resources, and prompts to AI assistants — or build clients that connect to them. For **getting started**, **usage guides**, and **full MCP feature documentation** (resources, prompts, sampling, roots, logging, completions, subscriptions, etc.), see the [main README](../../README.md). diff --git a/docs/OAUTH_SUPPORT.md b/docs/OAUTH_SUPPORT.md index 16809a407..63622ed4b 100644 --- a/docs/OAUTH_SUPPORT.md +++ b/docs/OAUTH_SUPPORT.md @@ -1,6 +1,6 @@ # Model Context Protocol OAuth Authorization -This document describes the OAuth 2.1 authorization implementation for Model Context Protocol (MCP), following the [MCP 2025-11-25 Authorization Specification](https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization/). +This document describes the OAuth 2.1 authorization implementation for Model Context Protocol (MCP), following the [MCP Authorization Specification](https://modelcontextprotocol.io/specification/draft/basic/authorization/). ## Features @@ -231,7 +231,7 @@ If you encounter authorization issues, check the following: ## References -- [MCP Authorization Specification (2025-11-25)](https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization/) +- [MCP Authorization Specification](https://modelcontextprotocol.io/specification/draft/basic/authorization/) - [OAuth 2.1 Specification Draft](https://oauth.net/2.1/) - [RFC 8414: OAuth 2.0 Authorization Server Metadata](https://datatracker.ietf.org/doc/html/rfc8414) - [RFC 7591: OAuth 2.0 Dynamic Client Registration Protocol](https://datatracker.ietf.org/doc/html/rfc7591) diff --git a/docs/readme/README.zh-cn.md b/docs/readme/README.zh-cn.md deleted file mode 100644 index 55518f095..000000000 --- a/docs/readme/README.zh-cn.md +++ /dev/null @@ -1,1026 +0,0 @@ -
-English -
- -# RMCP -[![Crates.io Version](https://img.shields.io/crates/v/rmcp)](https://crates.io/crates/rmcp) -[![docs.rs](https://img.shields.io/docsrs/rmcp)](https://docs.rs/rmcp/latest/rmcp) -[![CI](https://github.com/modelcontextprotocol/rust-sdk/actions/workflows/ci.yml/badge.svg)](https://github.com/modelcontextprotocol/rust-sdk/actions/workflows/ci.yml) -[![License](https://img.shields.io/crates/l/rmcp)](../../LICENSE) - -一个基于 tokio 异步运行时的官方 Rust Model Context Protocol SDK 实现。 - -> **迁移到 1.x?** 请参阅 [迁移指南](https://github.com/modelcontextprotocol/rust-sdk/discussions/716) 了解破坏性变更和升级说明。 - -本仓库包含以下 crate: - -- [rmcp](../../crates/rmcp):实现 RMCP 协议的核心库 - 详见 [rmcp](../../crates/rmcp/README.md) -- [rmcp-macros](../../crates/rmcp-macros):用于生成 RMCP 工具实现的过程宏库 - 详见 [rmcp-macros](../../crates/rmcp-macros/README.md) - -完整的 MCP 规范请参阅 [modelcontextprotocol.io](https://modelcontextprotocol.io/specification/2025-11-25)。 - -## 目录 - -- [使用](#使用) -- [工具](#工具) -- [资源](#资源) -- [提示词](#提示词) -- [采样](#采样) -- [根目录](#根目录) -- [日志](#日志) -- [补全](#补全) -- [通知](#通知) -- [订阅](#订阅) -- [任务](#任务长时间运行的工具调用) -- [示例](#示例) -- [OAuth 支持](#oauth-支持) -- [相关资源](#相关资源) -- [相关项目](#相关项目) -- [开发](#开发) - -## 使用 - -### 导入 - -```toml -rmcp = { version = "0.16.0", features = ["server"] } -## 或使用最新开发版本 -rmcp = { git = "https://github.com/modelcontextprotocol/rust-sdk", branch = "main" } -``` -### 第三方依赖 - -基本依赖: -- [tokio](https://github.com/tokio-rs/tokio) -- [serde](https://github.com/serde-rs/serde) -JSON Schema 生成 (version 2020-12): -- [schemars](https://github.com/GREsau/schemars) - -### 构建客户端 - -
-启动客户端 - -```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(()) -} -``` -
- -### 客户端生命周期模式 - -`serve()` 使用传统 MCP 生命周期:客户端发送 `initialize`,接收协商后的服务端信息, -然后发送 `notifications/initialized`。如需显式选择其他生命周期,请使用 -[`ClientServiceExt::serve_with_lifecycle`](../../crates/rmcp/src/service/client.rs): - -```rust, ignore -use rmcp::{ClientInfo, ClientLifecycleMode, ClientServiceExt, ProtocolVersion}; - -// 直接通过 server/discover 启动,并在每个请求中携带客户端元数据。 -let client = ClientInfo::default() - .serve_with_lifecycle( - transport, - ClientLifecycleMode::Discover { - preferred_versions: vec![ProtocolVersion::V_2026_07_28], - }, - ) - .await?; - -// 或先尝试发现生命周期;当传统服务端报告未实现 server/discover 时回退。 -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` 等同于现有的 `serve()` 行为。发现启动不会发送 -`notifications/initialized`;发现过程即完成启动,后续每个请求都会在 `_meta` -中携带协议版本、客户端信息和客户端能力。 - -### 构建服务端 - -
-构建传输层 - -```rust, ignore -use tokio::io::{stdin, stdout}; -let transport = (stdin(), stdout()); -``` - -
- -
-构建服务 - -你可以通过 [`ServerHandler`](../../crates/rmcp/src/handler/server.rs) 或 [`ClientHandler`](../../crates/rmcp/src/handler/client.rs) 轻松构建服务。 - -```rust, ignore -let service = common::counter::Counter::new(); -``` -
- -
-启动服务端 - -```rust, ignore -// 此调用将完成初始化过程 -let server = service.serve(transport).await?; -``` -
- -
-与服务端交互 - -服务端初始化完成后,你可以发送请求或通知: - -```rust, ignore -// 请求 -let roots = server.list_roots().await?; - -// 或发送通知 -server.notify_cancelled(...).await?; -``` -
- -
-等待服务停止 - -```rust, ignore -let quit_reason = server.waiting().await?; -// 或将其取消 -let quit_reason = server.cancel().await?; -``` -
- ---- - -## 工具 - -工具允许服务端向客户端暴露可调用的函数。每个工具都有名称、描述和参数的 JSON Schema。客户端通过 `list_tools` 发现工具,通过 `call_tool` 调用工具。 - -**MCP 规范:** [Tools](https://modelcontextprotocol.io/specification/2025-11-25/server/tools) - -### 服务端 - -`#[tool]`、`#[tool_router]` 和 `#[tool_handler]` 宏负责所有连接工作。对于纯工具服务端,可以使用 `#[tool_router(server_handler)]` 来省略单独的 `ServerHandler` 实现: - -```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) -> String { - (a + b).to_string() - } -} - -#[tokio::main] -async fn main() -> anyhow::Result<()> { - let service = Calculator.serve(stdio()).await?; - service.waiting().await?; - Ok(()) -} -``` - -当需要自定义服务端元数据或多种能力(工具 + 提示词)时,使用显式的 `#[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 {} -``` - -完整的宏文档请参阅 [`crates/rmcp-macros`](../../crates/rmcp-macros/README.md)。 - -### 客户端 - -```rust,ignore -use rmcp::model::CallToolRequestParams; - -// 列出所有工具 -let tools = client.list_all_tools().await?; - -// 按名称调用工具 -let result = client.call_tool(CallToolRequestParams::new("add")).await?; -``` - -**示例:** [`examples/servers/src/common/calculator.rs`](../../examples/servers/src/common/calculator.rs)(服务端),[`examples/servers/src/calculator_stdio.rs`](../../examples/servers/src/calculator_stdio.rs)(stdio 运行器) - ---- - -## 资源 - -资源允许服务端向客户端暴露数据(文件、数据库记录、API 响应)供其读取。每个资源通过 URI 标识,返回文本或二进制(base64 编码)内容。资源模板允许服务端声明带有动态参数的 URI 模式。 - -**MCP 规范:** [Resources](https://modelcontextprotocol.io/specification/2025-11-25/server/resources) - -### 服务端 - -在 `ServerHandler` trait 上实现 `list_resources()`、`read_resource()`,以及可选的 `list_resource_templates()`。在 `get_info()` 中启用资源能力。 - -```rust -use rmcp::{ - ErrorData as McpError, RoleServer, ServerHandler, ServiceExt, - model::*, - service::RequestContext, - transport::stdio, -}; -use serde_json::json; - -#[derive(Clone)] -struct MyServer; - -impl ServerHandler for MyServer { - fn get_info(&self) -> ServerInfo { - ServerInfo::new( - ServerCapabilities::builder() - .enable_resources() - .build(), - ) - } - - async fn list_resources( - &self, - _request: Option, - _context: RequestContext, - ) -> Result { - Ok(ListResourcesResult { - resources: vec![ - Resource::new("file:///config.json", "config"), - Resource::new("memo://insights", "insights"), - ], - next_cursor: None, - meta: None, - }) - } - - async fn read_resource( - &self, - request: ReadResourceRequestParams, - _context: RequestContext, - ) -> Result { - 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 })), - )), - } - } - - async fn list_resource_templates( - &self, - _request: Option, - _context: RequestContext, - ) -> Result { - Ok(ListResourceTemplatesResult { - resource_templates: vec![], - next_cursor: None, - meta: None, - }) - } -} -``` - -### 客户端 - -```rust -use rmcp::model::{ReadResourceRequestParams}; - -// 列出所有资源(自动处理分页) -let resources = client.list_all_resources().await?; - -// 通过 URI 读取特定资源 -let result = client.read_resource( - ReadResourceRequestParams::new("file:///config.json"), -).await?; - -// 列出资源模板 -let templates = client.list_all_resource_templates().await?; -``` - -### 通知 - -服务端可以在资源列表变更或特定资源更新时通知客户端: - -```rust -// 通知资源列表已变更(客户端应重新获取) -context.peer.notify_resource_list_changed().await?; - -// 通知特定资源已更新 -context.peer.notify_resource_updated( - ResourceUpdatedNotificationParam::new("file:///config.json"), -).await?; -``` - -客户端通过 `ClientHandler` 处理这些通知: - -```rust -impl ClientHandler for MyClient { - async fn on_resource_list_changed( - &self, - _context: NotificationContext, - ) { - // 重新获取资源列表 - } - - async fn on_resource_updated( - &self, - params: ResourceUpdatedNotificationParam, - _context: NotificationContext, - ) { - // 重新读取 params.uri 对应的资源 - } -} -``` - -**示例:** [`examples/servers/src/common/counter.rs`](../../examples/servers/src/common/counter.rs)(服务端),[`examples/clients/src/everything_stdio.rs`](../../examples/clients/src/everything_stdio.rs)(客户端) - ---- - -## 提示词 - -提示词是服务端向客户端暴露的可复用消息模板。它们接受类型化参数并返回对话消息。`#[prompt]` 宏自动处理参数验证和路由。 - -**MCP 规范:** [Prompts](https://modelcontextprotocol.io/specification/2025-11-25/server/prompts) - -### 服务端 - -使用 `#[prompt_router]`、`#[prompt]` 和 `#[prompt_handler]` 宏以声明式方式定义提示词。参数定义为派生 `JsonSchema` 的结构体。 - -```rust -use rmcp::{ - ErrorData as McpError, 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)] -pub struct CodeReviewArgs { - #[schemars(description = "Programming language of the code")] - pub language: String, - #[schemars(description = "Focus areas for the review")] - pub focus_areas: Option>, -} - -#[derive(Clone)] -pub struct MyServer { - prompt_router: PromptRouter, -} - -#[prompt_router] -impl MyServer { - fn new() -> Self { - Self { prompt_router: Self::prompt_router() } - } - - /// 无参数的简单提示词 - #[prompt(name = "greeting", description = "A simple greeting")] - async fn greeting(&self) -> Vec { - vec![PromptMessage::new_text( - Role::User, - "Hello! How can you help me today?", - )] - } - - /// 带类型化参数的提示词 - #[prompt(name = "code_review", description = "Review code in a given language")] - async fn code_review( - &self, - Parameters(args): Parameters, - ) -> Result { - 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] -impl ServerHandler for MyServer { - fn get_info(&self) -> ServerInfo { - ServerInfo::new(ServerCapabilities::builder().enable_prompts().build()) - } -} -``` - -提示词函数支持以下返回类型: -- `Vec` -- 简单消息列表 -- `GetPromptResult` -- 带可选描述的消息 -- `Result` -- 以上任一类型,附带错误处理 - -### 客户端 - -```rust -use rmcp::model::GetPromptRequestParams; - -// 列出所有提示词 -let prompts = client.list_all_prompts().await?; - -// 带参数获取提示词 -let result = client.get_prompt(GetPromptRequestParams { - meta: None, - name: "code_review".into(), - arguments: Some(rmcp::object!({ - "language": "Rust", - "focus_areas": ["performance", "safety"] - })), -}).await?; -``` - -### 通知 - -```rust -// 服务端:通知可用提示词已变更 -context.peer.notify_prompt_list_changed().await?; -``` - -**示例:** [`examples/servers/src/prompt_stdio.rs`](../../examples/servers/src/prompt_stdio.rs)(服务端),[`examples/clients/src/everything_stdio.rs`](../../examples/clients/src/everything_stdio.rs)(客户端) - ---- - -## 采样 - -采样反转了通常的方向:服务端请求客户端执行 LLM 补全。服务端发送 `create_message` 请求,客户端通过其 LLM 处理并返回结果。 - -**MCP 规范:** [Sampling](https://modelcontextprotocol.io/specification/2025-11-25/client/sampling) - -### 服务端(请求采样) - -通过 `context.peer.create_message()` 访问客户端的采样能力: - -```rust -use rmcp::model::*; - -// 在 ServerHandler 方法内部(例如 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?; - -// 提取响应文本 -let text = response.message.content - .first() - .and_then(|c| c.as_text()) - .map(|t| &t.text); -``` - -### 客户端(处理采样) - -在客户端实现 `ClientHandler::create_message()`。这是你调用实际 LLM 的地方: - -```rust -use rmcp::{ClientHandler, model::*, service::{RequestContext, RoleClient}}; - -#[derive(Clone, Default)] -struct MyClient; - -impl ClientHandler for MyClient { - async fn create_message( - &self, - params: CreateMessageRequestParams, - _context: RequestContext, - ) -> Result { - // 转发到你的 LLM,或返回模拟响应: - let response_text = call_your_llm(¶ms.messages).await; - - Ok(CreateMessageResult::new( - SamplingMessage::assistant_text(response_text), - "my-model".into(), - ) - .with_stop_reason(CreateMessageResult::STOP_REASON_END_TURN)) - } -} -``` - -**示例:** [`examples/servers/src/sampling_stdio.rs`](../../examples/servers/src/sampling_stdio.rs)(服务端),[`examples/clients/src/sampling_stdio.rs`](../../examples/clients/src/sampling_stdio.rs)(客户端) - ---- - -## 根目录 - -根目录告诉服务端客户端正在使用哪些目录或项目。根目录是一个 URI(通常为 `file://`),指向工作区或代码仓库。服务端可以查询根目录以了解在哪里查找文件以及如何限定工作范围。 - -**MCP 规范:** [Roots](https://modelcontextprotocol.io/specification/2025-11-25/client/roots) - -### 服务端 - -向客户端请求根目录列表,并处理变更通知: - -```rust -use rmcp::{ServerHandler, model::*, service::{NotificationContext, RoleServer}}; - -impl ServerHandler for MyServer { - // 向客户端查询根目录 - async fn call_tool( - &self, - request: CallToolRequestParams, - context: RequestContext, - ) -> Result { - let roots = context.peer.list_roots().await?; - // 使用 roots.roots 了解工作区边界 - // ... - } - - // 当客户端的根目录列表变更时调用 - async fn on_roots_list_changed( - &self, - _context: NotificationContext, - ) { - // 重新获取根目录以保持最新 - } -} -``` - -### 客户端 - -客户端声明根目录能力并实现 `list_roots()`: - -```rust -use rmcp::{ClientHandler, model::*}; - -impl ClientHandler for MyClient { - async fn list_roots( - &self, - _context: RequestContext, - ) -> Result { - Ok(ListRootsResult::new(vec![ - Root::new("file:///home/user/project").with_name("My Project"), - ])) - } -} -``` - -客户端在根目录变更时通知服务端: - -```rust -// 添加或移除工作区根目录后: -client.notify_roots_list_changed().await?; -``` - ---- - -## 日志 - -服务端可以向客户端发送结构化日志消息。客户端设置最低严重级别,服务端通过对等通知接口发送消息。 - -**MCP 规范:** [Logging](https://modelcontextprotocol.io/specification/2025-11-25/server/utilities/logging) - -### 服务端 - -启用日志能力,处理客户端的级别变更,并通过对等端发送日志消息: - -```rust -use rmcp::{ServerHandler, model::*, service::RequestContext}; - -impl ServerHandler for MyServer { - fn get_info(&self) -> ServerInfo { - ServerInfo::new( - ServerCapabilities::builder() - .enable_logging() - .build(), - ) - } - - // 客户端设置最低日志级别 - async fn set_level( - &self, - request: SetLevelRequestParams, - _context: RequestContext, - ) -> Result<(), ErrorData> { - // 存储 request.level 并据此过滤后续日志消息 - Ok(()) - } -} - -// 在任何可以访问 peer 的处理器中发送日志消息: -context.peer.notify_logging_message( - LoggingMessageNotificationParam::new( - LoggingLevel::Info, - serde_json::json!({ - "message": "Processing completed", - "items_processed": 42 - }), - ) - .with_logger("my-server"), -).await?; -``` - -可用日志级别(从低到高):`Debug`、`Info`、`Notice`、`Warning`、`Error`、`Critical`、`Alert`、`Emergency`。 - -### 客户端 - -客户端通过 `ClientHandler` 处理传入的日志消息: - -```rust -impl ClientHandler for MyClient { - async fn on_logging_message( - &self, - params: LoggingMessageNotificationParam, - _context: NotificationContext, - ) { - println!("[{}] {}: {}", params.level, - params.logger.unwrap_or_default(), params.data); - } -} -``` - -客户端也可以设置服务端的日志级别: - -```rust -client.set_level(SetLevelRequestParams::new(LoggingLevel::Warning)).await?; -``` - ---- - -## 补全 - -补全为提示词或资源模板参数提供自动补全建议。当用户填写参数时,客户端可以根据已输入的内容向服务端请求建议。 - -**MCP 规范:** [Completions](https://modelcontextprotocol.io/specification/2025-11-25/server/utilities/completion) - -### 服务端 - -启用补全能力并实现 `complete()` 处理器。使用 `request.context` 检查已填写的参数: - -```rust -use rmcp::{ErrorData as McpError, ServerHandler, model::*, service::RequestContext, RoleServer}; - -impl ServerHandler for MyServer { - fn get_info(&self) -> ServerInfo { - ServerInfo::new( - ServerCapabilities::builder() - .enable_completions() - .enable_prompts() - .build(), - ) - } - - async fn complete( - &self, - request: CompleteRequestParams, - _context: RequestContext, - ) -> Result { - 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" => { - // 根据已填写的参数调整建议 - if let Some(ctx) = &request.context { - if let Some(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![], - }; - - // 根据用户的部分输入进行过滤 - let filtered: Vec = 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)) - } -} -``` - -### 客户端 - -```rust -use rmcp::model::*; - -let result = client.complete(CompleteRequestParams::new( - Reference::for_prompt("sql_query"), - ArgumentInfo::new("operation", "SEL"), -)).await?; - -// result.completion.values 包含建议,例如 ["SELECT"] -``` - -**示例:** [`examples/servers/src/completion_stdio.rs`](../../examples/servers/src/completion_stdio.rs) - ---- - -## 通知 - -通知是即发即忘的消息——不需要响应。它们涵盖进度更新、取消和生命周期事件。双方都可以发送和接收通知。 - -**MCP 规范:** [Notifications](https://modelcontextprotocol.io/specification/2025-11-25/basic/notifications) - -### 进度通知 - -服务端可以在长时间运行的操作中报告进度: - -```rust -use rmcp::model::*; - -// 在工具处理器内部: -for i in 0..total_items { - process_item(i).await; - - context.peer.notify_progress( - ProgressNotificationParam::new( - ProgressToken(NumberOrString::Number(i as i64)), - i as f64, - ) - .with_total(total_items as f64) - .with_message(format!("Processing item {}/{}", i + 1, total_items)), - ).await?; -} -``` - -### 取消 - -任一方都可以取消正在进行的请求: - -```rust -// 发送取消通知 -context.peer.notify_cancelled(CancelledNotificationParam::new( - Some(the_request_id), - Some("User requested cancellation".into()), -)).await?; -``` - -在 `ServerHandler` 或 `ClientHandler` 中处理取消: - -```rust -impl ServerHandler for MyServer { - async fn on_cancelled( - &self, - params: CancelledNotificationParam, - _context: NotificationContext, - ) { - // 中止 params.request_id 对应的工作 - } -} -``` - -### 初始化通知 - -传统客户端在 `initialize` 握手完成后发送 `initialized` 通知。 -使用 `ClientLifecycleMode::Discover` 的客户端不会发送此通知: - -```rust -// 在传统 serve() 握手过程中由 rmcp 自动发送。 -// 服务端通过以下方式处理: -impl ServerHandler for MyServer { - async fn on_initialized( - &self, - _context: NotificationContext, - ) { - // 服务端已准备好接收请求 - } -} -``` - -### 列表变更通知 - -当可用的工具、提示词或资源发生变更时,通知客户端: - -```rust -context.peer.notify_tool_list_changed().await?; -context.peer.notify_prompt_list_changed().await?; -context.peer.notify_resource_list_changed().await?; -``` - -**示例:** [`examples/servers/src/common/progress_demo.rs`](../../examples/servers/src/common/progress_demo.rs) - ---- - -## 订阅 - -客户端可以订阅特定资源。当订阅的资源发生变更时,服务端发送通知,客户端可以重新读取该资源。 - -**MCP 规范:** [Resources - Subscriptions](https://modelcontextprotocol.io/specification/2025-11-25/server/resources#subscriptions) - -### 服务端 - -在资源能力中启用订阅,并实现 `subscribe()` / `unsubscribe()` 处理器: - -```rust -use rmcp::{ErrorData as McpError, ServerHandler, model::*, service::RequestContext, RoleServer}; -use std::sync::Arc; -use tokio::sync::Mutex; -use std::collections::HashSet; - -#[derive(Clone)] -struct MyServer { - subscriptions: Arc>>, -} - -impl ServerHandler for MyServer { - fn get_info(&self) -> ServerInfo { - ServerInfo::new( - ServerCapabilities::builder() - .enable_resources() - .enable_resources_subscribe() - .build(), - ) - } - - async fn subscribe( - &self, - request: SubscribeRequestParams, - _context: RequestContext, - ) -> Result<(), McpError> { - self.subscriptions.lock().await.insert(request.uri); - Ok(()) - } - - async fn unsubscribe( - &self, - request: UnsubscribeRequestParams, - _context: RequestContext, - ) -> Result<(), McpError> { - self.subscriptions.lock().await.remove(&request.uri); - Ok(()) - } -} -``` - -当订阅的资源发生变更时,通知客户端: - -```rust -// 检查资源是否有订阅者,然后通知 -context.peer.notify_resource_updated( - ResourceUpdatedNotificationParam::new("file:///config.json"), -).await?; -``` - -### 客户端 - -```rust -use rmcp::model::*; - -// 订阅资源更新 -client.subscribe(SubscribeRequestParams::new("file:///config.json")).await?; - -// 不再需要时取消订阅 -client.unsubscribe(UnsubscribeRequestParams::new("file:///config.json")).await?; -``` - -在 `ClientHandler` 中处理更新通知: - -```rust -impl ClientHandler for MyClient { - async fn on_resource_updated( - &self, - params: ResourceUpdatedNotificationParam, - _context: NotificationContext, - ) { - // 重新读取 params.uri 对应的资源 - } -} -``` - ---- - -## 任务(长时间运行的工具调用) - -`rmcp` 支持 SEP-1319 中定义的[基于任务的工具调用](https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/tasks)流程。为工具添加 `execution(task_support = "required" | "optional")` 注解,并在 `ServerHandler` 实现上添加 `#[task_handler]` —— `enqueue_task`、`tasks/list`、`tasks/get`、`tasks/result` 和 `tasks/cancel` 将在 `OperationProcessor` 之上自动生成。 - -```rust, ignore -#[tool( - description = "Sum two numbers after a 2-second delay", - execution(task_support = "required") -)] -async fn slow_sum(/* ... */) -> Result { /* ... */ } - -#[tool_handler] -#[task_handler] -impl ServerHandler for TaskDemo {} -``` - -完整的端到端示例请参阅 [`servers_task_stdio`](../../examples/servers/src/task_stdio.rs) 及对应的 [`clients_task_stdio`](../../examples/clients/src/task_stdio.rs)。 - ---- - -## 示例 - -查看 [examples](../../examples/README.md)。 - -## OAuth 支持 - -查看 [OAuth 支持](../OAUTH_SUPPORT.md) 了解详情。 - -## 相关资源 - -- [MCP 规范](https://modelcontextprotocol.io/specification/2025-11-25) -- [Schema](https://github.com/modelcontextprotocol/specification/blob/main/schema/2025-11-25/schema.ts) - -## 相关项目 - -### 扩展 `rmcp` - -- [rmcp-actix-web](https://gitlab.com/lx-industries/rmcp-actix-web) - 基于 `actix_web` 的 `rmcp` 后端 -- [rmcp-openapi](https://gitlab.com/lx-industries/rmcp-openapi) - 将 OpenAPI 定义的端点转换为 MCP 工具 - -### 基于 `rmcp` 构建 - -- [goose](https://github.com/block/goose) - 一个超越代码建议的开源、可扩展 AI 智能体 -- [apollo-mcp-server](https://github.com/apollographql/apollo-mcp-server) - 通过 Apollo GraphOS 将 AI 智能体连接到 GraphQL API 的 MCP 服务 -- [rustfs-mcp](https://github.com/rustfs/rustfs/tree/main/crates/mcp) - 为 AI/LLM 集成提供 S3 兼容对象存储操作的高性能 MCP 服务 -- [containerd-mcp-server](https://github.com/jokemanfire/mcp-containerd) - 基于 containerd 实现的 MCP 服务 -- [rmcp-openapi-server](https://gitlab.com/lx-industries/rmcp-openapi/-/tree/main/crates/rmcp-openapi-server) - 将 OpenAPI 定义的端点暴露为 MCP 工具的高性能 MCP 服务 -- [nvim-mcp](https://github.com/linw1995/nvim-mcp) - 与 Neovim 交互的 MCP 服务 -- [terminator](https://github.com/mediar-ai/terminator) - AI 驱动的桌面自动化 MCP 服务,支持跨平台,成功率超过 95% -- [stakpak-agent](https://github.com/stakpak/agent) - 安全加固的 DevOps 终端智能体,支持 MCP over mTLS、流式传输、密钥令牌化和异步任务管理 -- [video-transcriber-mcp-rs](https://github.com/nhatvu148/video-transcriber-mcp-rs) - 使用 whisper.cpp 从 1000+ 平台转录视频的高性能 MCP 服务 -- [NexusCore MCP](https://github.com/sjkim1127/Nexuscore_MCP) - 具有 Frida 集成和隐蔽脱壳功能的高级恶意软件分析与动态检测 MCP 服务 -- [spreadsheet-mcp](https://github.com/PSU3D0/spreadsheet-mcp) - 面向 LLM 智能体的高效 Token 使用的电子表格分析 MCP 服务,支持自动区域检测、重新计算、截图和编辑 -- [hyper-mcp](https://github.com/hyper-mcp-rs/hyper-mcp) - 通过 WebAssembly (WASM) 插件扩展功能的快速、安全的 MCP 服务 -- [rudof-mcp](https://github.com/rudof-project/rudof/tree/master/rudof_mcp) - RDF 验证和数据处理 MCP 服务,支持 ShEx/SHACL 验证、SPARQL 查询和格式转换。支持 stdio 和 Streamable HTTP 传输,具备完整的 MCP 功能(工具、提示词、资源、日志、补全、任务) -- [MCPMate](https://github.com/loocor/MCPMate) - 渐进式 MCP 管理桌面应用:从引导式服务导入开始,逐步扩展到多客户端配置集和 Unify 元工具,让能力暴露、Token 消耗与运行状态更可控,并在效率、成本和可靠性上提供更多选择 - - -## 开发 - -### 贡献指南 - -查看 [docs/CONTRIBUTE.MD](../CONTRIBUTE.MD) 获取贡献提示。 - -### 使用 Dev Container - -如果你想使用 Dev Container,查看 [docs/DEVCONTAINER.md](../DEVCONTAINER.md) 获取开发指南。