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 transport;
267
268// Re-exports
269pub use async_task::{Task, TaskStore};
270pub use client::{ClientTransport, McpClient, StdioClientTransport};
271pub use context::{
272 ChannelClientRequester, ClientRequester, ClientRequesterHandle, Extensions,
273 NotificationReceiver, NotificationSender, OutgoingRequest, OutgoingRequestReceiver,
274 OutgoingRequestSender, RequestContext, RequestContextBuilder, ServerNotification,
275 outgoing_request_channel,
276};
277pub use error::{BoxError, Error, Result, ToolError};
278pub use filter::{
279 CapabilityFilter, DenialBehavior, Filterable, PromptFilter, ResourceFilter, ToolFilter,
280};
281pub use jsonrpc::{JsonRpcLayer, JsonRpcService};
282pub use prompt::{BoxPromptService, Prompt, PromptBuilder, PromptHandler, PromptRequest};
283pub use protocol::{
284 CallToolResult, CompleteParams, CompleteResult, Completion, CompletionArgument,
285 CompletionReference, CompletionsCapability, Content, ContentRole, CreateMessageParams,
286 CreateMessageResult, ElicitAction, ElicitFieldValue, ElicitFormParams, ElicitFormSchema,
287 ElicitMode, ElicitRequestParams, ElicitResult, ElicitUrlParams, ElicitationCapability,
288 ElicitationCompleteParams, GetPromptResult, GetPromptResultBuilder, IncludeContext,
289 JsonRpcMessage, JsonRpcRequest, JsonRpcResponse, JsonRpcResponseMessage, ListRootsParams,
290 ListRootsResult, McpRequest, McpResponse, ModelHint, ModelPreferences,
291 PrimitiveSchemaDefinition, PromptMessage, PromptReference, PromptRole, ReadResourceResult,
292 ResourceContent, ResourceReference, Root, RootsCapability, SamplingCapability, SamplingContent,
293 SamplingContentOrArray, SamplingMessage, SamplingTool, ToolChoice,
294};
295pub use resource::{
296 BoxResourceService, Resource, ResourceBuilder, ResourceHandler, ResourceRequest,
297 ResourceTemplate, ResourceTemplateBuilder, ResourceTemplateHandler,
298};
299pub use router::{McpRouter, RouterRequest, RouterResponse};
300pub use session::{SessionPhase, SessionState};
301pub use tool::{BoxToolService, NoParams, Tool, ToolBuilder, ToolHandler, ToolRequest};
302pub use transport::{
303 BidirectionalStdioTransport, CatchError, GenericStdioTransport, StdioTransport,
304 SyncStdioTransport,
305};
306
307#[cfg(feature = "http")]
308pub use transport::HttpTransport;
309
310#[cfg(feature = "websocket")]
311pub use transport::WebSocketTransport;
312
313#[cfg(any(feature = "http", feature = "websocket"))]
314pub use transport::McpBoxService;
315
316#[cfg(feature = "childproc")]
317pub use transport::{ChildProcessConnection, ChildProcessTransport};
318
319#[cfg(feature = "oauth")]
320pub use oauth::{ScopeEnforcementLayer, ScopeEnforcementService};
321
322#[cfg(feature = "jwks")]
323pub use oauth::{JwksError, JwksValidator, JwksValidatorBuilder};
324
325#[cfg(feature = "testing")]
326pub use testing::TestClient;