turbomcp_server/handler.rs
1//! Core handler trait for MCP servers.
2//!
3//! This module re-exports the unified `McpHandler` trait from `turbomcp-core`
4//! and provides the `McpHandlerExt` extension trait for native transport runners.
5//!
6//! # Unified Architecture
7//!
8//! The `McpHandler` trait is defined in `turbomcp-core` and works on both native
9//! and WASM targets. This module extends it with native-only transport methods.
10//!
11//! # Portable Code Pattern
12//!
13//! The TurboMCP architecture enables writing portable servers that work on both native
14//! and WASM without platform-specific code in your business logic:
15//!
16//! ```rust,ignore
17//! use turbomcp::prelude::*;
18//!
19//! #[derive(Clone)]
20//! struct Calculator;
21//!
22//! #[server(name = "calculator", version = "1.0.0")]
23//! impl Calculator {
24//! /// Add two numbers together
25//! #[tool]
26//! async fn add(
27//! &self,
28//! #[description("First number")] a: i64,
29//! #[description("Second number")] b: i64,
30//! ) -> i64 {
31//! a + b
32//! }
33//! }
34//!
35//! // Native entry point (STDIO by default)
36//! #[cfg(not(target_arch = "wasm32"))]
37//! #[tokio::main]
38//! async fn main() {
39//! Calculator.run().await.unwrap();
40//! }
41//!
42//! // WASM entry point (Cloudflare Workers)
43//! #[cfg(target_arch = "wasm32")]
44//! #[event(fetch)]
45//! async fn fetch(req: Request, _env: Env, _ctx: Context) -> Result<Response> {
46//! Calculator.handle_worker_request(req).await
47//! }
48//! ```
49//!
50//! Note: The server implementation (Calculator) is identical - only the entry
51//! point differs per platform.
52//!
53//! # Transport Architecture
54//!
55//! Transport implementations are in the `transport` module:
56//! - `transport::stdio` - STDIO (line-based JSON-RPC)
57//! - `transport::tcp` - TCP sockets (line-based JSON-RPC)
58//! - `transport::unix` - Unix domain sockets (line-based JSON-RPC)
59//! - `transport::http` - HTTP POST (Axum-based JSON-RPC)
60//! - `transport::websocket` - WebSocket (Axum-based bidirectional)
61//!
62//! All line-based transports share the `LineTransportRunner` abstraction.
63//!
64//! # Default Entry Point
65//!
66//! For the simplest possible server, just use `.run()`:
67//!
68//! ```rust,ignore
69//! #[tokio::main]
70//! async fn main() {
71//! MyServer.run().await.unwrap();
72//! }
73//! ```
74//!
75//! This uses STDIO transport, which is the MCP default and works with
76//! Claude Desktop and other MCP clients out of the box.
77
78use std::future::Future;
79
80use serde_json::Value;
81use turbomcp_core::error::{McpError, McpResult};
82
83// Re-export the unified McpHandler from core
84pub use turbomcp_core::handler::McpHandler;
85
86// Use the server's rich context for native transports
87use super::RequestContext;
88
89/// Extension trait for running McpHandler on various transports.
90///
91/// This trait provides simple, zero-config entry points for running MCP servers.
92/// For advanced configuration (rate limits, connection limits, etc.), use the
93/// builder pattern via `McpServerExt::builder()`.
94///
95/// # Design Philosophy
96///
97/// - **Simple**: `handler.run()` → runs with STDIO (Claude Desktop compatible)
98/// - **Direct transport**: `handler.run_http("...")` → specific transport, default config
99/// - **Configurable**: `handler.builder().transport(...).serve()` → full control
100///
101/// # Example
102///
103/// ```rust,ignore
104/// use turbomcp::prelude::*;
105///
106/// #[tokio::main]
107/// async fn main() {
108/// // Simplest: STDIO (default)
109/// MyServer.run().await?;
110///
111/// // Specific transport, default config
112/// MyServer.run_http("0.0.0.0:8080").await?;
113///
114/// // Full configuration via builder
115/// MyServer.builder()
116/// .transport(Transport::http("0.0.0.0:8080"))
117/// .with_rate_limit(100, Duration::from_secs(1))
118/// .serve()
119/// .await?;
120/// }
121/// ```
122pub trait McpHandlerExt: McpHandler {
123 /// Run with the default transport (STDIO).
124 ///
125 /// STDIO is the MCP standard transport, compatible with Claude Desktop
126 /// and other MCP clients. This is the recommended entry point for most servers.
127 #[cfg(feature = "stdio")]
128 fn run(&self) -> impl Future<Output = McpResult<()>> + Send;
129
130 /// Run on STDIO transport (explicit, equivalent to `run()`).
131 #[cfg(feature = "stdio")]
132 fn run_stdio(&self) -> impl Future<Output = McpResult<()>> + Send;
133
134 /// Run on HTTP transport (JSON-RPC over HTTP POST).
135 #[cfg(feature = "http")]
136 fn run_http(&self, addr: &str) -> impl Future<Output = McpResult<()>> + Send;
137
138 /// Run on WebSocket transport (bidirectional JSON-RPC).
139 #[cfg(feature = "websocket")]
140 fn run_websocket(&self, addr: &str) -> impl Future<Output = McpResult<()>> + Send;
141
142 /// Run on TCP transport (line-based JSON-RPC).
143 #[cfg(feature = "tcp")]
144 fn run_tcp(&self, addr: &str) -> impl Future<Output = McpResult<()>> + Send;
145
146 /// Run on Unix domain socket transport (line-based JSON-RPC).
147 #[cfg(feature = "unix")]
148 fn run_unix(&self, path: &str) -> impl Future<Output = McpResult<()>> + Send;
149
150 /// Handle a single JSON-RPC request (for serverless environments).
151 ///
152 /// Useful for AWS Lambda, Cloudflare Workers, and other serverless
153 /// environments where you process one request at a time.
154 fn handle_request(
155 &self,
156 request: Value,
157 ctx: RequestContext,
158 ) -> impl Future<Output = McpResult<Value>> + Send;
159}
160
161/// Blanket implementation of McpHandlerExt for all McpHandler types.
162///
163/// Each transport method delegates to the corresponding module in `super::transport`.
164impl<T: McpHandler> McpHandlerExt for T {
165 #[cfg(feature = "stdio")]
166 fn run(&self) -> impl Future<Output = McpResult<()>> + Send {
167 super::transport::stdio::run(self)
168 }
169
170 #[cfg(feature = "stdio")]
171 fn run_stdio(&self) -> impl Future<Output = McpResult<()>> + Send {
172 super::transport::stdio::run(self)
173 }
174
175 #[cfg(feature = "http")]
176 fn run_http(&self, addr: &str) -> impl Future<Output = McpResult<()>> + Send {
177 let addr = addr.to_string();
178 let handler = self.clone();
179 async move { super::transport::http::run(&handler, &addr).await }
180 }
181
182 #[cfg(feature = "websocket")]
183 fn run_websocket(&self, addr: &str) -> impl Future<Output = McpResult<()>> + Send {
184 let addr = addr.to_string();
185 let handler = self.clone();
186 async move { super::transport::websocket::run(&handler, &addr).await }
187 }
188
189 #[cfg(feature = "tcp")]
190 fn run_tcp(&self, addr: &str) -> impl Future<Output = McpResult<()>> + Send {
191 let addr = addr.to_string();
192 let handler = self.clone();
193 async move { super::transport::tcp::run(&handler, &addr).await }
194 }
195
196 #[cfg(feature = "unix")]
197 fn run_unix(&self, path: &str) -> impl Future<Output = McpResult<()>> + Send {
198 let path = path.to_string();
199 let handler = self.clone();
200 async move { super::transport::unix::run(&handler, &path).await }
201 }
202
203 fn handle_request(
204 &self,
205 request: Value,
206 ctx: RequestContext,
207 ) -> impl Future<Output = McpResult<Value>> + Send {
208 let handler = self.clone();
209 async move {
210 let request_str = serde_json::to_string(&request)
211 .map_err(|e| McpError::internal(format!("Failed to serialize request: {e}")))?;
212
213 let parsed = super::router::parse_request(&request_str)?;
214 let core_ctx = ctx.to_core_context();
215 let response = super::router::route_request(&handler, parsed, &core_ctx).await;
216
217 serde_json::to_value(&response)
218 .map_err(|e| McpError::internal(format!("Failed to serialize response: {e}")))
219 }
220 }
221}
222
223#[cfg(test)]
224mod tests {
225 use super::*;
226 use turbomcp_core::context::RequestContext as CoreRequestContext;
227 use turbomcp_types::{
228 Prompt, PromptResult, Resource, ResourceResult, ServerInfo, Tool, ToolResult,
229 };
230
231 #[derive(Clone)]
232 struct TestHandler;
233
234 impl McpHandler for TestHandler {
235 fn server_info(&self) -> ServerInfo {
236 ServerInfo::new("test", "1.0.0")
237 }
238
239 fn list_tools(&self) -> Vec<Tool> {
240 vec![Tool::new("test_tool", "A test tool")]
241 }
242
243 fn list_resources(&self) -> Vec<Resource> {
244 vec![]
245 }
246
247 fn list_prompts(&self) -> Vec<Prompt> {
248 vec![]
249 }
250
251 fn call_tool<'a>(
252 &'a self,
253 name: &'a str,
254 _args: Value,
255 _ctx: &'a CoreRequestContext,
256 ) -> impl std::future::Future<Output = McpResult<ToolResult>> + Send + 'a {
257 let name = name.to_string();
258 async move {
259 if name == "test_tool" {
260 Ok(ToolResult::text("Tool executed"))
261 } else {
262 Err(McpError::tool_not_found(&name))
263 }
264 }
265 }
266
267 fn read_resource<'a>(
268 &'a self,
269 uri: &'a str,
270 _ctx: &'a CoreRequestContext,
271 ) -> impl std::future::Future<Output = McpResult<ResourceResult>> + Send + 'a {
272 let uri = uri.to_string();
273 async move { Err(McpError::resource_not_found(&uri)) }
274 }
275
276 fn get_prompt<'a>(
277 &'a self,
278 name: &'a str,
279 _args: Option<Value>,
280 _ctx: &'a CoreRequestContext,
281 ) -> impl std::future::Future<Output = McpResult<PromptResult>> + Send + 'a {
282 let name = name.to_string();
283 async move { Err(McpError::prompt_not_found(&name)) }
284 }
285 }
286
287 #[tokio::test]
288 async fn test_handle_request() {
289 let handler = TestHandler;
290 let ctx = RequestContext::stdio();
291
292 let request = serde_json::json!({
293 "jsonrpc": "2.0",
294 "id": 1,
295 "method": "ping"
296 });
297
298 let response = handler.handle_request(request, ctx).await.unwrap();
299 assert!(response.get("result").is_some());
300 }
301
302 #[tokio::test]
303 async fn test_handle_request_tools_list() {
304 let handler = TestHandler;
305 let ctx = RequestContext::stdio();
306
307 let request = serde_json::json!({
308 "jsonrpc": "2.0",
309 "id": 1,
310 "method": "tools/list"
311 });
312
313 let response = handler.handle_request(request, ctx).await.unwrap();
314 let result = response.get("result").unwrap();
315 let tools = result.get("tools").unwrap().as_array().unwrap();
316 assert_eq!(tools.len(), 1);
317 assert_eq!(tools[0]["name"], "test_tool");
318 }
319}