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