Skip to main content
Version: 1.1.0

Server Essentials

A server in rust-mcp-sdk has three parts: a handler trait, capability methods, and a runtime.

The ServerHandler traitโ€‹

ServerHandler is the trait you implement. It has ~30 methods with default implementations - you override only the ones relevant to your server:

#[async_trait]
impl ServerHandler for MyHandler {
// โœ… Must override to list your tools
async fn handle_list_tools_request(&self, ...) -> Result<ListToolsResult, RpcError>;

// โœ… Must override to execute them
async fn handle_call_tool_request(&self, ...) -> Result<CallToolResult, CallToolError>;

// All other methods have reasonable defaults
// (ping, initialize, notifications - work out of the box)
}

The Big 3 capabilitiesโ€‹

CapabilityMethod to overrideWhat it returns
Toolshandle_list_tools_request + handle_call_tool_requestFunctions the LLM can call
Resourceshandle_list_resources_request + handle_read_resource_requestData the LLM can read
Promptshandle_list_prompts_request + handle_get_prompt_requestReusable message templates

When a client asks "what can you do?" (list_tools), your handler returns a list of Tool objects. When the client "calls a tool" (call_tool), your handler runs the logic and returns a result.

Server lifecycleโ€‹

  1. Create - build InitializeResult (your server's identity + capabilities)
  2. Create handler - implement ServerHandler, convert with .to_mcp_server_handler()
  3. Create transport - StdioTransport::new() for stdio servers, or skip this step and let an HTTP backend (create_axum_server()) manage connections for you
  4. Start - server.start().await
  5. Run - the SDK handles client connections, JSON-RPC, and dispatches to your handler
// Minimal lifecycle
let server_details = InitializeResult { /* ... */ };
let handler = MyHandler.to_mcp_server_handler();
let transport = StdioTransport::new(TransportOptions::default())?;
let server = server_runtime::create_server(McpServerOptions {
server_details,
transport,
handler,
task_store: None,
client_task_store: None,
message_observer: None,
});
server.start().await?;

Declaring capabilitiesโ€‹

You must declare what your server supports in ServerCapabilities:

ServerCapabilities {
tools: Some(ServerCapabilitiesTools::default()), // I have tools
resources: Some(ServerCapabilitiesResources::default()), // I have resources
prompts: Some(ServerCapabilitiesPrompts::default()), // I have prompts
..Default::default()
}
Logging and completions

The logging and completions capability fields are plain JSON maps - declare them with logging: Some(Default::default()).

If you don't declare a capability, clients won't try to use it - even if your handler implements the corresponding method.

Standard vs Core handlerโ€‹

  • ServerHandler - fine-grained methods per capability. Use this in 90% of cases.
  • ServerHandlerCore - three raw methods (handle_request, handle_notification, handle_error). Use for full control.

See Handler Traits for a detailed comparison.

Nextโ€‹

Follow the guided path in Get Started, or dive deeper into Tools, Resources, or Prompts.