From 38ceec794691a44dc09cc1f03fca96f899fea8de Mon Sep 17 00:00:00 2001 From: Jonathan Hefner Date: Wed, 10 Dec 2025 13:21:02 -0600 Subject: [PATCH] docs: show README content on docs.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use `#![doc = include_str!("../README.md")]` to display README as crate documentation on docs.rs for both `rmcp` and `rmcp-macros`. Changes to support this: - Fix code examples to compile as doc tests (`rust,no_run`) - Fix broken rustdoc links with explicit `crate::` paths - Add "Structured Output" section and examples link to rmcp README - Simplify rmcp-macros README to a summary table with doc links - Fix grammar throughout - Add CSS to hide GitHub badges when rendered as rustdoc 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- crates/rmcp-macros/README.md | 176 ++++++++-------------------------- crates/rmcp-macros/src/lib.rs | 2 + crates/rmcp/README.md | 129 +++++++++++++++++-------- crates/rmcp/src/lib.rs | 135 +------------------------- 4 files changed, 133 insertions(+), 309 deletions(-) diff --git a/crates/rmcp-macros/README.md b/crates/rmcp-macros/README.md index f009329b..16df636e 100644 --- a/crates/rmcp-macros/README.md +++ b/crates/rmcp-macros/README.md @@ -1,162 +1,64 @@ -# rmcp-macros - -`rmcp-macros` is a procedural macro library for the Rust Model Context Protocol (RMCP) SDK, providing macros that facilitate the development of RMCP applications. + -## Features +
-This library primarily provides the following macros: +# rmcp-macros -- `#[tool]`: Used to mark functions as RMCP tools, automatically generating necessary metadata and invocation mechanisms +[![Crates.io](https://img.shields.io/crates/v/rmcp-macros.svg)](https://crates.io/crates/rmcp-macros) +[![Documentation](https://docs.rs/rmcp-macros/badge.svg)](https://docs.rs/rmcp-macros) -## Usage +
-### tool +`rmcp-macros` is a procedural macro library for the Rust Model Context Protocol (RMCP) SDK, providing macros that facilitate the development of RMCP applications. -This macro is used to mark a function as a tool handler. +## Available Macros -This will generate a function that return the attribute of this tool, with type `rmcp::model::Tool`. +| Macro | Description | +|-------|-------------| +| [`#[tool]`][tool] | Mark a function as an MCP tool handler | +| [`#[tool_router]`][tool_router] | Generate a tool router from an impl block | +| [`#[tool_handler]`][tool_handler] | Generate `call_tool` and `list_tools` handler methods | +| [`#[prompt]`][prompt] | Mark a function as an MCP prompt handler | +| [`#[prompt_router]`][prompt_router] | Generate a prompt router from an impl block | +| [`#[prompt_handler]`][prompt_handler] | Generate `get_prompt` and `list_prompts` handler methods | -#### Usage +[tool]: https://docs.rs/rmcp-macros/latest/rmcp_macros/attr.tool.html +[tool_router]: https://docs.rs/rmcp-macros/latest/rmcp_macros/attr.tool_router.html +[tool_handler]: https://docs.rs/rmcp-macros/latest/rmcp_macros/attr.tool_handler.html +[prompt]: https://docs.rs/rmcp-macros/latest/rmcp_macros/attr.prompt.html +[prompt_router]: https://docs.rs/rmcp-macros/latest/rmcp_macros/attr.prompt_router.html +[prompt_handler]: https://docs.rs/rmcp-macros/latest/rmcp_macros/attr.prompt_handler.html -| field | type | usage | -| :- | :- | :- | -| `name` | `String` | The name of the tool. If not provided, it defaults to the function name. | -| `description` | `String` | A description of the tool. The document of this function will be used. | -| `input_schema` | `Expr` | A JSON Schema object defining the expected parameters for the tool. If not provide, if will use the json schema of its argument with type `Parameters` | -| `annotations` | `ToolAnnotationsAttribute` | Additional tool information. Defaults to `None`. | +## Quick Example -#### Example +```rust,ignore +use rmcp::{tool, tool_router, tool_handler, ServerHandler, model::*}; -```rust -#[tool(name = "my_tool", description = "This is my tool", annotations(title = "我的工具", read_only_hint = true))] -pub async fn my_tool(param: Parameters) { - // handling tool request +#[derive(Clone)] +struct MyServer { + tool_router: rmcp::handler::server::tool::ToolRouter, } -``` - -### tool_router - -This macro is used to generate a tool router based on functions marked with `#[rmcp::tool]` in an implementation block. - -It creates a function that returns a `ToolRouter` instance. - -In most case, you need to add a field for handler to store the router information and initialize it when creating handler, or store it with a static variable. - -#### Usage -| field | type | usage | -| :- | :- | :- | -| `router` | `Ident` | The name of the router function to be generated. Defaults to `tool_router`. | -| `vis` | `Visibility` | The visibility of the generated router function. Defaults to empty. | - -#### Example - -```rust #[tool_router] -impl MyToolHandler { - #[tool] - pub fn my_tool() { - - } - - pub fn new() -> Self { - Self { - // the default name of tool router will be `tool_router` - tool_router: Self::tool_router(), - } - } -} -``` - -Or specify the visibility and router name, which would be helpful when you want to combine multiple routers into one: - -```rust -mod a { - #[tool_router(router = tool_router_a, vis = "pub")] - impl MyToolHandler { - #[tool] - fn my_tool_a() { - - } +impl MyServer { + #[tool(description = "Say hello")] + async fn hello(&self) -> String { + "Hello, world!".into() } } -mod b { - #[tool_router(router = tool_router_b, vis = "pub")] - impl MyToolHandler { - #[tool] - fn my_tool_b() { - - } - } -} - -impl MyToolHandler { - fn new() -> Self { - Self { - tool_router: self::tool_router_a() + self::tool_router_b(), - } - } -} -``` - -### tool_handler - -This macro will generate the handler for `tool_call` and `list_tools` methods in the implementation block, by using an existing `ToolRouter` instance. - -#### Usage - -| field | type | usage | -| :- | :- | :- | -| `router` | `Expr` | The expression to access the `ToolRouter` instance. Defaults to `self.tool_router`. | - -#### Example -```rust #[tool_handler] -impl ServerHandler for MyToolHandler { - // ...implement other handler -} -``` - -or using a custom router expression: -```rust -#[tool_handler(router = self.get_router().await)] -impl ServerHandler for MyToolHandler { - // ...implement other handler -} -``` - -#### Explained -This macro will be expended to something like this: -```rust -impl ServerHandler for MyToolHandler { - async fn call_tool( - &self, - request: CallToolRequestParam, - context: RequestContext, - ) -> Result { - let tcc = ToolCallContext::new(self, request, context); - self.tool_router.call(tcc).await - } - - async fn list_tools( - &self, - _request: Option, - _context: RequestContext, - ) -> Result { - let items = self.tool_router.list_all(); - Ok(ListToolsResult::with_all_items(items)) +impl ServerHandler for MyServer { + fn get_info(&self) -> ServerInfo { + ServerInfo::default() } } ``` - -## Advanced Features - -- Support for custom tool names and descriptions -- Automatic generation of tool descriptions from documentation comments -- JSON Schema generation for tool parameters +See the [full documentation](https://docs.rs/rmcp-macros) for detailed usage of each macro. ## License -Please refer to the LICENSE file in the project root directory. +Please refer to the LICENSE file in the project root directory. diff --git a/crates/rmcp-macros/src/lib.rs b/crates/rmcp-macros/src/lib.rs index 6bb06827..2152bc0a 100644 --- a/crates/rmcp-macros/src/lib.rs +++ b/crates/rmcp-macros/src/lib.rs @@ -1,3 +1,5 @@ +#![doc = include_str!("../README.md")] + #[allow(unused_imports)] use proc_macro::TokenStream; diff --git a/crates/rmcp/README.md b/crates/rmcp/README.md index 8f021d33..c1fcb493 100644 --- a/crates/rmcp/README.md +++ b/crates/rmcp/README.md @@ -1,12 +1,17 @@ -# RMCP: Rust Model Context Protocol + -`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. +
-wait for the first release. - +# RMCP: Rust Model Context Protocol +[![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) +
+ +`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. ## Quick Start @@ -14,12 +19,15 @@ wait for the first release. Creating a server with tools is simple using the `#[tool]` macro: -```rust, ignore +```rust,no_run use rmcp::{ - handler::server::router::tool::ToolRouter, model::*, tool, tool_handler, tool_router, - transport::stdio, ErrorData as McpError, ServiceExt, + ServerHandler, ServiceExt, + handler::server::tool::ToolRouter, + model::*, + tool, tool_handler, tool_router, + transport::stdio, + ErrorData as McpError, }; -use std::future::Future; use std::sync::Arc; use tokio::sync::Mutex; @@ -58,7 +66,7 @@ impl Counter { // Implement the server handler #[tool_handler] -impl rmcp::ServerHandler for Counter { +impl ServerHandler for Counter { fn get_info(&self) -> ServerInfo { ServerInfo { instructions: Some("A simple counter that tallies the number of times the increment tool has been used".into()), @@ -76,20 +84,63 @@ async fn main() -> Result<(), Box> { 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, +# } +# +# #[tool_router] +# impl Calculator { +#[tool(name = "calculate", description = "Perform a calculation")] +async fn calculate(&self, params: Parameters) -> Result, 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. + ### Client Implementation Creating a client to interact with a server: -```rust, ignore +```rust,no_run use rmcp::{ + ServiceExt, model::CallToolRequestParam, - service::ServiceExt, - transport::{TokioChildProcess, ConfigureCommandExt} + transport::{ConfigureCommandExt, TokioChildProcess}, }; use tokio::process::Command; @@ -97,12 +148,12 @@ use tokio::process::Command; async fn main() -> Result<(), Box> { // 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?; + .serve(TokioChildProcess::new(Command::new("uvx").configure( + |cmd| { + cmd.arg("mcp-server-git"); + }, + ))?) + .await?; // Get server information let server_info = service.peer_info(); @@ -115,19 +166,20 @@ async fn main() -> Result<(), Box> { // Call a tool let result = service .call_tool(CallToolRequestParam { - name: "increment".into(), - arguments: None, + name: "git_status".into(), + arguments: serde_json::json!({ "repo_path": "." }).as_object().cloned(), }) .await?; println!("Result: {result:#?}"); // Gracefully close the connection service.cancel().await?; - Ok(()) } ``` +For more examples, see the [examples directory](https://github.com/anthropics/mcp-rust-sdk/tree/main/examples) in the repository. + ## Transport Options RMCP supports multiple transport mechanisms, each suited for different use cases: @@ -142,7 +194,7 @@ For working directly with I/O streams (`tokio::io::AsyncRead` and `tokio::io::As Run MCP servers as child processes and communicate via standard I/O. Example: -```rust +```rust,ignore use rmcp::transport::TokioChildProcess; use tokio::process::Command; @@ -150,8 +202,6 @@ 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). @@ -203,7 +253,7 @@ RMCP uses feature flags to control which components are included: - `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`] for details) + - `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) @@ -218,22 +268,23 @@ RMCP uses feature flags to control which components are included:
Transport -The transport type must implemented [`Transport`] trait, which allow it send message concurrently and receive message sequentially. + +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: -| transport | client | server | -|:-: |:-: |:-: | -| std IO | [`child_process::TokioChildProcess`] | [`io::stdio`] | -| streamable http | [`streamable_http_client::StreamableHttpClientTransport`] | [`streamable_http_server::session::create_session`] | +| transport | client | server | +|:---------------:|:-----------------------------------------------------------------------------------:|:-----------------------------------------------------------------------------:| +| std IO | [`TokioChildProcess`](crate::transport::TokioChildProcess) | [`stdio`](crate::transport::stdio) | +| streamable http | [`StreamableHttpClientTransport`](crate::transport::StreamableHttpClientTransport) | [`StreamableHttpService`](crate::transport::StreamableHttpService) | -#### [IntoTransport](`IntoTransport`) trait -[`IntoTransport`] is a helper trait that implicitly convert a type into a transport type. +#### [`IntoTransport`](crate::transport::IntoTransport) trait +[`IntoTransport`](crate::transport::IntoTransport) is a helper trait that implicitly converts a type into a transport type. -These types is automatically implemented [`IntoTransport`] trait -1. A type that already implement both [`futures::Sink`] and [`futures::Stream`] trait, or a tuple `(Tx, Rx)` where `Tx` is [`futures::Sink`] and `Rx` is [`futures::Stream`]. -2. A type that implement both [`tokio::io::AsyncRead`] and [`tokio::io::AsyncWrite`] trait. or a tuple `(R, W)` where `R` is [`tokio::io::AsyncRead`] and `W` is [`tokio::io::AsyncWrite`]. -3. A type that implement [Worker](`worker::Worker`) trait. -4. A type that implement [`Transport`] trait. +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.
diff --git a/crates/rmcp/src/lib.rs b/crates/rmcp/src/lib.rs index 9f81eabe..6b3b4431 100644 --- a/crates/rmcp/src/lib.rs +++ b/crates/rmcp/src/lib.rs @@ -1,138 +1,7 @@ #![cfg_attr(docsrs, feature(doc_cfg))] #![cfg_attr(docsrs, allow(unused_attributes))] -//! The official Rust SDK for the Model Context Protocol (MCP). -//! -//! The MCP is a protocol that allows AI assistants to communicate with other -//! services. `rmcp` is the official Rust implementation of this protocol. -//! -//! There are two ways in which the library can be used, namely to build a -//! server or to build a client. -//! -//! ## Server -//! -//! A server is a service that exposes capabilities. For example, a common -//! use-case is for the server to make multiple tools available to clients such -//! as Claude Desktop or the Cursor IDE. -//! -//! For example, to implement a server that has a tool that can count, you would -//! make an object for that tool and add an implementation with the `#[tool_router]` macro: -//! -//! ```rust -//! use std::sync::Arc; -//! use rmcp::{ErrorData as McpError, model::*, tool, tool_router, handler::server::tool::ToolRouter}; -//! use tokio::sync::Mutex; -//! -//! #[derive(Clone)] -//! pub struct Counter { -//! counter: Arc>, -//! tool_router: ToolRouter, -//! } -//! -//! #[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 { -//! let mut counter = self.counter.lock().await; -//! *counter += 1; -//! Ok(CallToolResult::success(vec![Content::text( -//! counter.to_string(), -//! )])) -//! } -//! } -//! ``` -//! -//! ### Structured Output -//! -//! Tools can also 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, -//! # } -//! # -//! # #[tool_router] -//! # impl Calculator { -//! #[tool(name = "calculate", description = "Perform a calculation")] -//! async fn calculate(&self, params: Parameters) -> Result, 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. -//! -//! Next also implement [ServerHandler] for your server type and start the server inside -//! `main` by calling `.serve(...)`. See the examples directory in the repository for more information. -//! -//! ## Client -//! -//! A client can be used to interact with a server. Clients can be used to get a -//! list of the available tools and to call them. For example, we can `uv` to -//! start a MCP server in Python and then list the tools and call `git status` -//! as follows: -//! -//! ```rust -//! use anyhow::Result; -//! use rmcp::{model::CallToolRequestParam, service::ServiceExt, transport::{TokioChildProcess, ConfigureCommandExt}}; -//! use tokio::process::Command; -//! -//! async fn client() -> Result<()> { -//! let service = ().serve(TokioChildProcess::new(Command::new("uvx").configure(|cmd| { -//! cmd.arg("mcp-server-git"); -//! }))?).await?; -//! -//! // Initialize -//! let server_info = service.peer_info(); -//! println!("Connected to server: {server_info:#?}"); -//! -//! // List tools -//! let tools = service.list_tools(Default::default()).await?; -//! println!("Available tools: {tools:#?}"); -//! -//! // Call tool 'git_status' with arguments = {"repo_path": "."} -//! let tool_result = service -//! .call_tool(CallToolRequestParam { -//! name: "git_status".into(), -//! arguments: serde_json::json!({ "repo_path": "." }).as_object().cloned(), -//! }) -//! .await?; -//! println!("Tool result: {tool_result:#?}"); -//! -//! service.cancel().await?; -//! Ok(()) -//! } -//! ``` +#![doc = include_str!("../README.md")] + mod error; #[allow(deprecated)] pub use error::{Error, ErrorData, RmcpError};