feat: docs update (#718)

This commit is contained in:
Alex Hancock 2026-03-03 11:14:30 -05:00 committed by GitHub
parent f63718d202
commit 6842f9cc3c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 1571 additions and 1048 deletions

768
README.md
View file

@ -10,11 +10,32 @@
An official Rust Model Context Protocol SDK implementation with tokio async runtime.
> **Migrating to 1.x?** See the [migration guide](https://github.com/modelcontextprotocol/rust-sdk/discussions/716) for breaking changes and upgrade instructions.
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).
## Table of Contents
- [Usage](#usage)
- [Resources](#resources)
- [Prompts](#prompts)
- [Sampling](#sampling)
- [Roots](#roots)
- [Logging](#logging)
- [Completions](#completions)
- [Notifications](#notifications)
- [Subscriptions](#subscriptions)
- [Examples](#examples)
- [OAuth Support](#oauth-support)
- [Related Resources](#related-resources)
- [Related Projects](#related-projects)
- [Development](#development)
## Usage
### Import the crate
@ -106,15 +127,754 @@ let quit_reason = server.cancel().await?;
```
</details>
---
## 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](https://modelcontextprotocol.io/specification/2025-11-25/server/resources)
### Server-side
Implement `list_resources()`, `read_resource()`, and optionally `list_resource_templates()` on the `ServerHandler` trait. Enable the resources capability in `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 {
capabilities: ServerCapabilities::builder()
.enable_resources()
.build(),
..Default::default()
}
}
async fn list_resources(
&self,
_request: Option<PaginatedRequestParams>,
_context: RequestContext<RoleServer>,
) -> Result<ListResourcesResult, McpError> {
Ok(ListResourcesResult {
resources: vec![
RawResource::new("file:///config.json", "config").no_annotation(),
RawResource::new("memo://insights", "insights").no_annotation(),
],
next_cursor: None,
meta: None,
})
}
async fn read_resource(
&self,
request: ReadResourceRequestParams,
_context: RequestContext<RoleServer>,
) -> Result<ReadResourceResult, McpError> {
match request.uri.as_str() {
"file:///config.json" => Ok(ReadResourceResult {
contents: vec![ResourceContents::text(r#"{"key": "value"}"#, &request.uri)],
}),
"memo://insights" => Ok(ReadResourceResult {
contents: 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<PaginatedRequestParams>,
_context: RequestContext<RoleServer>,
) -> Result<ListResourceTemplatesResult, McpError> {
Ok(ListResourceTemplatesResult {
resource_templates: vec![],
next_cursor: None,
meta: None,
})
}
}
```
### Client-side
```rust
use rmcp::model::{ReadResourceRequestParams};
// List all resources (handles pagination automatically)
let resources = client.list_all_resources().await?;
// Read a specific resource by URI
let result = client.read_resource(ReadResourceRequestParams {
meta: None,
uri: "file:///config.json".into(),
}).await?;
// List resource templates
let templates = client.list_all_resource_templates().await?;
```
### Notifications
Servers can notify clients when the resource list changes or when a specific resource is updated:
```rust
// 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 {
uri: "file:///config.json".into(),
}).await?;
```
Clients handle these via `ClientHandler`:
```rust
impl ClientHandler for MyClient {
async fn on_resource_list_changed(
&self,
_context: NotificationContext<RoleClient>,
) {
// Re-fetch the resource list
}
async fn on_resource_updated(
&self,
params: ResourceUpdatedNotificationParam,
_context: NotificationContext<RoleClient>,
) {
// Re-read the updated resource at params.uri
}
}
```
**Example:** [`examples/servers/src/common/counter.rs`](examples/servers/src/common/counter.rs) (server), [`examples/clients/src/everything_stdio.rs`](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](https://modelcontextprotocol.io/specification/2025-11-25/server/prompts)
### Server-side
Use the `#[prompt_router]`, `#[prompt]`, and `#[prompt_handler]` macros to define prompts declaratively. Arguments are defined as structs deriving `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<Vec<String>>,
}
#[derive(Clone)]
pub struct MyServer {
prompt_router: PromptRouter<Self>,
}
#[prompt_router]
impl MyServer {
fn new() -> Self {
Self { prompt_router: Self::prompt_router() }
}
/// Simple prompt without parameters
#[prompt(name = "greeting", description = "A simple greeting")]
async fn greeting(&self) -> Vec<PromptMessage> {
vec![PromptMessage::new_text(
PromptMessageRole::User,
"Hello! How can you help me today?",
)]
}
/// Prompt with typed arguments
#[prompt(name = "code_review", description = "Review code in a given language")]
async fn code_review(
&self,
Parameters(args): Parameters<CodeReviewArgs>,
) -> Result<GetPromptResult, McpError> {
let focus = args.focus_areas
.unwrap_or_else(|| vec!["correctness".into()]);
Ok(GetPromptResult {
description: Some(format!("Code review for {}", args.language)),
messages: vec![
PromptMessage::new_text(
PromptMessageRole::User,
format!("Review my {} code. Focus on: {}", args.language, focus.join(", ")),
),
],
})
}
}
#[prompt_handler]
impl ServerHandler for MyServer {
fn get_info(&self) -> ServerInfo {
ServerInfo {
capabilities: ServerCapabilities::builder().enable_prompts().build(),
..Default::default()
}
}
}
```
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
```rust
use rmcp::model::GetPromptRequestParams;
// List all prompts
let prompts = client.list_all_prompts().await?;
// Get a prompt with arguments
let result = client.get_prompt(GetPromptRequestParams {
meta: None,
name: "code_review".into(),
arguments: Some(rmcp::object!({
"language": "Rust",
"focus_areas": ["performance", "safety"]
})),
}).await?;
```
### Notifications
```rust
// Server: notify that available prompts have changed
context.peer.notify_prompt_list_changed().await?;
```
**Example:** [`examples/servers/src/prompt_stdio.rs`](examples/servers/src/prompt_stdio.rs) (server), [`examples/clients/src/everything_stdio.rs`](examples/clients/src/everything_stdio.rs) (client)
---
## Sampling
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)
### Server-side (requesting sampling)
Access the client's sampling capability through `context.peer.create_message()`:
```rust
use rmcp::model::*;
// Inside a ServerHandler method (e.g., call_tool):
let response = context.peer.create_message(CreateMessageRequestParams {
meta: None,
task: None,
messages: vec![SamplingMessage::user_text("Explain this error: connection refused")],
model_preferences: Some(ModelPreferences {
hints: Some(vec![ModelHint { name: Some("claude".into()) }]),
cost_priority: Some(0.3),
speed_priority: Some(0.8),
intelligence_priority: Some(0.7),
}),
system_prompt: Some("You are a helpful assistant.".into()),
include_context: Some(ContextInclusion::None),
temperature: Some(0.7),
max_tokens: 150,
stop_sequences: None,
metadata: None,
tools: None,
tool_choice: None,
}).await?;
// Extract the response text
let 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:
```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<RoleClient>,
) -> Result<CreateMessageResult, ErrorData> {
// Forward to your LLM, or return a mock response:
let response_text = call_your_llm(&params.messages).await;
Ok(CreateMessageResult {
message: SamplingMessage::assistant_text(response_text),
model: "my-model".into(),
stop_reason: Some(CreateMessageResult::STOP_REASON_END_TURN.into()),
})
}
}
```
**Example:** [`examples/servers/src/sampling_stdio.rs`](examples/servers/src/sampling_stdio.rs) (server), [`examples/clients/src/sampling_stdio.rs`](examples/clients/src/sampling_stdio.rs) (client)
---
## Roots
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)
### Server-side
Ask the client for its root list, and handle change notifications:
```rust
use rmcp::{ServerHandler, model::*, service::{NotificationContext, RoleServer}};
impl ServerHandler for MyServer {
// Query the client for its roots
async fn call_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 changes
async fn on_roots_list_changed(
&self,
_context: NotificationContext<RoleServer>,
) {
// Re-fetch roots to stay current
}
}
```
### Client-side
Clients declare roots capability and implement `list_roots()`:
```rust
use rmcp::{ClientHandler, model::*};
impl ClientHandler for MyClient {
async fn list_roots(
&self,
_context: RequestContext<RoleClient>,
) -> Result<ListRootsResult, ErrorData> {
Ok(ListRootsResult {
roots: vec![
Root {
uri: "file:///home/user/project".into(),
name: Some("My Project".into()),
},
],
})
}
}
```
Clients notify the server when roots change:
```rust
// After adding or removing a workspace root:
client.notify_roots_list_changed().await?;
```
---
## Logging
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)
### Server-side
Enable the logging capability, handle level changes from the client, and send log messages via the peer:
```rust
use rmcp::{ServerHandler, model::*, service::RequestContext};
impl ServerHandler for MyServer {
fn get_info(&self) -> ServerInfo {
ServerInfo {
capabilities: ServerCapabilities::builder()
.enable_logging()
.build(),
..Default::default()
}
}
// Client sets the minimum log level
async fn set_level(
&self,
request: SetLevelRequestParams,
_context: RequestContext<RoleServer>,
) -> Result<(), ErrorData> {
// Store request.level and filter future log messages accordingly
Ok(())
}
}
// Send a log message from any handler with access to the peer:
context.peer.notify_logging_message(LoggingMessageNotificationParam {
level: LoggingLevel::Info,
logger: Some("my-server".into()),
data: serde_json::json!({
"message": "Processing completed",
"items_processed": 42
}),
}).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`:
```rust
impl ClientHandler for MyClient {
async fn on_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:
```rust
client.set_level(SetLevelRequestParams {
level: LoggingLevel::Warning,
meta: None,
}).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](https://modelcontextprotocol.io/specification/2025-11-25/server/utilities/completion)
### Server-side
Enable the completions capability and implement the `complete()` handler. Use `request.context` to inspect previously filled arguments:
```rust
use rmcp::{ErrorData as McpError, ServerHandler, model::*, service::RequestContext, RoleServer};
impl ServerHandler for MyServer {
fn get_info(&self) -> ServerInfo {
ServerInfo {
capabilities: ServerCapabilities::builder()
.enable_completions()
.enable_prompts()
.build(),
..Default::default()
}
}
async fn complete(
&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 arguments
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![],
};
// Filter by the user's partial input
let filtered: Vec<String> = values.into_iter()
.map(String::from)
.filter(|v| v.to_lowercase().contains(&request.argument.value.to_lowercase()))
.collect();
Ok(CompleteResult {
completion: CompletionInfo {
values: filtered,
total: None,
has_more: Some(false),
},
})
}
}
```
### Client-side
```rust
use rmcp::model::*;
let result = client.complete(CompleteRequestParams {
meta: None,
r#ref: Reference::Prompt(PromptReference {
name: "sql_query".into(),
}),
argument: ArgumentInfo {
name: "operation".into(),
value: "SEL".into(),
},
context: None,
}).await?;
// result.completion.values contains suggestions like ["SELECT"]
```
**Example:** [`examples/servers/src/completion_stdio.rs`](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](https://modelcontextprotocol.io/specification/2025-11-25/basic/notifications)
### Progress notifications
Servers can report progress during long-running operations:
```rust
use rmcp::model::*;
// Inside a tool handler:
for i in 0..total_items {
process_item(i).await;
context.peer.notify_progress(ProgressNotificationParam {
progress_token: ProgressToken(NumberOrString::Number(i as i64)),
progress: i as f64,
total: Some(total_items as f64),
message: Some(format!("Processing item {}/{}", i + 1, total_items)),
}).await?;
}
```
### Cancellation
Either side can cancel an in-progress request:
```rust
// Send a cancellation
context.peer.notify_cancelled(CancelledNotificationParam {
request_id: the_request_id,
reason: Some("User requested cancellation".into()),
}).await?;
```
Handle cancellation in `ServerHandler` or `ClientHandler`:
```rust
impl ServerHandler for MyServer {
async fn on_cancelled(
&self,
params: CancelledNotificationParam,
_context: NotificationContext<RoleServer>,
) {
// Abort work for params.request_id
}
}
```
### Initialized notification
Clients send `initialized` after the handshake completes:
```rust
// Sent automatically by rmcp during the serve() handshake.
// Servers handle it via:
impl ServerHandler for MyServer {
async fn on_initialized(
&self,
_context: NotificationContext<RoleServer>,
) {
// Server is ready to receive requests
}
}
```
### List-changed notifications
When available tools, prompts, or resources change, tell the client:
```rust
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`](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](https://modelcontextprotocol.io/specification/2025-11-25/server/resources#subscriptions)
### Server-side
Enable subscriptions in the resources capability and implement the `subscribe()` / `unsubscribe()` handlers:
```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<Mutex<HashSet<String>>>,
}
impl ServerHandler for MyServer {
fn get_info(&self) -> ServerInfo {
ServerInfo {
capabilities: ServerCapabilities::builder()
.enable_resources()
.enable_resources_subscribe()
.build(),
..Default::default()
}
}
async fn subscribe(
&self,
request: SubscribeRequestParams,
_context: RequestContext<RoleServer>,
) -> Result<(), McpError> {
self.subscriptions.lock().await.insert(request.uri);
Ok(())
}
async fn unsubscribe(
&self,
request: UnsubscribeRequestParams,
_context: RequestContext<RoleServer>,
) -> Result<(), McpError> {
self.subscriptions.lock().await.remove(&request.uri);
Ok(())
}
}
```
When a subscribed resource changes, notify the client:
```rust
// Check if the resource has subscribers, then notify
context.peer.notify_resource_updated(ResourceUpdatedNotificationParam {
uri: "file:///config.json".into(),
}).await?;
```
### Client-side
```rust
use rmcp::model::*;
// Subscribe to updates for a resource
client.subscribe(SubscribeRequestParams {
meta: None,
uri: "file:///config.json".into(),
}).await?;
// Unsubscribe when no longer needed
client.unsubscribe(UnsubscribeRequestParams {
meta: None,
uri: "file:///config.json".into(),
}).await?;
```
Handle update notifications in `ClientHandler`:
```rust
impl ClientHandler for MyClient {
async fn on_resource_updated(
&self,
params: ResourceUpdatedNotificationParam,
_context: NotificationContext<RoleClient>,
) {
// Re-read the resource at params.uri
}
}
```
---
## Examples
See [examples](examples/README.md).
## Feature Documentation
See [docs/FEATURES.md](docs/FEATURES.md) for detailed documentation on core MCP features: resources, prompts, sampling, roots, logging, completions, notifications, and subscriptions.
## OAuth Support
See [Oauth_support](docs/OAUTH_SUPPORT.md) for details.

View file

@ -11,7 +11,9 @@
</div>
`rmcp-macros` is a procedural macro library for the Rust Model Context Protocol (RMCP) SDK, providing macros that facilitate the development of RMCP applications.
Procedural macros for the [RMCP](../rmcp) SDK. Most users should depend on `rmcp` with the `macros` feature (enabled by default) rather than using this crate directly.
For **getting started** and **full MCP feature documentation**, see the [main README](../../README.md).
## Available Macros
@ -63,4 +65,4 @@ See the [full documentation](https://docs.rs/rmcp-macros) for detailed usage of
## License
Please refer to the LICENSE file in the project root directory.
This project is licensed under the terms specified in the repository's [LICENSE](../../LICENSE) file.

View file

@ -4,304 +4,63 @@
<div class="rustdoc-hidden">
# RMCP: Rust Model Context Protocol
# rmcp
[![Crates.io](https://img.shields.io/crates/v/rmcp.svg)](https://crates.io/crates/rmcp)
[![Documentation](https://docs.rs/rmcp/badge.svg)](https://docs.rs/rmcp)
</div>
`rmcp` is the official Rust implementation of the Model Context Protocol (MCP), a protocol designed for AI assistants to communicate with other services. This library can be used to build both servers that expose capabilities to AI assistants and clients that interact with such servers.
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.
## Quick Start
### Server Implementation
Creating a server with tools is simple using the `#[tool]` macro:
```rust,ignore
use rmcp::{
ServerHandler, ServiceExt,
handler::server::tool::ToolRouter,
model::*,
tool, tool_handler, tool_router,
transport::stdio,
ErrorData as McpError,
};
use std::sync::Arc;
use tokio::sync::Mutex;
#[derive(Clone)]
pub struct Counter {
counter: Arc<Mutex<i32>>,
tool_router: ToolRouter<Self>,
}
#[tool_router]
impl Counter {
fn new() -> Self {
Self {
counter: Arc::new(Mutex::new(0)),
tool_router: Self::tool_router(),
}
}
#[tool(description = "Increment the counter by 1")]
async fn increment(&self) -> Result<CallToolResult, McpError> {
let mut counter = self.counter.lock().await;
*counter += 1;
Ok(CallToolResult::success(vec![Content::text(
counter.to_string(),
)]))
}
#[tool(description = "Get the current counter value")]
async fn get(&self) -> Result<CallToolResult, McpError> {
let counter = self.counter.lock().await;
Ok(CallToolResult::success(vec![Content::text(
counter.to_string(),
)]))
}
}
// Implement the server handler
#[tool_handler]
impl ServerHandler for Counter {
fn get_info(&self) -> ServerInfo {
ServerInfo::new(ServerCapabilities::builder().enable_tools().build())
.with_instructions("A simple counter that tallies the number of times the increment tool has been used")
}
}
// Run the server
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create and run the server with STDIO transport
let service = Counter::new().serve(stdio()).await.inspect_err(|e| {
println!("Error starting server: {}", e);
})?;
service.waiting().await?;
Ok(())
}
```
### Structured Output
Tools can return structured JSON data with schemas. Use the [`Json`] wrapper:
```rust
# use rmcp::{tool, tool_router, handler::server::{tool::ToolRouter, wrapper::Parameters}, Json};
# use schemars::JsonSchema;
# use serde::{Serialize, Deserialize};
#
#[derive(Serialize, Deserialize, JsonSchema)]
struct CalculationRequest {
a: i32,
b: i32,
operation: String,
}
#[derive(Serialize, Deserialize, JsonSchema)]
struct CalculationResult {
result: i32,
operation: String,
}
# #[derive(Clone)]
# struct Calculator {
# tool_router: ToolRouter<Self>,
# }
#
# #[tool_router]
# impl Calculator {
#[tool(name = "calculate", description = "Perform a calculation")]
async fn calculate(&self, params: Parameters<CalculationRequest>) -> Result<Json<CalculationResult>, String> {
let result = match params.0.operation.as_str() {
"add" => params.0.a + params.0.b,
"multiply" => params.0.a * params.0.b,
_ => return Err("Unknown operation".to_string()),
};
Ok(Json(CalculationResult { result, operation: params.0.operation }))
}
# }
```
The `#[tool]` macro automatically generates an output schema from the `CalculationResult` type. See the [documentation of `tool` module](crate::handler::server::router::tool) for more instructions.
## Tasks
RMCP implements the task lifecycle from SEP-1686 so long-running or asynchronous tool calls can be queued and polled safely.
- **Create:** set the `task` field on `CallToolRequestParam` to ask the server to enqueue the tool call. The response is a `CreateTaskResult` that includes the generated `task.task_id`.
- **Inspect:** use `tasks/get` (`GetTaskInfoRequest`) to retrieve metadata such as status, timestamps, TTL, and poll interval.
- **Await results:** call `tasks/result` (`GetTaskResultRequest`) to block until the task completes and receive either the final `CallToolResult` payload or a protocol error.
- **Cancel:** call `tasks/cancel` (`CancelTaskRequest`) to request termination of a running task.
To expose task support, enable the `tasks` capability when building `ServerCapabilities`. The `#[task_handler]` macro and `OperationProcessor` utility provide reference implementations for enqueuing, tracking, and collecting task results.
### Client Implementation
Creating a client to interact with a server:
```rust,ignore
use rmcp::{
ServiceExt,
model::CallToolRequestParams,
transport::{ConfigureCommandExt, TokioChildProcess},
};
use tokio::process::Command;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Connect to a server running as a child process
let service = ()
.serve(TokioChildProcess::new(Command::new("uvx").configure(
|cmd| {
cmd.arg("mcp-server-git");
},
))?)
.await?;
// Get server information
let server_info = service.peer_info();
println!("Connected to server: {server_info:#?}");
// List available tools
let tools = service.list_tools(Default::default()).await?;
println!("Available tools: {tools:#?}");
// Call a tool
let result = service
.call_tool(
CallToolRequestParams::new("git_status")
.with_arguments(serde_json::json!({ "repo_path": "." }).as_object().cloned().unwrap_or_default())
)
.await?;
println!("Result: {result:#?}");
// Gracefully close the connection
service.cancel().await?;
Ok(())
}
```
For more examples, see the [examples directory](https://github.com/modelcontextprotocol/rust-sdk/tree/main/examples) in the repository.
For detailed documentation on core MCP features (resources, prompts, sampling, roots, logging, completions, notifications, subscriptions), see [FEATURES.md](https://github.com/modelcontextprotocol/rust-sdk/blob/main/docs/FEATURES.md).
## Transport Options
RMCP supports multiple transport mechanisms, each suited for different use cases:
### `transport-async-rw`
Low-level interface for asynchronous read/write operations. This is the foundation for many other transports.
### `transport-io`
For working directly with I/O streams (`tokio::io::AsyncRead` and `tokio::io::AsyncWrite`).
### `transport-child-process`
Run MCP servers as child processes and communicate via standard I/O.
Example:
```rust,ignore
use rmcp::transport::TokioChildProcess;
use tokio::process::Command;
let transport = TokioChildProcess::new(Command::new("mcp-server"))?;
let service = client.serve(transport).await?;
```
## Access with peer interface when handling message
You can get the [`Peer`](crate::service::Peer) struct from [`NotificationContext`](crate::service::NotificationContext) and [`RequestContext`](crate::service::RequestContext).
```rust, ignore
# use rmcp::{
# ServerHandler,
# model::{LoggingLevel, LoggingMessageNotificationParam, ProgressNotificationParam},
# service::{NotificationContext, RoleServer},
# };
# pub struct Handler;
impl ServerHandler for Handler {
async fn on_progress(
&self,
notification: ProgressNotificationParam,
context: NotificationContext<RoleServer>,
) {
let peer = context.peer;
let _ = peer
.notify_logging_message(LoggingMessageNotificationParam {
level: LoggingLevel::Info,
logger: None,
data: serde_json::json!({
"message": format!("Progress: {}", notification.progress),
}),
})
.await;
}
}
```
## Manage Multi Services
For many cases you need to manage several service in a collection, you can call `into_dyn` to convert services into the same type.
```rust, ignore
let service = service.into_dyn();
```
For **getting started**, **usage guides**, and **full MCP feature documentation** (resources, prompts, sampling, roots, logging, completions, subscriptions, etc.), see the [main README](../../README.md).
## Feature Flags
RMCP uses feature flags to control which components are included:
| Feature | Description | Default |
|---------|-------------|---------|
| `server` | Server functionality and the tool system | ✅ |
| `client` | Client functionality | |
| `macros` | `#[tool]` / `#[prompt]` macros (re-exports [`rmcp-macros`](../rmcp-macros)) | ✅ |
| `schemars` | JSON Schema generation for tool definitions | |
| `auth` | OAuth 2.0 authentication support | |
| `elicitation` | Elicitation support | |
- `client`: Enable client functionality
- `server`: Enable server functionality and the tool system
- `macros`: Enable the `#[tool]` macro (enabled by default)
- Transport-specific features:
- `transport-async-rw`: Async read/write support
- `transport-io`: I/O stream support
- `transport-child-process`: Child process support
- `transport-streamable-http-client` / `transport-streamable-http-server`: HTTP streaming (client agnostic, see [`StreamableHttpClientTransport`](crate::transport::StreamableHttpClientTransport) for details)
- `transport-streamable-http-client-reqwest`: a default `reqwest` implementation of the streamable http client
- `auth`: OAuth2 authentication support
- `schemars`: JSON Schema generation (for tool definitions)
- TLS backend options (for HTTP transports):
- `reqwest`: Uses rustls (pure Rust TLS, recommended default)
- `reqwest-native-tls`: Uses platform native TLS (OpenSSL on Linux, Secure Transport on macOS, SChannel on Windows)
- `reqwest-tls-no-provider`: Uses rustls without a default crypto provider (bring your own)
### Transport features
| Feature | Description |
|---------|-------------|
| `transport-io` | Server-side stdio transport |
| `transport-child-process` | Client-side stdio transport (spawns a child process) |
| `transport-async-rw` | Generic async read/write transport |
| `transport-streamable-http-client` | Streamable HTTP client (transport-agnostic) |
| `transport-streamable-http-client-reqwest` | Streamable HTTP client with default `reqwest` backend |
| `transport-streamable-http-server` | Streamable HTTP server transport |
### TLS backend options (for HTTP transports)
| Feature | Description |
|---------|-------------|
| `reqwest` | Uses rustls — pure Rust TLS (recommended default) |
| `reqwest-native-tls` | Uses platform-native TLS (OpenSSL / Secure Transport / SChannel) |
| `reqwest-tls-no-provider` | Uses rustls without a default crypto provider (bring your own) |
## Transports
- `transport-io`: Server stdio transport
- `transport-child-process`: Client stdio transport
- `transport-streamable-http-server` streamable http server transport
- `transport-streamable-http-client` streamable http client transport
The transport layer is pluggable. Two built-in pairs cover the most common cases:
<details>
<summary>Transport</summary>
| | Client | Server |
|:-:|:-:|:-:|
| **stdio** | [`TokioChildProcess`](crate::transport::TokioChildProcess) | [`stdio`](crate::transport::stdio) |
| **Streamable HTTP** | [`StreamableHttpClientTransport`](crate::transport::StreamableHttpClientTransport) | [`StreamableHttpService`](crate::transport::StreamableHttpService) |
The transport type must implement the [`Transport`](crate::transport::Transport) trait, which allows it to send messages concurrently and receive messages sequentially.
There are 2 pairs of standard transport types:
Any type that implements the [`Transport`](crate::transport::Transport) trait can be used. The [`IntoTransport`](crate::transport::IntoTransport) helper trait provides automatic conversions from:
| transport | client | server |
|:---------------:|:-----------------------------------------------------------------------------------:|:-----------------------------------------------------------------------------:|
| std IO | [`TokioChildProcess`](crate::transport::TokioChildProcess) | [`stdio`](crate::transport::stdio) |
| streamable http | [`StreamableHttpClientTransport`](crate::transport::StreamableHttpClientTransport) | [`StreamableHttpService`](crate::transport::StreamableHttpService) |
#### [`IntoTransport`](crate::transport::IntoTransport) trait
[`IntoTransport`](crate::transport::IntoTransport) is a helper trait that implicitly converts a type into a transport type.
These types automatically implement [`IntoTransport`](crate::transport::IntoTransport):
1. A type that implements both `futures::Sink` and `futures::Stream`, or a tuple `(Tx, Rx)` where `Tx` is `futures::Sink` and `Rx` is `futures::Stream`.
2. A type that implements both `tokio::io::AsyncRead` and `tokio::io::AsyncWrite`, or a tuple `(R, W)` where `R` is `tokio::io::AsyncRead` and `W` is `tokio::io::AsyncWrite`.
3. A type that implements the [`Worker`](crate::transport::worker::Worker) trait.
4. A type that implements the [`Transport`](crate::transport::Transport) trait.
</details>
1. `(Sink, Stream)` or a combined `Sink + Stream`
2. `(AsyncRead, AsyncWrite)` or a combined `AsyncRead + AsyncWrite`
3. A [`Worker`](crate::transport::worker::Worker) implementation
4. A [`Transport`](crate::transport::Transport) implementation directly
## License
This project is licensed under the terms specified in the repository's LICENSE file.
This project is licensed under the terms specified in the repository's [LICENSE](../../LICENSE) file.

View file

@ -1,758 +0,0 @@
# RMCP Feature Documentation
This document covers the core MCP features supported by `rmcp`, with server and client code examples for each.
For the full MCP specification, see [modelcontextprotocol.io](https://modelcontextprotocol.io/specification/2025-11-25).
## Table of Contents
- [Resources](#resources)
- [Prompts](#prompts)
- [Sampling](#sampling)
- [Roots](#roots)
- [Logging](#logging)
- [Completions](#completions)
- [Notifications](#notifications)
- [Subscriptions](#subscriptions)
---
## 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](https://modelcontextprotocol.io/specification/2025-11-25/server/resources)
### Server-side
Implement `list_resources()`, `read_resource()`, and optionally `list_resource_templates()` on the `ServerHandler` trait. Enable the resources capability in `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 {
capabilities: ServerCapabilities::builder()
.enable_resources()
.build(),
..Default::default()
}
}
async fn list_resources(
&self,
_request: Option<PaginatedRequestParams>,
_context: RequestContext<RoleServer>,
) -> Result<ListResourcesResult, McpError> {
Ok(ListResourcesResult {
resources: vec![
RawResource::new("file:///config.json", "config").no_annotation(),
RawResource::new("memo://insights", "insights").no_annotation(),
],
next_cursor: None,
meta: None,
})
}
async fn read_resource(
&self,
request: ReadResourceRequestParams,
_context: RequestContext<RoleServer>,
) -> Result<ReadResourceResult, McpError> {
match request.uri.as_str() {
"file:///config.json" => Ok(ReadResourceResult {
contents: vec![ResourceContents::text(r#"{"key": "value"}"#, &request.uri)],
}),
"memo://insights" => Ok(ReadResourceResult {
contents: 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<PaginatedRequestParams>,
_context: RequestContext<RoleServer>,
) -> Result<ListResourceTemplatesResult, McpError> {
Ok(ListResourceTemplatesResult {
resource_templates: vec![],
next_cursor: None,
meta: None,
})
}
}
```
### Client-side
```rust
use rmcp::model::{ReadResourceRequestParams};
// List all resources (handles pagination automatically)
let resources = client.list_all_resources().await?;
// Read a specific resource by URI
let result = client.read_resource(ReadResourceRequestParams {
meta: None,
uri: "file:///config.json".into(),
}).await?;
// List resource templates
let templates = client.list_all_resource_templates().await?;
```
### Notifications
Servers can notify clients when the resource list changes or when a specific resource is updated:
```rust
// 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 {
uri: "file:///config.json".into(),
}).await?;
```
Clients handle these via `ClientHandler`:
```rust
impl ClientHandler for MyClient {
async fn on_resource_list_changed(
&self,
_context: NotificationContext<RoleClient>,
) {
// Re-fetch the resource list
}
async fn on_resource_updated(
&self,
params: ResourceUpdatedNotificationParam,
_context: NotificationContext<RoleClient>,
) {
// Re-read the updated resource at params.uri
}
}
```
**Example:** [`examples/servers/src/common/counter.rs`](../examples/servers/src/common/counter.rs) (server), [`examples/clients/src/everything_stdio.rs`](../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](https://modelcontextprotocol.io/specification/2025-11-25/server/prompts)
### Server-side
Use the `#[prompt_router]`, `#[prompt]`, and `#[prompt_handler]` macros to define prompts declaratively. Arguments are defined as structs deriving `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<Vec<String>>,
}
#[derive(Clone)]
pub struct MyServer {
prompt_router: PromptRouter<Self>,
}
#[prompt_router]
impl MyServer {
fn new() -> Self {
Self { prompt_router: Self::prompt_router() }
}
/// Simple prompt without parameters
#[prompt(name = "greeting", description = "A simple greeting")]
async fn greeting(&self) -> Vec<PromptMessage> {
vec![PromptMessage::new_text(
PromptMessageRole::User,
"Hello! How can you help me today?",
)]
}
/// Prompt with typed arguments
#[prompt(name = "code_review", description = "Review code in a given language")]
async fn code_review(
&self,
Parameters(args): Parameters<CodeReviewArgs>,
) -> Result<GetPromptResult, McpError> {
let focus = args.focus_areas
.unwrap_or_else(|| vec!["correctness".into()]);
Ok(GetPromptResult {
description: Some(format!("Code review for {}", args.language)),
messages: vec![
PromptMessage::new_text(
PromptMessageRole::User,
format!("Review my {} code. Focus on: {}", args.language, focus.join(", ")),
),
],
})
}
}
#[prompt_handler]
impl ServerHandler for MyServer {
fn get_info(&self) -> ServerInfo {
ServerInfo {
capabilities: ServerCapabilities::builder().enable_prompts().build(),
..Default::default()
}
}
}
```
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
```rust
use rmcp::model::GetPromptRequestParams;
// List all prompts
let prompts = client.list_all_prompts().await?;
// Get a prompt with arguments
let result = client.get_prompt(GetPromptRequestParams {
meta: None,
name: "code_review".into(),
arguments: Some(rmcp::object!({
"language": "Rust",
"focus_areas": ["performance", "safety"]
})),
}).await?;
```
### Notifications
```rust
// Server: notify that available prompts have changed
context.peer.notify_prompt_list_changed().await?;
```
**Example:** [`examples/servers/src/prompt_stdio.rs`](../examples/servers/src/prompt_stdio.rs) (server), [`examples/clients/src/everything_stdio.rs`](../examples/clients/src/everything_stdio.rs) (client)
---
## Sampling
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)
### Server-side (requesting sampling)
Access the client's sampling capability through `context.peer.create_message()`:
```rust
use rmcp::model::*;
// Inside a ServerHandler method (e.g., call_tool):
let response = context.peer.create_message(CreateMessageRequestParams {
meta: None,
task: None,
messages: vec![SamplingMessage::user_text("Explain this error: connection refused")],
model_preferences: Some(ModelPreferences {
hints: Some(vec![ModelHint { name: Some("claude".into()) }]),
cost_priority: Some(0.3),
speed_priority: Some(0.8),
intelligence_priority: Some(0.7),
}),
system_prompt: Some("You are a helpful assistant.".into()),
include_context: Some(ContextInclusion::None),
temperature: Some(0.7),
max_tokens: 150,
stop_sequences: None,
metadata: None,
tools: None,
tool_choice: None,
}).await?;
// Extract the response text
let 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:
```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<RoleClient>,
) -> Result<CreateMessageResult, ErrorData> {
// Forward to your LLM, or return a mock response:
let response_text = call_your_llm(&params.messages).await;
Ok(CreateMessageResult {
message: SamplingMessage::assistant_text(response_text),
model: "my-model".into(),
stop_reason: Some(CreateMessageResult::STOP_REASON_END_TURN.into()),
})
}
}
```
**Example:** [`examples/servers/src/sampling_stdio.rs`](../examples/servers/src/sampling_stdio.rs) (server), [`examples/clients/src/sampling_stdio.rs`](../examples/clients/src/sampling_stdio.rs) (client)
---
## Roots
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)
### Server-side
Ask the client for its root list, and handle change notifications:
```rust
use rmcp::{ServerHandler, model::*, service::{NotificationContext, RoleServer}};
impl ServerHandler for MyServer {
// Query the client for its roots
async fn call_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 changes
async fn on_roots_list_changed(
&self,
_context: NotificationContext<RoleServer>,
) {
// Re-fetch roots to stay current
}
}
```
### Client-side
Clients declare roots capability and implement `list_roots()`:
```rust
use rmcp::{ClientHandler, model::*};
impl ClientHandler for MyClient {
async fn list_roots(
&self,
_context: RequestContext<RoleClient>,
) -> Result<ListRootsResult, ErrorData> {
Ok(ListRootsResult {
roots: vec![
Root {
uri: "file:///home/user/project".into(),
name: Some("My Project".into()),
},
],
})
}
}
```
Clients notify the server when roots change:
```rust
// After adding or removing a workspace root:
client.notify_roots_list_changed().await?;
```
---
## Logging
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)
### Server-side
Enable the logging capability, handle level changes from the client, and send log messages via the peer:
```rust
use rmcp::{ServerHandler, model::*, service::RequestContext};
impl ServerHandler for MyServer {
fn get_info(&self) -> ServerInfo {
ServerInfo {
capabilities: ServerCapabilities::builder()
.enable_logging()
.build(),
..Default::default()
}
}
// Client sets the minimum log level
async fn set_level(
&self,
request: SetLevelRequestParams,
_context: RequestContext<RoleServer>,
) -> Result<(), ErrorData> {
// Store request.level and filter future log messages accordingly
Ok(())
}
}
// Send a log message from any handler with access to the peer:
context.peer.notify_logging_message(LoggingMessageNotificationParam {
level: LoggingLevel::Info,
logger: Some("my-server".into()),
data: serde_json::json!({
"message": "Processing completed",
"items_processed": 42
}),
}).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`:
```rust
impl ClientHandler for MyClient {
async fn on_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:
```rust
client.set_level(SetLevelRequestParams {
level: LoggingLevel::Warning,
meta: None,
}).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](https://modelcontextprotocol.io/specification/2025-11-25/server/utilities/completion)
### Server-side
Enable the completions capability and implement the `complete()` handler. Use `request.context` to inspect previously filled arguments:
```rust
use rmcp::{ErrorData as McpError, ServerHandler, model::*, service::RequestContext, RoleServer};
impl ServerHandler for MyServer {
fn get_info(&self) -> ServerInfo {
ServerInfo {
capabilities: ServerCapabilities::builder()
.enable_completions()
.enable_prompts()
.build(),
..Default::default()
}
}
async fn complete(
&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 arguments
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![],
};
// Filter by the user's partial input
let filtered: Vec<String> = values.into_iter()
.map(String::from)
.filter(|v| v.to_lowercase().contains(&request.argument.value.to_lowercase()))
.collect();
Ok(CompleteResult {
completion: CompletionInfo {
values: filtered,
total: None,
has_more: Some(false),
},
})
}
}
```
### Client-side
```rust
use rmcp::model::*;
let result = client.complete(CompleteRequestParams {
meta: None,
r#ref: Reference::Prompt(PromptReference {
name: "sql_query".into(),
}),
argument: ArgumentInfo {
name: "operation".into(),
value: "SEL".into(),
},
context: None,
}).await?;
// result.completion.values contains suggestions like ["SELECT"]
```
**Example:** [`examples/servers/src/completion_stdio.rs`](../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](https://modelcontextprotocol.io/specification/2025-11-25/basic/notifications)
### Progress notifications
Servers can report progress during long-running operations:
```rust
use rmcp::model::*;
// Inside a tool handler:
for i in 0..total_items {
process_item(i).await;
context.peer.notify_progress(ProgressNotificationParam {
progress_token: ProgressToken(NumberOrString::Number(i as i64)),
progress: i as f64,
total: Some(total_items as f64),
message: Some(format!("Processing item {}/{}", i + 1, total_items)),
}).await?;
}
```
### Cancellation
Either side can cancel an in-progress request:
```rust
// Send a cancellation
context.peer.notify_cancelled(CancelledNotificationParam {
request_id: the_request_id,
reason: Some("User requested cancellation".into()),
}).await?;
```
Handle cancellation in `ServerHandler` or `ClientHandler`:
```rust
impl ServerHandler for MyServer {
async fn on_cancelled(
&self,
params: CancelledNotificationParam,
_context: NotificationContext<RoleServer>,
) {
// Abort work for params.request_id
}
}
```
### Initialized notification
Clients send `initialized` after the handshake completes:
```rust
// Sent automatically by rmcp during the serve() handshake.
// Servers handle it via:
impl ServerHandler for MyServer {
async fn on_initialized(
&self,
_context: NotificationContext<RoleServer>,
) {
// Server is ready to receive requests
}
}
```
### List-changed notifications
When available tools, prompts, or resources change, tell the client:
```rust
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`](../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](https://modelcontextprotocol.io/specification/2025-11-25/server/resources#subscriptions)
### Server-side
Enable subscriptions in the resources capability and implement the `subscribe()` / `unsubscribe()` handlers:
```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<Mutex<HashSet<String>>>,
}
impl ServerHandler for MyServer {
fn get_info(&self) -> ServerInfo {
ServerInfo {
capabilities: ServerCapabilities::builder()
.enable_resources()
.enable_resources_subscribe()
.build(),
..Default::default()
}
}
async fn subscribe(
&self,
request: SubscribeRequestParams,
_context: RequestContext<RoleServer>,
) -> Result<(), McpError> {
self.subscriptions.lock().await.insert(request.uri);
Ok(())
}
async fn unsubscribe(
&self,
request: UnsubscribeRequestParams,
_context: RequestContext<RoleServer>,
) -> Result<(), McpError> {
self.subscriptions.lock().await.remove(&request.uri);
Ok(())
}
}
```
When a subscribed resource changes, notify the client:
```rust
// Check if the resource has subscribers, then notify
context.peer.notify_resource_updated(ResourceUpdatedNotificationParam {
uri: "file:///config.json".into(),
}).await?;
```
### Client-side
```rust
use rmcp::model::*;
// Subscribe to updates for a resource
client.subscribe(SubscribeRequestParams {
meta: None,
uri: "file:///config.json".into(),
}).await?;
// Unsubscribe when no longer needed
client.unsubscribe(UnsubscribeRequestParams {
meta: None,
uri: "file:///config.json".into(),
}).await?;
```
Handle update notifications in `ClientHandler`:
```rust
impl ClientHandler for MyClient {
async fn on_resource_updated(
&self,
params: ResourceUpdatedNotificationParam,
_context: NotificationContext<RoleClient>,
) {
// Re-read the resource at params.uri
}
}
```

View file

@ -10,11 +10,32 @@
一个基于 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-支持)
- [相关资源](#相关资源)
- [相关项目](#相关项目)
- [开发](#开发)
## 使用
### 导入
@ -106,15 +127,754 @@ let quit_reason = server.cancel().await?;
```
</details>
---
## 资源
资源允许服务端向客户端暴露数据文件、数据库记录、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 {
capabilities: ServerCapabilities::builder()
.enable_resources()
.build(),
..Default::default()
}
}
async fn list_resources(
&self,
_request: Option<PaginatedRequestParams>,
_context: RequestContext<RoleServer>,
) -> Result<ListResourcesResult, McpError> {
Ok(ListResourcesResult {
resources: vec![
RawResource::new("file:///config.json", "config").no_annotation(),
RawResource::new("memo://insights", "insights").no_annotation(),
],
next_cursor: None,
meta: None,
})
}
async fn read_resource(
&self,
request: ReadResourceRequestParams,
_context: RequestContext<RoleServer>,
) -> Result<ReadResourceResult, McpError> {
match request.uri.as_str() {
"file:///config.json" => Ok(ReadResourceResult {
contents: vec![ResourceContents::text(r#"{"key": "value"}"#, &request.uri)],
}),
"memo://insights" => Ok(ReadResourceResult {
contents: 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<PaginatedRequestParams>,
_context: RequestContext<RoleServer>,
) -> Result<ListResourceTemplatesResult, McpError> {
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 {
meta: None,
uri: "file:///config.json".into(),
}).await?;
// 列出资源模板
let templates = client.list_all_resource_templates().await?;
```
### 通知
服务端可以在资源列表变更或特定资源更新时通知客户端:
```rust
// 通知资源列表已变更(客户端应重新获取)
context.peer.notify_resource_list_changed().await?;
// 通知特定资源已更新
context.peer.notify_resource_updated(ResourceUpdatedNotificationParam {
uri: "file:///config.json".into(),
}).await?;
```
客户端通过 `ClientHandler` 处理这些通知:
```rust
impl ClientHandler for MyClient {
async fn on_resource_list_changed(
&self,
_context: NotificationContext<RoleClient>,
) {
// 重新获取资源列表
}
async fn on_resource_updated(
&self,
params: ResourceUpdatedNotificationParam,
_context: NotificationContext<RoleClient>,
) {
// 重新读取 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<Vec<String>>,
}
#[derive(Clone)]
pub struct MyServer {
prompt_router: PromptRouter<Self>,
}
#[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<PromptMessage> {
vec![PromptMessage::new_text(
PromptMessageRole::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<CodeReviewArgs>,
) -> Result<GetPromptResult, McpError> {
let focus = args.focus_areas
.unwrap_or_else(|| vec!["correctness".into()]);
Ok(GetPromptResult {
description: Some(format!("Code review for {}", args.language)),
messages: vec![
PromptMessage::new_text(
PromptMessageRole::User,
format!("Review my {} code. Focus on: {}", args.language, focus.join(", ")),
),
],
})
}
}
#[prompt_handler]
impl ServerHandler for MyServer {
fn get_info(&self) -> ServerInfo {
ServerInfo {
capabilities: ServerCapabilities::builder().enable_prompts().build(),
..Default::default()
}
}
}
```
提示词函数支持以下返回类型:
- `Vec<PromptMessage>` -- 简单消息列表
- `GetPromptResult` -- 带可选描述的消息
- `Result<T, McpError>` -- 以上任一类型,附带错误处理
### 客户端
```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 {
meta: None,
task: None,
messages: vec![SamplingMessage::user_text("Explain this error: connection refused")],
model_preferences: Some(ModelPreferences {
hints: Some(vec![ModelHint { name: Some("claude".into()) }]),
cost_priority: Some(0.3),
speed_priority: Some(0.8),
intelligence_priority: Some(0.7),
}),
system_prompt: Some("You are a helpful assistant.".into()),
include_context: Some(ContextInclusion::None),
temperature: Some(0.7),
max_tokens: 150,
stop_sequences: None,
metadata: None,
tools: None,
tool_choice: None,
}).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<RoleClient>,
) -> Result<CreateMessageResult, ErrorData> {
// 转发到你的 LLM或返回模拟响应
let response_text = call_your_llm(&params.messages).await;
Ok(CreateMessageResult {
message: SamplingMessage::assistant_text(response_text),
model: "my-model".into(),
stop_reason: Some(CreateMessageResult::STOP_REASON_END_TURN.into()),
})
}
}
```
**示例:** [`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<RoleServer>,
) -> Result<CallToolResult, ErrorData> {
let roots = context.peer.list_roots().await?;
// 使用 roots.roots 了解工作区边界
// ...
}
// 当客户端的根目录列表变更时调用
async fn on_roots_list_changed(
&self,
_context: NotificationContext<RoleServer>,
) {
// 重新获取根目录以保持最新
}
}
```
### 客户端
客户端声明根目录能力并实现 `list_roots()`
```rust
use rmcp::{ClientHandler, model::*};
impl ClientHandler for MyClient {
async fn list_roots(
&self,
_context: RequestContext<RoleClient>,
) -> Result<ListRootsResult, ErrorData> {
Ok(ListRootsResult {
roots: vec![
Root {
uri: "file:///home/user/project".into(),
name: Some("My Project".into()),
},
],
})
}
}
```
客户端在根目录变更时通知服务端:
```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 {
capabilities: ServerCapabilities::builder()
.enable_logging()
.build(),
..Default::default()
}
}
// 客户端设置最低日志级别
async fn set_level(
&self,
request: SetLevelRequestParams,
_context: RequestContext<RoleServer>,
) -> Result<(), ErrorData> {
// 存储 request.level 并据此过滤后续日志消息
Ok(())
}
}
// 在任何可以访问 peer 的处理器中发送日志消息:
context.peer.notify_logging_message(LoggingMessageNotificationParam {
level: LoggingLevel::Info,
logger: Some("my-server".into()),
data: serde_json::json!({
"message": "Processing completed",
"items_processed": 42
}),
}).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<RoleClient>,
) {
println!("[{}] {}: {}", params.level,
params.logger.unwrap_or_default(), params.data);
}
}
```
客户端也可以设置服务端的日志级别:
```rust
client.set_level(SetLevelRequestParams {
level: LoggingLevel::Warning,
meta: None,
}).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 {
capabilities: ServerCapabilities::builder()
.enable_completions()
.enable_prompts()
.build(),
..Default::default()
}
}
async fn complete(
&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" => {
// 根据已填写的参数调整建议
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<String> = values.into_iter()
.map(String::from)
.filter(|v| v.to_lowercase().contains(&request.argument.value.to_lowercase()))
.collect();
Ok(CompleteResult {
completion: CompletionInfo {
values: filtered,
total: None,
has_more: Some(false),
},
})
}
}
```
### 客户端
```rust
use rmcp::model::*;
let result = client.complete(CompleteRequestParams {
meta: None,
r#ref: Reference::Prompt(PromptReference {
name: "sql_query".into(),
}),
argument: ArgumentInfo {
name: "operation".into(),
value: "SEL".into(),
},
context: None,
}).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 {
progress_token: ProgressToken(NumberOrString::Number(i as i64)),
progress: i as f64,
total: Some(total_items as f64),
message: Some(format!("Processing item {}/{}", i + 1, total_items)),
}).await?;
}
```
### 取消
任一方都可以取消正在进行的请求:
```rust
// 发送取消通知
context.peer.notify_cancelled(CancelledNotificationParam {
request_id: the_request_id,
reason: Some("User requested cancellation".into()),
}).await?;
```
`ServerHandler``ClientHandler` 中处理取消:
```rust
impl ServerHandler for MyServer {
async fn on_cancelled(
&self,
params: CancelledNotificationParam,
_context: NotificationContext<RoleServer>,
) {
// 中止 params.request_id 对应的工作
}
}
```
### 初始化通知
客户端在握手完成后发送 `initialized` 通知:
```rust
// 在 serve() 握手过程中由 rmcp 自动发送。
// 服务端通过以下方式处理:
impl ServerHandler for MyServer {
async fn on_initialized(
&self,
_context: NotificationContext<RoleServer>,
) {
// 服务端已准备好接收请求
}
}
```
### 列表变更通知
当可用的工具、提示词或资源发生变更时,通知客户端:
```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<Mutex<HashSet<String>>>,
}
impl ServerHandler for MyServer {
fn get_info(&self) -> ServerInfo {
ServerInfo {
capabilities: ServerCapabilities::builder()
.enable_resources()
.enable_resources_subscribe()
.build(),
..Default::default()
}
}
async fn subscribe(
&self,
request: SubscribeRequestParams,
_context: RequestContext<RoleServer>,
) -> Result<(), McpError> {
self.subscriptions.lock().await.insert(request.uri);
Ok(())
}
async fn unsubscribe(
&self,
request: UnsubscribeRequestParams,
_context: RequestContext<RoleServer>,
) -> Result<(), McpError> {
self.subscriptions.lock().await.remove(&request.uri);
Ok(())
}
}
```
当订阅的资源发生变更时,通知客户端:
```rust
// 检查资源是否有订阅者,然后通知
context.peer.notify_resource_updated(ResourceUpdatedNotificationParam {
uri: "file:///config.json".into(),
}).await?;
```
### 客户端
```rust
use rmcp::model::*;
// 订阅资源更新
client.subscribe(SubscribeRequestParams {
meta: None,
uri: "file:///config.json".into(),
}).await?;
// 不再需要时取消订阅
client.unsubscribe(UnsubscribeRequestParams {
meta: None,
uri: "file:///config.json".into(),
}).await?;
```
`ClientHandler` 中处理更新通知:
```rust
impl ClientHandler for MyClient {
async fn on_resource_updated(
&self,
params: ResourceUpdatedNotificationParam,
_context: NotificationContext<RoleClient>,
) {
// 重新读取 params.uri 对应的资源
}
}
```
---
## 示例
查看 [examples](../../examples/README.md)。
## 功能文档
查看 [docs/FEATURES.md](../FEATURES.md) 了解核心 MCP 功能的详细文档:资源、提示词、采样、根目录、日志、补全、通知和订阅。
## OAuth 支持
查看 [OAuth 支持](../OAUTH_SUPPORT.md) 了解详情。