Skip to main content

Crate tower_mcp

Crate tower_mcp 

Source
Expand description

§tower-mcp

Tower-native Model Context Protocol (MCP) implementation for Rust.

This crate provides a composable, middleware-friendly approach to building MCP servers and clients using the Tower service abstraction.

§Philosophy

Unlike framework-style MCP implementations, tower-mcp treats MCP as just another protocol that can be served through Tower’s Service trait. This means:

  • Standard tower middleware (tracing, metrics, rate limiting, auth) just works
  • Same service can be exposed over multiple transports (stdio, HTTP, WebSocket)
  • Easy integration with existing tower-based applications (axum, tonic, etc.)

§Familiar to axum Users

If you’ve used axum, tower-mcp’s API will feel familiar. We’ve adopted axum’s patterns for a consistent Rust web ecosystem experience:

  • Extractor pattern: Tool handlers use extractors like extract::State<T>, extract::Json<T>, and extract::Context - just like axum’s request extractors
  • Router composition: McpRouter::merge() and McpRouter::nest() work like axum’s router methods for combining routers
  • Per-route middleware: Apply Tower layers to individual tools, resources, or prompts via .layer() on builders
  • Builder pattern: Fluent builders for tools, resources, and prompts
use std::sync::Arc;
use tower_mcp::{ToolBuilder, CallToolResult};
use tower_mcp::extract::{State, Json, Context};
use schemars::JsonSchema;
use serde::Deserialize;

#[derive(Clone)]
struct AppState { db_url: String }

#[derive(Deserialize, JsonSchema)]
struct SearchInput { query: String }

// Looks just like an axum handler!
let tool = ToolBuilder::new("search")
    .description("Search the database")
    .extractor_handler_typed::<_, _, _, SearchInput>(
        Arc::new(AppState { db_url: "postgres://...".into() }),
        |State(app): State<Arc<AppState>>,
         ctx: Context,
         Json(input): Json<SearchInput>| async move {
            ctx.report_progress(0.5, Some(1.0), Some("Searching...")).await;
            Ok(CallToolResult::text(format!("Found results for: {}", input.query)))
        },
    )
    .build()
    .unwrap();

§Quick Start: Server

Build an MCP server with tools, resources, and prompts:

use tower_mcp::{BoxError, McpRouter, ToolBuilder, CallToolResult, StdioTransport};
use schemars::JsonSchema;
use serde::Deserialize;

#[derive(Debug, Deserialize, JsonSchema)]
struct GreetInput {
    name: String,
}

#[tokio::main]
async fn main() -> Result<(), BoxError> {
    // Define a tool
    let greet = ToolBuilder::new("greet")
        .description("Greet someone by name")
        .handler(|input: GreetInput| async move {
            Ok(CallToolResult::text(format!("Hello, {}!", input.name)))
        })
        .build()?;

    // Create router and run over stdio
    let router = McpRouter::new()
        .server_info("my-server", "1.0.0")
        .tool(greet);

    StdioTransport::new(router).run().await?;
    Ok(())
}

§Quick Start: Client

Connect to an MCP server and call tools:

use tower_mcp::BoxError;
use tower_mcp::client::{McpClient, StdioClientTransport};

#[tokio::main]
async fn main() -> Result<(), BoxError> {
    // Connect to server
    let transport = StdioClientTransport::spawn("my-mcp-server", &[]).await?;
    let mut client = McpClient::new(transport);

    // Initialize and list tools
    client.initialize("my-client", "1.0.0").await?;
    let tools = client.list_tools().await?;

    // Call a tool
    let result = client.call_tool("greet", serde_json::json!({"name": "World"})).await?;
    println!("{:?}", result);

    Ok(())
}

§Key Types

§Server

§Client

§Protocol

§Feature Flags

  • full - Enable all optional features
  • http - HTTP/SSE transport for web servers
  • websocket - WebSocket transport for bidirectional communication
  • childproc - Child process transport for subprocess management
  • oauth - OAuth 2.1 resource server support (token validation, metadata endpoint)
  • testing - Test utilities ([TestClient]) for ergonomic MCP server testing

§Middleware Placement Guide

tower-mcp supports Tower middleware at multiple levels. Choose based on scope:

LevelMethodScopeUse Cases
TransportHttpTransport::layer()All MCP requestsGlobal timeout, rate limit, metrics
axum.into_router().layer()HTTP layer onlyCORS, compression, request logging
Per-toolToolBuilder::...layer()Single toolTool-specific timeout, concurrency
Per-resourceResourceBuilder::...layer()Single resourceCaching, read timeout
Per-promptPromptBuilder::...layer()Single promptGeneration timeout

§Decision Tree

Where should my middleware go?
│
├─ Affects ALL MCP requests?
│  └─ Yes → Transport: HttpTransport::layer() or WebSocketTransport::layer()
│
├─ HTTP-specific (CORS, compression, headers)?
│  └─ Yes → axum: transport.into_router().layer(...)
│
├─ Only one specific tool?
│  └─ Yes → Per-tool: ToolBuilder::...handler(...).layer(...)
│
├─ Only one specific resource?
│  └─ Yes → Per-resource: ResourceBuilder::...handler(...).layer(...)
│
└─ Only one specific prompt?
   └─ Yes → Per-prompt: PromptBuilder::...handler(...).layer(...)

§Example: Layered Timeouts

use std::time::Duration;
use tower::timeout::TimeoutLayer;
use tower_mcp::{McpRouter, ToolBuilder, CallToolResult, HttpTransport};
use schemars::JsonSchema;
use serde::Deserialize;

#[derive(Debug, Deserialize, JsonSchema)]
struct SearchInput { query: String }

// This tool gets a longer timeout than the global default
let slow_search = ToolBuilder::new("slow_search")
    .description("Thorough search (may take a while)")
    .handler(|input: SearchInput| async move {
        // ... slow operation ...
        Ok(CallToolResult::text("results"))
    })
    .layer(TimeoutLayer::new(Duration::from_secs(60)))  // 60s for this tool
    .build()
    .unwrap();

let router = McpRouter::new()
    .server_info("example", "1.0.0")
    .tool(slow_search);

// Global 30s timeout for all OTHER requests
let transport = HttpTransport::new(router)
    .layer(TimeoutLayer::new(Duration::from_secs(30)));

In this example:

  • slow_search tool has a 60-second timeout (per-tool layer)
  • All other MCP requests have a 30-second timeout (transport layer)
  • The per-tool layer is inner to the transport layer

§Layer Ordering

Layers wrap from outside in. The first layer added is the outermost:

Request → [Transport Layer] → [Per-tool Layer] → Handler → Response

For per-tool/resource/prompt, chained .layer() calls also wrap outside-in:

ToolBuilder::new("api")
    .handler(...)
    .layer(TimeoutLayer::new(...))      // Outer: timeout checked first
    .layer(ConcurrencyLimitLayer::new(5)) // Inner: concurrency after timeout
    .build()

§Full Example

See examples/tool_middleware.rs for a complete example demonstrating:

  • Different timeouts per tool
  • Concurrency limiting for expensive operations
  • Multiple layers combined on a single tool

§MCP Specification

This crate implements the MCP specification (2025-11-25): https://modelcontextprotocol.io/specification/2025-11-25

Re-exports§

pub use async_task::Task;
pub use async_task::TaskStore;
pub use client::ClientTransport;
pub use client::McpClient;
pub use client::StdioClientTransport;
pub use context::ChannelClientRequester;
pub use context::ClientRequester;
pub use context::ClientRequesterHandle;
pub use context::Extensions;
pub use context::NotificationReceiver;
pub use context::NotificationSender;
pub use context::OutgoingRequest;
pub use context::OutgoingRequestReceiver;
pub use context::OutgoingRequestSender;
pub use context::RequestContext;
pub use context::RequestContextBuilder;
pub use context::ServerNotification;
pub use context::outgoing_request_channel;
pub use error::BoxError;
pub use error::Error;
pub use error::Result;
pub use error::ToolError;
pub use filter::CapabilityFilter;
pub use filter::DenialBehavior;
pub use filter::Filterable;
pub use filter::PromptFilter;
pub use filter::ResourceFilter;
pub use filter::ToolFilter;
pub use jsonrpc::JsonRpcLayer;
pub use jsonrpc::JsonRpcService;
pub use prompt::BoxPromptService;
pub use prompt::Prompt;
pub use prompt::PromptBuilder;
pub use prompt::PromptHandler;
pub use prompt::PromptRequest;
pub use protocol::CallToolResult;
pub use protocol::CompleteParams;
pub use protocol::CompleteResult;
pub use protocol::Completion;
pub use protocol::CompletionArgument;
pub use protocol::CompletionReference;
pub use protocol::CompletionsCapability;
pub use protocol::Content;
pub use protocol::ContentRole;
pub use protocol::CreateMessageParams;
pub use protocol::CreateMessageResult;
pub use protocol::ElicitAction;
pub use protocol::ElicitFieldValue;
pub use protocol::ElicitFormParams;
pub use protocol::ElicitFormSchema;
pub use protocol::ElicitMode;
pub use protocol::ElicitRequestParams;
pub use protocol::ElicitResult;
pub use protocol::ElicitUrlParams;
pub use protocol::ElicitationCapability;
pub use protocol::ElicitationCompleteParams;
pub use protocol::GetPromptResult;
pub use protocol::GetPromptResultBuilder;
pub use protocol::IncludeContext;
pub use protocol::JsonRpcMessage;
pub use protocol::JsonRpcRequest;
pub use protocol::JsonRpcResponse;
pub use protocol::JsonRpcResponseMessage;
pub use protocol::ListRootsParams;
pub use protocol::ListRootsResult;
pub use protocol::McpRequest;
pub use protocol::McpResponse;
pub use protocol::ModelHint;
pub use protocol::ModelPreferences;
pub use protocol::PrimitiveSchemaDefinition;
pub use protocol::PromptMessage;
pub use protocol::PromptReference;
pub use protocol::PromptRole;
pub use protocol::ReadResourceResult;
pub use protocol::ResourceContent;
pub use protocol::ResourceReference;
pub use protocol::Root;
pub use protocol::RootsCapability;
pub use protocol::SamplingCapability;
pub use protocol::SamplingContent;
pub use protocol::SamplingContentOrArray;
pub use protocol::SamplingMessage;
pub use protocol::SamplingTool;
pub use protocol::ToolChoice;
pub use resource::BoxResourceService;
pub use resource::Resource;
pub use resource::ResourceBuilder;
pub use resource::ResourceHandler;
pub use resource::ResourceRequest;
pub use resource::ResourceTemplate;
pub use resource::ResourceTemplateBuilder;
pub use resource::ResourceTemplateHandler;
pub use router::McpRouter;
pub use router::RouterRequest;
pub use router::RouterResponse;
pub use session::SessionPhase;
pub use session::SessionState;
pub use tool::BoxToolService;
pub use tool::NoParams;
pub use tool::Tool;
pub use tool::ToolBuilder;
pub use tool::ToolHandler;
pub use tool::ToolRequest;
pub use transport::BidirectionalStdioTransport;
pub use transport::CatchError;
pub use transport::GenericStdioTransport;
pub use transport::StdioTransport;
pub use transport::SyncStdioTransport;

Modules§

async_task
Async task management for long-running MCP operations
auth
Authentication middleware helpers for MCP servers
client
MCP Client implementation
context
Request context for MCP handlers
error
Error types for tower-mcp
extract
Extractor pattern for tool handlers
filter
Session-based capability filtering.
jsonrpc
JSON-RPC 2.0 service layer
prompt
Prompt definition and builder API
protocol
MCP protocol types based on JSON-RPC 2.0
resource
Resource definition and builder API
router
MCP Router - routes requests to tools, resources, and prompts
session
MCP session state management
tool
Tool definition and builder API
transport
MCP transport implementations