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//! ## Advanced Features
244//!
245//! ### Sampling (LLM Requests)
246//!
247//! Tools can request LLM completions from the client via [`RequestContext::sample()`].
248//! This enables AI-assisted tools like "suggest a query" or "analyze results":
249//!
250//! ```rust,ignore
251//! use tower_mcp::{ToolBuilder, CallToolResult, CreateMessageParams, SamplingMessage};
252//! use tower_mcp::extract::Context;
253//!
254//! let tool = ToolBuilder::new("suggest")
255//!     .description("Get AI suggestions")
256//!     .extractor_handler(|ctx: Context| async move {
257//!         if !ctx.can_sample() {
258//!             return Ok(CallToolResult::error("Sampling not available"));
259//!         }
260//!
261//!         let params = CreateMessageParams::new()
262//!             .message(SamplingMessage::user("Suggest 3 search queries for: rust async"))
263//!             .max_tokens(200);
264//!
265//!         let result = ctx.sample(params).await?;
266//!         let text = result.first_text().unwrap_or("No response");
267//!         Ok(CallToolResult::text(text))
268//!     })
269//!     .build()?;
270//! ```
271//!
272//! ### Elicitation (User Input)
273//!
274//! Tools can request user input via forms using [`RequestContext::elicit_form()`]
275//! or the convenience method [`RequestContext::confirm()`]:
276//!
277//! ```rust,ignore
278//! use tower_mcp::{ToolBuilder, CallToolResult};
279//! use tower_mcp::extract::Context;
280//!
281//! // Simple confirmation dialog
282//! let delete_tool = ToolBuilder::new("delete")
283//!     .description("Delete a file")
284//!     .extractor_handler(|ctx: Context| async move {
285//!         if !ctx.confirm("Are you sure you want to delete this file?").await? {
286//!             return Ok(CallToolResult::text("Cancelled"));
287//!         }
288//!         // ... perform deletion ...
289//!         Ok(CallToolResult::text("Deleted"))
290//!     })
291//!     .build()?;
292//! ```
293//!
294//! For complex forms, use [`ElicitFormSchema`] to define multiple fields.
295//!
296//! ### Progress Notifications
297//!
298//! Long-running tools can report progress via [`RequestContext::report_progress()`]:
299//!
300//! ```rust,ignore
301//! use tower_mcp::{ToolBuilder, CallToolResult};
302//! use tower_mcp::extract::Context;
303//!
304//! let process_tool = ToolBuilder::new("process")
305//!     .description("Process items")
306//!     .extractor_handler(|ctx: Context| async move {
307//!         let items = vec!["a", "b", "c", "d", "e"];
308//!         let total = items.len() as f64;
309//!
310//!         for (i, item) in items.iter().enumerate() {
311//!             ctx.report_progress(i as f64, Some(total), Some(&format!("Processing {}", item))).await;
312//!             // ... process item ...
313//!         }
314//!
315//!         Ok(CallToolResult::text("Done"))
316//!     })
317//!     .build()?;
318//! ```
319//!
320//! ### Router Composition
321//!
322//! Combine multiple routers using [`McpRouter::merge()`] or [`McpRouter::nest()`]:
323//!
324//! ```rust,ignore
325//! use tower_mcp::McpRouter;
326//!
327//! // Create domain-specific routers
328//! let db_router = McpRouter::new()
329//!     .tool(query_tool)
330//!     .tool(insert_tool);
331//!
332//! let api_router = McpRouter::new()
333//!     .tool(fetch_tool);
334//!
335//! // Nest with prefixes: tools become "db.query", "db.insert", "api.fetch"
336//! let combined = McpRouter::new()
337//!     .server_info("combined", "1.0")
338//!     .nest("db", db_router)
339//!     .nest("api", api_router);
340//!
341//! // Or merge without prefixes
342//! let merged = McpRouter::new()
343//!     .merge(db_router)
344//!     .merge(api_router);
345//! ```
346//!
347//! ## MCP Specification
348//!
349//! This crate implements the MCP specification (2025-11-25):
350//! <https://modelcontextprotocol.io/specification/2025-11-25>
351
352pub mod async_task;
353pub mod auth;
354pub mod client;
355pub mod context;
356pub mod error;
357pub mod extract;
358pub mod filter;
359pub mod jsonrpc;
360#[cfg(feature = "oauth")]
361pub mod oauth;
362pub mod prompt;
363pub mod protocol;
364pub mod resource;
365pub mod router;
366pub mod session;
367#[cfg(feature = "testing")]
368pub mod testing;
369pub mod tool;
370pub mod tracing_layer;
371pub mod transport;
372
373// Re-exports
374pub use async_task::{Task, TaskStore};
375pub use client::{ClientTransport, McpClient, StdioClientTransport};
376pub use context::{
377    ChannelClientRequester, ClientRequester, ClientRequesterHandle, Extensions,
378    NotificationReceiver, NotificationSender, OutgoingRequest, OutgoingRequestReceiver,
379    OutgoingRequestSender, RequestContext, RequestContextBuilder, ServerNotification,
380    outgoing_request_channel,
381};
382pub use error::{BoxError, Error, Result, ToolError};
383pub use filter::{
384    CapabilityFilter, DenialBehavior, Filterable, PromptFilter, ResourceFilter, ToolFilter,
385};
386pub use jsonrpc::{JsonRpcLayer, JsonRpcService};
387pub use prompt::{BoxPromptService, Prompt, PromptBuilder, PromptHandler, PromptRequest};
388pub use protocol::{
389    CallToolResult, CompleteParams, CompleteResult, Completion, CompletionArgument,
390    CompletionReference, CompletionsCapability, Content, ContentRole, CreateMessageParams,
391    CreateMessageResult, ElicitAction, ElicitFieldValue, ElicitFormParams, ElicitFormSchema,
392    ElicitMode, ElicitRequestParams, ElicitResult, ElicitUrlParams, ElicitationCapability,
393    ElicitationCompleteParams, GetPromptResult, GetPromptResultBuilder, IncludeContext,
394    JsonRpcMessage, JsonRpcRequest, JsonRpcResponse, JsonRpcResponseMessage, ListRootsParams,
395    ListRootsResult, McpRequest, McpResponse, ModelHint, ModelPreferences,
396    PrimitiveSchemaDefinition, PromptMessage, PromptReference, PromptRole, ReadResourceResult,
397    ResourceContent, ResourceReference, Root, RootsCapability, SamplingCapability, SamplingContent,
398    SamplingContentOrArray, SamplingMessage, SamplingTool, ToolChoice,
399};
400pub use resource::{
401    BoxResourceService, Resource, ResourceBuilder, ResourceHandler, ResourceRequest,
402    ResourceTemplate, ResourceTemplateBuilder, ResourceTemplateHandler,
403};
404pub use router::{McpRouter, RouterRequest, RouterResponse};
405pub use session::{SessionPhase, SessionState};
406pub use tool::{BoxToolService, NoParams, Tool, ToolBuilder, ToolHandler, ToolRequest};
407pub use tracing_layer::{McpTracingLayer, McpTracingService};
408pub use transport::{
409    BidirectionalStdioTransport, CatchError, GenericStdioTransport, StdioTransport,
410    SyncStdioTransport,
411};
412
413#[cfg(feature = "http")]
414pub use transport::HttpTransport;
415
416#[cfg(feature = "websocket")]
417pub use transport::WebSocketTransport;
418
419#[cfg(any(feature = "http", feature = "websocket"))]
420pub use transport::McpBoxService;
421
422#[cfg(feature = "childproc")]
423pub use transport::{ChildProcessConnection, ChildProcessTransport};
424
425#[cfg(feature = "oauth")]
426pub use oauth::{ScopeEnforcementLayer, ScopeEnforcementService};
427
428#[cfg(feature = "jwks")]
429pub use oauth::{JwksError, JwksValidator, JwksValidatorBuilder};
430
431#[cfg(feature = "testing")]
432pub use testing::TestClient;