tmcp
A complete Rust implementation of the Model Context Protocol (MCP), providing both client and server capabilities for building AI-integrated applications.
Overview
tmcp offers an ergonomic API for implementing MCP servers and clients with support for tools, resources, and prompts. The library uses async/await patterns with Tokio and provides procedural macros to eliminate boilerplate.
Features
- Derive Macros: Simple
#[mcp_server]attribute for automatic implementation - Multiple Transports: TCP, HTTP (with SSE), and stdio support
- Type Safety: Strongly typed protocol messages with serde
- Async-First: Built on Tokio for high-performance async I/O
Transport Options
- TCP:
server.listen_tcp("127.0.0.1:3000") - HTTP:
server.listen_http("127.0.0.1:3000")(uses SSE for server->client) - Stdio:
server.listen_stdio()for subprocess integration
Building Servers: Macro vs Trait
tmcp provides two approaches for implementing MCP servers:
The #[mcp_server] Macro
Best for simple servers that primarily expose tools. The macro automatically:
- Generates [
ServerHandler] trait implementation - Derives tool schemas from function signatures using
schemars - Registers tools in
list_toolsand routes calls incall_tool - Provides sensible defaults for
initialize
use schemars::JsonSchema;
use serde::Deserialize;
use tmcp::{mcp_server, schema::CallToolResult, tool, ServerCtx, ToolResult};
#[derive(Debug, Deserialize, JsonSchema)]
struct GreetParams {
name: String,
}
#[mcp_server]
impl MyServer {
#[tool]
async fn greet(&self, _ctx: &ServerCtx, params: GreetParams) -> ToolResult {
Ok(CallToolResult::new().with_text_content(format!(
"Hello, {}!",
params.name
)))
}
}
The [ServerHandler] Trait
Use the trait directly when you need:
- Custom initialization: Validate clients, negotiate capabilities, or reject connections
- Per-connection state: Access to
ServerCtxin all methods for client-specific data - Resources and prompts: Full access to MCP features beyond tools
- Fine-grained error handling: Custom error responses and logging
use tmcp::{ServerHandler, ServerCtx, Result};
use async_trait::async_trait;
struct MyServer;
#[async_trait]
impl ServerHandler for MyServer {
async fn initialize(&self, ctx: &ServerCtx, ...) -> Result<InitializeResult> {
// Custom capability negotiation
}
async fn list_tools(&self, ctx: &ServerCtx, ...) -> Result<ListToolsResult> {
// Dynamic tool registration
}
}
See [ServerHandler] documentation for the default behavior philosophy.