Skip to main content

tower_mcp/
lib.rs

1//! # tower-mcp
2//!
3//! Tower-native Model Context Protocol (MCP) implementation for Rust.
4//!
5//! This crate provides a composable, middleware-friendly approach to building
6//! MCP servers and clients using the [Tower](https://docs.rs/tower) service abstraction.
7//!
8//! ## Philosophy
9//!
10//! Unlike framework-style MCP implementations, tower-mcp treats MCP as just another
11//! protocol that can be served through Tower's `Service` trait. This means:
12//!
13//! - Standard tower middleware (tracing, metrics, rate limiting, auth) just works
14//! - Same service can be exposed over multiple transports (stdio, HTTP, WebSocket)
15//! - Easy integration with existing tower-based applications (axum, tonic, etc.)
16//!
17//! ## Familiar to axum Users
18//!
19//! If you've used [axum](https://docs.rs/axum), tower-mcp's API will feel familiar.
20//! We've adopted axum's patterns for a consistent Rust web ecosystem experience:
21//!
22//! - **Extractor pattern**: Tool handlers use extractors like [`extract::State<T>`],
23//!   [`extract::Json<T>`], and [`extract::Context`] - just like axum's request extractors
24//! - **Router composition**: [`McpRouter::merge()`] and [`McpRouter::nest()`] work like
25//!   axum's router methods for combining routers
26//! - **Per-route middleware**: Apply Tower layers to individual tools, resources, or
27//!   prompts via `.layer()` on builders
28//! - **Builder pattern**: Fluent builders for tools, resources, and prompts
29//!
30//! ```rust
31//! use std::sync::Arc;
32//! use tower_mcp::{ToolBuilder, CallToolResult};
33//! use tower_mcp::extract::{State, Json, Context};
34//! use schemars::JsonSchema;
35//! use serde::Deserialize;
36//!
37//! #[derive(Clone)]
38//! struct AppState { db_url: String }
39//!
40//! #[derive(Deserialize, JsonSchema)]
41//! struct SearchInput { query: String }
42//!
43//! // Looks just like an axum handler!
44//! let tool = ToolBuilder::new("search")
45//!     .description("Search the database")
46//!     .extractor_handler_typed::<_, _, _, SearchInput>(
47//!         Arc::new(AppState { db_url: "postgres://...".into() }),
48//!         |State(app): State<Arc<AppState>>,
49//!          ctx: Context,
50//!          Json(input): Json<SearchInput>| async move {
51//!             ctx.report_progress(0.5, Some(1.0), Some("Searching...")).await;
52//!             Ok(CallToolResult::text(format!("Found results for: {}", input.query)))
53//!         },
54//!     )
55//!     .build()
56//!     .unwrap();
57//! ```
58//!
59//! ## Quick Start: Server
60//!
61//! Build an MCP server with tools, resources, and prompts:
62//!
63//! ```rust,no_run
64//! use tower_mcp::{BoxError, McpRouter, ToolBuilder, CallToolResult, StdioTransport};
65//! use schemars::JsonSchema;
66//! use serde::Deserialize;
67//!
68//! #[derive(Debug, Deserialize, JsonSchema)]
69//! struct GreetInput {
70//!     name: String,
71//! }
72//!
73//! #[tokio::main]
74//! async fn main() -> Result<(), BoxError> {
75//!     // Define a tool
76//!     let greet = ToolBuilder::new("greet")
77//!         .description("Greet someone by name")
78//!         .handler(|input: GreetInput| async move {
79//!             Ok(CallToolResult::text(format!("Hello, {}!", input.name)))
80//!         })
81//!         .build()?;
82//!
83//!     // Create router and run over stdio
84//!     let router = McpRouter::new()
85//!         .server_info("my-server", "1.0.0")
86//!         .tool(greet);
87//!
88//!     StdioTransport::new(router).run().await?;
89//!     Ok(())
90//! }
91//! ```
92//!
93//! ## Quick Start: Client
94//!
95//! Connect to an MCP server and call tools:
96//!
97//! ```rust,no_run
98//! use tower_mcp::BoxError;
99//! use tower_mcp::client::{McpClient, StdioClientTransport};
100//!
101//! #[tokio::main]
102//! async fn main() -> Result<(), BoxError> {
103//!     // Connect to server
104//!     let transport = StdioClientTransport::spawn("my-mcp-server", &[]).await?;
105//!     let mut client = McpClient::new(transport);
106//!
107//!     // Initialize and list tools
108//!     client.initialize("my-client", "1.0.0").await?;
109//!     let tools = client.list_tools().await?;
110//!
111//!     // Call a tool
112//!     let result = client.call_tool("greet", serde_json::json!({"name": "World"})).await?;
113//!     println!("{:?}", result);
114//!
115//!     Ok(())
116//! }
117//! ```
118//!
119//! ## Key Types
120//!
121//! ### Server
122//! - [`McpRouter`] - Routes MCP requests to tools, resources, and prompts
123//! - [`ToolBuilder`] - Builder for defining tools with type-safe handlers
124//! - [`ResourceBuilder`] - Builder for defining resources
125//! - [`PromptBuilder`] - Builder for defining prompts
126//! - [`StdioTransport`] - Stdio transport for CLI servers
127//!
128//! ### Client
129//! - [`McpClient`] - Client for connecting to MCP servers
130//! - [`StdioClientTransport`] - Spawn and connect to server subprocesses
131//!
132//! ### Protocol
133//! - [`CallToolResult`] - Tool execution result with content
134//! - [`ReadResourceResult`] - Resource read result
135//! - [`GetPromptResult`] - Prompt expansion result
136//! - [`Content`] - Text, image, audio, or resource content
137//!
138//! ## Feature Flags
139//!
140//! - `full` - Enable all optional features
141//! - `http` - HTTP/SSE transport for web servers
142//! - `websocket` - WebSocket transport for bidirectional communication
143//! - `childproc` - Child process transport for subprocess management
144//! - `oauth` - OAuth 2.1 resource server support (token validation, metadata endpoint)
145//! - `testing` - Test utilities ([`TestClient`]) for ergonomic MCP server testing
146//!
147//! ## Middleware Placement Guide
148//!
149//! tower-mcp supports Tower middleware at multiple levels. Choose based on scope:
150//!
151//! | Level | Method | Scope | Use Cases |
152//! |-------|--------|-------|-----------|
153//! | **Transport** | `HttpTransport::layer()` | All MCP requests | Global timeout, rate limit, metrics |
154//! | **axum** | `.into_router().layer()` | HTTP layer only | CORS, compression, request logging |
155//! | **Per-tool** | `ToolBuilder::...layer()` | Single tool | Tool-specific timeout, concurrency |
156//! | **Per-resource** | `ResourceBuilder::...layer()` | Single resource | Caching, read timeout |
157//! | **Per-prompt** | `PromptBuilder::...layer()` | Single prompt | Generation timeout |
158//!
159//! ### Decision Tree
160//!
161//! ```text
162//! Where should my middleware go?
163//! │
164//! ├─ Affects ALL MCP requests?
165//! │  └─ Yes → Transport: HttpTransport::layer() or WebSocketTransport::layer()
166//! │
167//! ├─ HTTP-specific (CORS, compression, headers)?
168//! │  └─ Yes → axum: transport.into_router().layer(...)
169//! │
170//! ├─ Only one specific tool?
171//! │  └─ Yes → Per-tool: ToolBuilder::...handler(...).layer(...)
172//! │
173//! ├─ Only one specific resource?
174//! │  └─ Yes → Per-resource: ResourceBuilder::...handler(...).layer(...)
175//! │
176//! └─ Only one specific prompt?
177//!    └─ Yes → Per-prompt: PromptBuilder::...handler(...).layer(...)
178//! ```
179//!
180//! ### Example: Layered Timeouts
181//!
182//! ```rust,ignore
183//! use std::time::Duration;
184//! use tower::timeout::TimeoutLayer;
185//! use tower_mcp::{McpRouter, ToolBuilder, CallToolResult, HttpTransport};
186//! use schemars::JsonSchema;
187//! use serde::Deserialize;
188//!
189//! #[derive(Debug, Deserialize, JsonSchema)]
190//! struct SearchInput { query: String }
191//!
192//! // This tool gets a longer timeout than the global default
193//! let slow_search = ToolBuilder::new("slow_search")
194//!     .description("Thorough search (may take a while)")
195//!     .handler(|input: SearchInput| async move {
196//!         // ... slow operation ...
197//!         Ok(CallToolResult::text("results"))
198//!     })
199//!     .layer(TimeoutLayer::new(Duration::from_secs(60)))  // 60s for this tool
200//!     .build()
201//!     .unwrap();
202//!
203//! let router = McpRouter::new()
204//!     .server_info("example", "1.0.0")
205//!     .tool(slow_search);
206//!
207//! // Global 30s timeout for all OTHER requests
208//! let transport = HttpTransport::new(router)
209//!     .layer(TimeoutLayer::new(Duration::from_secs(30)));
210//! ```
211//!
212//! In this example:
213//! - `slow_search` tool has a 60-second timeout (per-tool layer)
214//! - All other MCP requests have a 30-second timeout (transport layer)
215//! - The per-tool layer is **inner** to the transport layer
216//!
217//! ### Layer Ordering
218//!
219//! Layers wrap from outside in. The first layer added is the outermost:
220//!
221//! ```text
222//! Request → [Transport Layer] → [Per-tool Layer] → Handler → Response
223//! ```
224//!
225//! For per-tool/resource/prompt, chained `.layer()` calls also wrap outside-in:
226//!
227//! ```rust,ignore
228//! ToolBuilder::new("api")
229//!     .handler(...)
230//!     .layer(TimeoutLayer::new(...))      // Outer: timeout checked first
231//!     .layer(ConcurrencyLimitLayer::new(5)) // Inner: concurrency after timeout
232//!     .build()
233//! ```
234//!
235//! ### Full Example
236//!
237//! See [`examples/tool_middleware.rs`](https://github.com/joshrotenberg/tower-mcp/blob/main/examples/tool_middleware.rs)
238//! for a complete example demonstrating:
239//! - Different timeouts per tool
240//! - Concurrency limiting for expensive operations
241//! - Multiple layers combined on a single tool
242//!
243//! ## MCP Specification
244//!
245//! This crate implements the MCP specification (2025-11-25):
246//! <https://modelcontextprotocol.io/specification/2025-11-25>
247
248pub mod async_task;
249pub mod auth;
250pub mod client;
251pub mod context;
252pub mod error;
253pub mod extract;
254pub mod filter;
255pub mod jsonrpc;
256#[cfg(feature = "oauth")]
257pub mod oauth;
258pub mod prompt;
259pub mod protocol;
260pub mod resource;
261pub mod router;
262pub mod session;
263#[cfg(feature = "testing")]
264pub mod testing;
265pub mod tool;
266pub mod tracing_layer;
267pub mod transport;
268
269// Re-exports
270pub use async_task::{Task, TaskStore};
271pub use client::{ClientTransport, McpClient, StdioClientTransport};
272pub use context::{
273    ChannelClientRequester, ClientRequester, ClientRequesterHandle, Extensions,
274    NotificationReceiver, NotificationSender, OutgoingRequest, OutgoingRequestReceiver,
275    OutgoingRequestSender, RequestContext, RequestContextBuilder, ServerNotification,
276    outgoing_request_channel,
277};
278pub use error::{BoxError, Error, Result, ToolError};
279pub use filter::{
280    CapabilityFilter, DenialBehavior, Filterable, PromptFilter, ResourceFilter, ToolFilter,
281};
282pub use jsonrpc::{JsonRpcLayer, JsonRpcService};
283pub use prompt::{BoxPromptService, Prompt, PromptBuilder, PromptHandler, PromptRequest};
284pub use protocol::{
285    CallToolResult, CompleteParams, CompleteResult, Completion, CompletionArgument,
286    CompletionReference, CompletionsCapability, Content, ContentRole, CreateMessageParams,
287    CreateMessageResult, ElicitAction, ElicitFieldValue, ElicitFormParams, ElicitFormSchema,
288    ElicitMode, ElicitRequestParams, ElicitResult, ElicitUrlParams, ElicitationCapability,
289    ElicitationCompleteParams, GetPromptResult, GetPromptResultBuilder, IncludeContext,
290    JsonRpcMessage, JsonRpcRequest, JsonRpcResponse, JsonRpcResponseMessage, ListRootsParams,
291    ListRootsResult, McpRequest, McpResponse, ModelHint, ModelPreferences,
292    PrimitiveSchemaDefinition, PromptMessage, PromptReference, PromptRole, ReadResourceResult,
293    ResourceContent, ResourceReference, Root, RootsCapability, SamplingCapability, SamplingContent,
294    SamplingContentOrArray, SamplingMessage, SamplingTool, ToolChoice,
295};
296pub use resource::{
297    BoxResourceService, Resource, ResourceBuilder, ResourceHandler, ResourceRequest,
298    ResourceTemplate, ResourceTemplateBuilder, ResourceTemplateHandler,
299};
300pub use router::{McpRouter, RouterRequest, RouterResponse};
301pub use session::{SessionPhase, SessionState};
302pub use tool::{BoxToolService, NoParams, Tool, ToolBuilder, ToolHandler, ToolRequest};
303pub use tracing_layer::{McpTracingLayer, McpTracingService};
304pub use transport::{
305    BidirectionalStdioTransport, CatchError, GenericStdioTransport, StdioTransport,
306    SyncStdioTransport,
307};
308
309#[cfg(feature = "http")]
310pub use transport::HttpTransport;
311
312#[cfg(feature = "websocket")]
313pub use transport::WebSocketTransport;
314
315#[cfg(any(feature = "http", feature = "websocket"))]
316pub use transport::McpBoxService;
317
318#[cfg(feature = "childproc")]
319pub use transport::{ChildProcessConnection, ChildProcessTransport};
320
321#[cfg(feature = "oauth")]
322pub use oauth::{ScopeEnforcementLayer, ScopeEnforcementService};
323
324#[cfg(feature = "jwks")]
325pub use oauth::{JwksError, JwksValidator, JwksValidatorBuilder};
326
327#[cfg(feature = "testing")]
328pub use testing::TestClient;