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