Based on research of the official SDK from git main branch.
The git version has different module structure than documented:
- Use
rmcp::model::CallToolResult(notrmcp::server::CallToolResult) - Use
rmcp::Error as McpError(notServiceError) - Use
schemars::JsonSchemain derive macros (matching version 0.8.x) - Use
rmcp::model::InitializeResultas ServerInfo - Use
rmcp::handler::server::tool::Parameters - Use
rmcp::model::Contentfor tool responses
-
ServerHandler Trait: Core trait that services implement
- Provides default implementations for most methods
- Must be
Sized + Send + Sync + 'static - Methods return futures with Result types
-
Tool Definition: Uses
#[tool]attribute macro#[tool_router] impl Counter { #[tool(description = "Increment the counter by 1")] async fn increment(&self) -> Result<CallToolResult, McpError> { // implementation } }
-
Transport Setup:
use tokio::io::{stdin, stdout}; let transport = (stdin(), stdout()); let server = service.serve(transport).await?;
-
Service Structure:
#[derive(Clone)] pub struct MyService { // fields } #[tool_router] impl MyService { // tool methods with #[tool] attribute } impl ServerHandler for MyService { async fn get_info(&self) -> ServerInfo { // server metadata } }
-
Tool Method Signatures:
- Return
Result<CallToolResult, McpError> - Can be async or sync
- Parameters can be:
- Individual params with
#[schemars(description = "...")] - Aggregated with
Parameters<T>where T is a struct or JsonObject
- Individual params with
- Return
-
Required Imports:
use rmcp::{ handler::{ServerHandler, Parameters}, types::{CallToolResult, McpError, ServerInfo}, ServiceExt, };
Tools return CallToolResult which must be constructed using:
CallToolResult::success(vec![Content::text("response text")])For JSON responses, serialize first:
CallToolResult::success(vec![Content::text(
serde_json::to_string_pretty(&json_value).unwrap()
)])#[rmcp::tool(description = "Add two numbers")]
async fn add(
&self,
params: Parameters<AddRequest>,
) -> Result<CallToolResult, McpError> {
let AddRequest { a, b } = params.0;
let result = a + b;
Ok(CallToolResult::success(vec![Content::text(
result.to_string()
)]))
}Use specific error constructors:
McpError::invalid_params("message", Some(json!({...})))McpError::internal_error("message", Some(json!({...})))McpError::resource_not_found("message", Some(json!({...})))
The crates.io version uses different macros:
#[tool(tool_box)]on impl block#[tool(aggr)]for aggregated params#[tool(param)]for individual params
The git version is cleaner and more streamlined.
- stdio: Fully implemented and stable
- SSE: Deprecated but still supported
- Streamable HTTP: New recommended transport (replacing SSE)
- Examples exist for all transports in examples/servers/src/
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Initialize service
let service = MyService::new();
// Set up transport (stdio example)
use tokio::io::{stdin, stdout};
let transport = (stdin(), stdout());
// Serve
let server = service.serve(transport).await?;
// Wait for completion
let quit_reason = server.waiting().await?;
println!("Server quit: {:?}", quit_reason);
Ok(())
}