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;