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//!     .title("Search Database")
46//!     .description("Search the database")
47//!     .extractor_handler(
48//!         Arc::new(AppState { db_url: "postgres://...".into() }),
49//!         |State(app): State<Arc<AppState>>,
50//!          ctx: Context,
51//!          Json(input): Json<SearchInput>| async move {
52//!             ctx.report_progress(0.5, Some(1.0), Some("Searching...")).await;
53//!             Ok(CallToolResult::text(format!("Found results for: {}", input.query)))
54//!         },
55//!     )
56//!     .build();
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//!         .title("Greet")
78//!         .description("Greet someone by name")
79//!         .handler(|input: GreetInput| async move {
80//!             Ok(CallToolResult::text(format!("Hello, {}!", input.name)))
81//!         })
82//!         .build();
83//!
84//!     // Create router and run over stdio
85//!     let router = McpRouter::new()
86//!         .server_info("my-server", "1.0.0")
87//!         .tool(greet);
88//!
89//!     StdioTransport::new(router).run().await?;
90//!     Ok(())
91//! }
92//! ```
93//!
94//! ## Quick Start: Client
95//!
96//! Connect to an MCP server and call tools:
97//!
98//! ```rust,no_run
99//! use tower_mcp::BoxError;
100//! use tower_mcp::client::{McpClient, StdioClientTransport};
101//!
102//! #[tokio::main]
103//! async fn main() -> Result<(), BoxError> {
104//!     // Connect to server
105//!     let transport = StdioClientTransport::spawn("my-mcp-server", &[]).await?;
106//!     let client = McpClient::connect(transport).await?;
107//!
108//!     // Initialize and list tools
109//!     client.initialize("my-client", "1.0.0").await?;
110//!     let tools = client.list_tools().await?;
111//!
112//!     // Call a tool
113//!     let result = client.call_tool("greet", serde_json::json!({"name": "World"})).await?;
114//!     println!("{:?}", result);
115//!
116//!     Ok(())
117//! }
118//! ```
119//!
120//! ## Key Types
121//!
122//! ### Server
123//! - [`McpRouter`] - Routes MCP requests to tools, resources, and prompts
124//! - [`ToolBuilder`] - Builder for defining tools with type-safe handlers
125//! - [`ResourceBuilder`] - Builder for defining resources
126//! - [`PromptBuilder`] - Builder for defining prompts
127//! - [`StdioTransport`] - Stdio transport for CLI servers
128//!
129//! ### Client
130//! - [`McpClient`] - Client for connecting to MCP servers
131//! - [`StdioClientTransport`] - Spawn and connect to server subprocesses
132//!
133//! ### Protocol
134//! - [`CallToolResult`] - Tool execution result with content
135//! - [`ReadResourceResult`] - Resource read result
136//! - [`GetPromptResult`] - Prompt expansion result
137//! - [`Content`] - Text, image, audio, or resource content
138//!
139//! ## Feature Flags
140//!
141//! - `full` - Enable all optional features
142//! - `http` - HTTP/SSE transport for web servers
143//! - `websocket` - WebSocket transport for bidirectional communication
144//! - `childproc` - Child process transport for subprocess management
145//! - `oauth` - OAuth 2.1 resource server support (token validation, metadata endpoint)
146//! - `testing` - Test utilities (`TestClient`) for ergonomic MCP server testing
147//! - `dynamic-tools` - Runtime registration/deregistration of tools, prompts, and resources via
148//!   [`DynamicToolRegistry`], [`DynamicPromptRegistry`], [`DynamicResourceRegistry`],
149//!   [`DynamicResourceTemplateRegistry`]
150//! - `proxy` - Multi-server aggregation proxy ([`McpProxy`](proxy::McpProxy))
151//!
152//! ## Middleware Placement Guide
153//!
154//! tower-mcp supports Tower middleware at multiple levels. Choose based on scope:
155//!
156//! | Level | Method | Scope | Use Cases |
157//! |-------|--------|-------|-----------|
158//! | **Transport** | `StdioTransport::layer()`, `HttpTransport::layer()` | All MCP requests | Global timeout, rate limit, metrics |
159//! | **axum** | `.into_router().layer()` | HTTP layer only | CORS, compression, request logging |
160//! | **Per-tool** | `ToolBuilder::...layer()` | Single tool | Tool-specific timeout, concurrency |
161//! | **Per-resource** | `ResourceBuilder::...layer()` | Single resource | Caching, read timeout |
162//! | **Per-prompt** | `PromptBuilder::...layer()` | Single prompt | Generation timeout |
163//!
164//! ### Decision Tree
165//!
166//! ```text
167//! Where should my middleware go?
168//! │
169//! ├─ Affects ALL MCP requests?
170//! │  └─ Yes → Transport: StdioTransport::layer(), HttpTransport::layer(), or WebSocketTransport::layer()
171//! │
172//! ├─ HTTP-specific (CORS, compression, headers)?
173//! │  └─ Yes → axum: transport.into_router().layer(...)
174//! │
175//! ├─ Only one specific tool?
176//! │  └─ Yes → Per-tool: ToolBuilder::...handler(...).layer(...)
177//! │
178//! ├─ Only one specific resource?
179//! │  └─ Yes → Per-resource: ResourceBuilder::...handler(...).layer(...)
180//! │
181//! └─ Only one specific prompt?
182//!    └─ Yes → Per-prompt: PromptBuilder::...handler(...).layer(...)
183//! ```
184//!
185//! ### Example: Layered Timeouts
186//!
187//! ```rust,ignore
188//! use std::time::Duration;
189//! use tower::timeout::TimeoutLayer;
190//! use tower_mcp::{McpRouter, ToolBuilder, CallToolResult, HttpTransport};
191//! use schemars::JsonSchema;
192//! use serde::Deserialize;
193//!
194//! #[derive(Debug, Deserialize, JsonSchema)]
195//! struct SearchInput { query: String }
196//!
197//! // This tool gets a longer timeout than the global default
198//! let slow_search = ToolBuilder::new("slow_search")
199//!     .description("Thorough search (may take a while)")
200//!     .handler(|input: SearchInput| async move {
201//!         // ... slow operation ...
202//!         Ok(CallToolResult::text("results"))
203//!     })
204//!     .layer(TimeoutLayer::new(Duration::from_secs(60)))  // 60s for this tool
205//!     .build();
206//!
207//! let router = McpRouter::new()
208//!     .server_info("example", "1.0.0")
209//!     .tool(slow_search);
210//!
211//! // Global 30s timeout for all OTHER requests
212//! let transport = HttpTransport::new(router)
213//!     .layer(TimeoutLayer::new(Duration::from_secs(30)));
214//! ```
215//!
216//! In this example:
217//! - `slow_search` tool has a 60-second timeout (per-tool layer)
218//! - All other MCP requests have a 30-second timeout (transport layer)
219//! - The per-tool layer is **inner** to the transport layer
220//!
221//! ### Layer Ordering
222//!
223//! Layers wrap from outside in. The first layer added is the outermost:
224//!
225//! ```text
226//! Request → [Transport Layer] → [Per-tool Layer] → Handler → Response
227//! ```
228//!
229//! For per-tool/resource/prompt, chained `.layer()` calls also wrap outside-in:
230//!
231//! ```rust,ignore
232//! ToolBuilder::new("api")
233//!     .handler(...)
234//!     .layer(TimeoutLayer::new(...))      // Outer: timeout checked first
235//!     .layer(ConcurrencyLimitLayer::new(5)) // Inner: concurrency after timeout
236//!     .build()
237//! ```
238//!
239//! ### Full Example
240//!
241//! See [`examples/tool_middleware.rs`](https://github.com/joshrotenberg/tower-mcp/blob/main/examples/tool_middleware.rs)
242//! for a complete example demonstrating:
243//! - Different timeouts per tool
244//! - Concurrency limiting for expensive operations
245//! - Multiple layers combined on a single tool
246//!
247//! ## Advanced Features
248//!
249//! ### Sampling (LLM Requests)
250//!
251//! Tools can request LLM completions from the client via [`RequestContext::sample()`].
252//! This enables AI-assisted tools like "suggest a query" or "analyze results":
253//!
254//! ```rust,ignore
255//! use tower_mcp::{ToolBuilder, CallToolResult, CreateMessageParams, SamplingMessage};
256//! use tower_mcp::extract::Context;
257//!
258//! let tool = ToolBuilder::new("suggest")
259//!     .description("Get AI suggestions")
260//!     .extractor_handler(|ctx: Context| async move {
261//!         if !ctx.can_sample() {
262//!             return Ok(CallToolResult::error("Sampling not available"));
263//!         }
264//!
265//!         let params = CreateMessageParams::new()
266//!             .message(SamplingMessage::user("Suggest 3 search queries for: rust async"))
267//!             .max_tokens(200);
268//!
269//!         let result = ctx.sample(params).await?;
270//!         let text = result.first_text().unwrap_or("No response");
271//!         Ok(CallToolResult::text(text))
272//!     })
273//!     .build();
274//! ```
275//!
276//! ### Elicitation (User Input)
277//!
278//! Tools can request user input via forms using [`RequestContext::elicit_form()`]
279//! or the convenience method [`RequestContext::confirm()`]:
280//!
281//! ```rust,ignore
282//! use tower_mcp::{ToolBuilder, CallToolResult};
283//! use tower_mcp::extract::Context;
284//!
285//! // Simple confirmation dialog
286//! let delete_tool = ToolBuilder::new("delete")
287//!     .description("Delete a file")
288//!     .extractor_handler(|ctx: Context| async move {
289//!         if !ctx.confirm("Are you sure you want to delete this file?").await? {
290//!             return Ok(CallToolResult::text("Cancelled"));
291//!         }
292//!         // ... perform deletion ...
293//!         Ok(CallToolResult::text("Deleted"))
294//!     })
295//!     .build();
296//! ```
297//!
298//! For complex forms, use [`ElicitFormSchema`] to define multiple fields.
299//!
300//! ### Progress Notifications
301//!
302//! Long-running tools can report progress via [`RequestContext::report_progress()`]:
303//!
304//! ```rust,ignore
305//! use tower_mcp::{ToolBuilder, CallToolResult};
306//! use tower_mcp::extract::Context;
307//!
308//! let process_tool = ToolBuilder::new("process")
309//!     .description("Process items")
310//!     .extractor_handler(|ctx: Context| async move {
311//!         let items = vec!["a", "b", "c", "d", "e"];
312//!         let total = items.len() as f64;
313//!
314//!         for (i, item) in items.iter().enumerate() {
315//!             ctx.report_progress(i as f64, Some(total), Some(&format!("Processing {}", item))).await;
316//!             // ... process item ...
317//!         }
318//!
319//!         Ok(CallToolResult::text("Done"))
320//!     })
321//!     .build();
322//! ```
323//!
324//! ### Router Composition
325//!
326//! Combine multiple routers using [`McpRouter::merge()`] or [`McpRouter::nest()`]:
327//!
328//! ```rust,ignore
329//! use tower_mcp::McpRouter;
330//!
331//! // Create domain-specific routers
332//! let db_router = McpRouter::new()
333//!     .tool(query_tool)
334//!     .tool(insert_tool);
335//!
336//! let api_router = McpRouter::new()
337//!     .tool(fetch_tool);
338//!
339//! // Nest with prefixes: tools become "db.query", "db.insert", "api.fetch"
340//! let combined = McpRouter::new()
341//!     .server_info("combined", "1.0")
342//!     .nest("db", db_router)
343//!     .nest("api", api_router);
344//!
345//! // Or merge without prefixes
346//! let merged = McpRouter::new()
347//!     .merge(db_router)
348//!     .merge(api_router);
349//! ```
350//!
351//! ### Multi-Server Proxy
352//!
353//! Aggregate multiple backend MCP servers behind a single endpoint using
354//! [`McpProxy`](proxy::McpProxy) (requires the `proxy` feature):
355//!
356//! ```rust,ignore
357//! use tower_mcp::proxy::McpProxy;
358//! use tower_mcp::client::StdioClientTransport;
359//!
360//! let proxy = McpProxy::builder("my-proxy", "1.0.0")
361//!     .backend("db", StdioClientTransport::spawn("db-server", &[]).await?)
362//!     .await
363//!     .backend("fs", StdioClientTransport::spawn("fs-server", &[]).await?)
364//!     .await
365//!     .build()
366//!     .await?;
367//!
368//! // Tools become `db_query`, `fs_read`, etc.
369//! // Serve over any transport -- stdio, HTTP, WebSocket.
370//! GenericStdioTransport::new(proxy).run().await?;
371//! ```
372//!
373//! The proxy supports per-backend Tower middleware, notification forwarding,
374//! health checks, and request coalescing. See the [`proxy`] module for details.
375//!
376//! ## MCP Specification
377//!
378//! This crate implements the MCP specification (2025-11-25):
379//! <https://modelcontextprotocol.io/specification/2025-11-25>
380
381pub mod async_task;
382pub mod auth;
383pub mod client;
384pub mod context;
385pub mod error;
386pub mod extract;
387pub mod filter;
388pub mod jsonrpc;
389pub mod middleware;
390#[cfg(feature = "oauth")]
391pub mod oauth;
392pub mod prompt;
393pub mod protocol;
394#[cfg(feature = "proxy")]
395pub mod proxy;
396#[cfg(feature = "dynamic-tools")]
397pub mod registry;
398pub mod resource;
399pub mod router;
400pub mod session;
401#[cfg(feature = "testing")]
402pub mod testing;
403pub mod tool;
404pub mod tracing_layer;
405pub mod transport;
406
407// Re-export proc macros when the `macros` feature is enabled
408#[cfg(feature = "macros")]
409pub use tower_mcp_macros::prompt_fn;
410#[cfg(feature = "macros")]
411pub use tower_mcp_macros::resource_fn;
412#[cfg(feature = "macros")]
413pub use tower_mcp_macros::resource_template_fn;
414#[cfg(feature = "macros")]
415pub use tower_mcp_macros::tool_fn;
416
417// Re-exports
418pub use async_task::{Task, TaskStore};
419pub use client::{
420    ChannelTransport, ClientHandler, ClientTransport, McpClient, McpClientBuilder,
421    NotificationHandler, StdioClientTransport,
422};
423#[cfg(feature = "http-client")]
424pub use client::{HttpClientConfig, HttpClientTransport};
425#[cfg(feature = "oauth-client")]
426pub use client::{OAuthClientCredentials, OAuthClientError, TokenProvider};
427pub use context::{
428    ChannelClientRequester, ClientRequester, ClientRequesterHandle, Extensions,
429    NotificationReceiver, NotificationSender, OutgoingRequest, OutgoingRequestReceiver,
430    OutgoingRequestSender, RequestContext, RequestContextBuilder, ServerNotification,
431    outgoing_request_channel,
432};
433pub use error::{BoxError, Error, Result, ResultExt, ToolError};
434pub use filter::{
435    CapabilityFilter, DenialBehavior, Filterable, PromptFilter, ResourceFilter, ToolFilter,
436};
437pub use jsonrpc::{JsonRpcLayer, JsonRpcService};
438pub use middleware::{
439    AuditLayer, AuditService, McpTracingLayer, McpTracingService, ToolCallLoggingLayer,
440    ToolCallLoggingService,
441};
442pub use prompt::{BoxPromptService, Prompt, PromptBuilder, PromptHandler, PromptRequest};
443#[allow(deprecated)]
444pub use protocol::{
445    BooleanSchema, CallToolParams, CallToolResult, CancelTaskParams, CancelledParams,
446    ClientCapabilities, ClientTasksCancelCapability, ClientTasksCapability,
447    ClientTasksElicitationCapability, ClientTasksElicitationCreateCapability,
448    ClientTasksListCapability, ClientTasksRequestsCapability, ClientTasksSamplingCapability,
449    ClientTasksSamplingCreateMessageCapability, CompleteParams, CompleteResult, Completion,
450    CompletionArgument, CompletionContext, CompletionReference, CompletionsCapability, Content,
451    ContentAnnotations, ContentRole, CreateMessageParams, CreateMessageResult, CreateTaskResult,
452    ElicitAction, ElicitFieldValue, ElicitFormParams, ElicitFormSchema, ElicitMode,
453    ElicitRequestParams, ElicitResult, ElicitUrlParams, ElicitationCapability,
454    ElicitationCompleteParams, ElicitationFormCapability, ElicitationUrlCapability, EmptyResult,
455    GetPromptParams, GetPromptResult, GetPromptResultBuilder, GetTaskInfoParams,
456    GetTaskResultParams, IconTheme, Implementation, IncludeContext, InitializeParams,
457    InitializeResult, IntegerSchema, JsonRpcErrorResponse, JsonRpcMessage, JsonRpcNotification,
458    JsonRpcRequest, JsonRpcResponse, JsonRpcResponseMessage, JsonRpcResultResponse,
459    ListPromptsParams, ListPromptsResult, ListResourceTemplatesParams, ListResourceTemplatesResult,
460    ListResourcesParams, ListResourcesResult, ListRootsParams, ListRootsResult, ListTasksParams,
461    ListTasksResult, ListToolsParams, ListToolsResult, LogLevel, LoggingCapability,
462    LoggingMessageParams, McpNotification, McpRequest, McpResponse, ModelHint, ModelPreferences,
463    MultiSelectEnumItems, MultiSelectEnumSchema, NumberSchema, PrimitiveSchemaDefinition,
464    ProgressParams, ProgressToken, PromptArgument, PromptDefinition, PromptMessage,
465    PromptReference, PromptRole, PromptsCapability, ReadResourceParams, ReadResourceResult,
466    RequestId, RequestMeta, ResourceContent, ResourceDefinition, ResourceReference,
467    ResourceTemplateDefinition, ResourcesCapability, Root, RootsCapability, SamplingCapability,
468    SamplingContent, SamplingContentOrArray, SamplingContextCapability, SamplingMessage,
469    SamplingTool, SamplingToolsCapability, ServerCapabilities, SetLogLevelParams,
470    SingleSelectEnumSchema, StringSchema, SubscribeResourceParams, TaskInfo, TaskObject,
471    TaskRequestParams, TaskStatus, TaskStatusChangedParams, TaskStatusParams, TaskSupportMode,
472    TasksCancelCapability, TasksCapability, TasksListCapability, TasksRequestsCapability,
473    TasksToolsCallCapability, TasksToolsRequestsCapability, ToolAnnotations, ToolChoice,
474    ToolDefinition, ToolExecution, ToolIcon, ToolsCapability, UnsubscribeResourceParams,
475};
476#[cfg(feature = "dynamic-tools")]
477pub use registry::{
478    DynamicPromptRegistry, DynamicResourceRegistry, DynamicResourceTemplateRegistry,
479    DynamicToolRegistry,
480};
481pub use resource::{
482    BoxResourceService, Resource, ResourceBuilder, ResourceHandler, ResourceRequest,
483    ResourceTemplate, ResourceTemplateBuilder, ResourceTemplateHandler,
484};
485pub use router::{McpRouter, RouterRequest, RouterResponse, ToolAnnotationsMap};
486pub use session::{SessionPhase, SessionState};
487pub use tool::{BoxToolService, GuardLayer, NoParams, Tool, ToolBuilder, ToolHandler, ToolRequest};
488pub use transport::{
489    BidirectionalStdioTransport, CatchError, GenericStdioTransport, StdioTransport,
490    SyncStdioTransport,
491};
492
493#[cfg(feature = "http")]
494pub use transport::{HttpTransport, SessionHandle};
495
496#[cfg(feature = "websocket")]
497pub use transport::WebSocketTransport;
498
499#[cfg(any(feature = "http", feature = "websocket"))]
500pub use transport::McpBoxService;
501
502#[cfg(feature = "childproc")]
503pub use transport::{ChildProcessConnection, ChildProcessTransport};
504
505#[cfg(feature = "oauth")]
506pub use oauth::{ScopeEnforcementLayer, ScopeEnforcementService};
507
508#[cfg(feature = "jwks")]
509pub use oauth::{JwksError, JwksValidator, JwksValidatorBuilder};
510
511#[cfg(feature = "testing")]
512pub use testing::TestClient;