rust-sdk/crates/rmcp
Michael Assaf 9349f5cb26
feat: Add prompt support (#351)
* feat: add prompt support with typed argument handling

- Implement #[prompt], #[prompt_router], and #[prompt_handler] macros
- Add automatic JSON schema generation from Rust types for arguments
- Support flexible async handler signatures with automatic adaptation
- Create PromptRouter for efficient prompt dispatch
- Include comprehensive tests and example implementation

This enables MCP servers to provide reusable prompt templates that
LLMs can discover and invoke with strongly-typed parameters, similar
to the existing tool system but optimized for prompt use cases.

🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>

* refactor: unify parameter handling between tools and prompts

- Replace Arguments<T> with Parameters<T> for consistent API
- Create shared common module for tool/prompt utilities
- Modernize async handling with futures::future::BoxFuture
- Move cached_schema_for_type to common module for reuse
- Update error types from rmcp::Error to rmcp::ErrorData
- Add comprehensive trait implementations for parameter extraction

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor: extract Parameters wrapper to shared module and unify trait usage

- Move Parameters type from common.rs to dedicated wrapper/parameters.rs module
- Unify extractor traits by using FromContextPart for both tools and prompts
- Remove duplicate FromToolCallContextPart and FromPromptContextPart traits
- Add structured output support to Json wrapper with IntoCallToolResult impl
- Improve error messages with more descriptive panic for schema serialization
- Update all imports across codebase to use new module path
- Clean up trailing whitespace and formatting inconsistencies

This consolidates parameter extraction logic and reduces code duplication
between tool and prompt handlers while maintaining backward compatibility.

* chore: remove committed .egg-info directory and update gitignore

* docs: fix documentation formatting
2025-08-21 11:22:19 +08:00
..
src feat: Add prompt support (#351) 2025-08-21 11:22:19 +08:00
tests feat: Add prompt support (#351) 2025-08-21 11:22:19 +08:00
Cargo.toml feat: Add prompt support (#351) 2025-08-21 11:22:19 +08:00
CHANGELOG.md chore: release v0.6.0 (#369) 2025-08-19 09:23:09 -04:00
README.md feat: include reqwest in transport-streamble-http-client feature (#376) 2025-08-20 11:43:18 +08:00

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.

Quick Start

Server Implementation

Creating a server with tools is simple using the #[tool] macro:

use rmcp::{ErrorData as McpError, ServiceExt, model::*, tool, tool_router, transport::stdio, handler::server::tool::ToolCallContext, handler::server::router::tool::ToolRouter};
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 rmcp::ServerHandler for Counter {
    fn get_info(&self) -> ServerInfo {
        ServerInfo {
            instructions: Some("A simple calculator".into()),
            capabilities: ServerCapabilities::builder().enable_tools().build(),
            ..Default::default()
        }
    }
}

// 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(())
}

Client Implementation

Creating a client to interact with a server:

use rmcp::{
    model::CallToolRequestParam,
    service::ServiceExt,
    transport::{TokioChildProcess, ConfigureCommandExt}
};
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(CallToolRequestParam {
            name: "increment".into(),
            arguments: None,
        })
        .await?;
    println!("Result: {result:#?}");

    // Gracefully close the connection
    service.cancel().await?;
    
    Ok(())
}

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:

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 struct from NotificationContext and RequestContext.

# 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.

let service = service.into_dyn();

Feature Flags

RMCP uses feature flags to control which components are included:

  • 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-sse-client / transport-sse-server: SSE support (client agnostic)
      • transport-sse-client-reqwest: a default reqwest implementation of the SSE client
    • transport-streamable-http-client / transport-streamable-http-server: HTTP streaming (client agnostic, see [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)

Transports

  • transport-io: Server stdio transport
  • transport-sse-server: Server SSE transport
  • transport-child-process: Client stdio transport
  • transport-sse-client: Client sse transport
  • transport-streamable-http-server streamable http server transport
  • transport-streamable-http-client streamable http client transport
Transport The transport type must implemented [`Transport`] trait, which allow it send message concurrently and receive message sequentially. There are 3 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]
sse [sse_client::SseClientTransport] [sse_server::SseServer]

IntoTransport trait

[IntoTransport] is a helper trait that implicitly convert 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 trait.
  4. A type that implement [Transport] trait.

License

This project is licensed under the terms specified in the repository's LICENSE file.