1use alloc::string::ToString;
30use serde_json::Value;
31
32use crate::PROTOCOL_VERSION;
33use crate::context::RequestContext;
34use crate::error::McpError;
35use crate::handler::McpHandler;
36use crate::jsonrpc::{JsonRpcIncoming, JsonRpcOutgoing};
37use turbomcp_types::ServerInfo;
38
39#[derive(Debug, Clone, Default)]
44pub struct RouteConfig<'a> {
45 pub protocol_version: Option<&'a str>,
48}
49
50pub async fn route_request<H: McpHandler>(
80 handler: &H,
81 request: JsonRpcIncoming,
82 ctx: &RequestContext,
83 config: &RouteConfig<'_>,
84) -> JsonRpcOutgoing {
85 let id = request.id.clone();
86
87 match request.method.as_str() {
88 "initialize" => {
90 let params = request.params.clone().unwrap_or_default();
91
92 let Some(client_info) = params.get("clientInfo") else {
94 return JsonRpcOutgoing::error(
95 id,
96 McpError::invalid_params("Missing required field: clientInfo"),
97 );
98 };
99
100 let client_name = client_info.get("name").and_then(|v| v.as_str());
102 let client_version = client_info.get("version").and_then(|v| v.as_str());
103 if client_name.is_none() || client_version.is_none() {
104 return JsonRpcOutgoing::error(
105 id,
106 McpError::invalid_params("clientInfo must contain 'name' and 'version' fields"),
107 );
108 }
109
110 let protocol_version = config.protocol_version.unwrap_or(PROTOCOL_VERSION);
111 let info = handler.server_info();
112 let result = build_initialize_result(&info, handler, protocol_version);
113 JsonRpcOutgoing::success(id, result)
114 }
115
116 "initialized" | "notifications/initialized" => {
119 if id.is_some() {
120 JsonRpcOutgoing::success(id, serde_json::json!({}))
121 } else {
122 JsonRpcOutgoing::notification_ack()
123 }
124 }
125
126 "tools/list" => {
128 let tools = handler.list_tools();
129 let result = serde_json::json!({ "tools": tools });
130 JsonRpcOutgoing::success(id, result)
131 }
132
133 "tools/call" => {
134 let params = request.params.unwrap_or_default();
135 let name = params
136 .get("name")
137 .and_then(|v| v.as_str())
138 .unwrap_or_default();
139 let args = params.get("arguments").cloned().unwrap_or_default();
140
141 match handler.call_tool(name, args, ctx).await {
142 Ok(result) => match serde_json::to_value(&result) {
143 Ok(result_value) => JsonRpcOutgoing::success(id, result_value),
144 Err(e) => JsonRpcOutgoing::error(
145 id,
146 McpError::internal(alloc::format!(
147 "Failed to serialize tool result: {}",
148 e
149 )),
150 ),
151 },
152 Err(err) => JsonRpcOutgoing::error(id, err),
153 }
154 }
155
156 "resources/list" => {
158 let resources = handler.list_resources();
159 let result = serde_json::json!({ "resources": resources });
160 JsonRpcOutgoing::success(id, result)
161 }
162
163 "resources/read" => {
164 let params = request.params.unwrap_or_default();
165 let uri = params
166 .get("uri")
167 .and_then(|v| v.as_str())
168 .unwrap_or_default();
169
170 match handler.read_resource(uri, ctx).await {
171 Ok(result) => match serde_json::to_value(&result) {
172 Ok(result_value) => JsonRpcOutgoing::success(id, result_value),
173 Err(e) => JsonRpcOutgoing::error(
174 id,
175 McpError::internal(alloc::format!(
176 "Failed to serialize resource result: {}",
177 e
178 )),
179 ),
180 },
181 Err(err) => JsonRpcOutgoing::error(id, err),
182 }
183 }
184
185 "prompts/list" => {
187 let prompts = handler.list_prompts();
188 let result = serde_json::json!({ "prompts": prompts });
189 JsonRpcOutgoing::success(id, result)
190 }
191
192 "prompts/get" => {
193 let params = request.params.unwrap_or_default();
194 let name = params
195 .get("name")
196 .and_then(|v| v.as_str())
197 .unwrap_or_default();
198 let args = params.get("arguments").cloned();
199
200 match handler.get_prompt(name, args, ctx).await {
201 Ok(result) => match serde_json::to_value(&result) {
202 Ok(result_value) => JsonRpcOutgoing::success(id, result_value),
203 Err(e) => JsonRpcOutgoing::error(
204 id,
205 McpError::internal(alloc::format!(
206 "Failed to serialize prompt result: {}",
207 e
208 )),
209 ),
210 },
211 Err(err) => JsonRpcOutgoing::error(id, err),
212 }
213 }
214
215 "tasks/list" => {
217 let params = request.params.unwrap_or_default();
218 let cursor = params.get("cursor").and_then(|v| v.as_str());
219 let limit = params
220 .get("limit")
221 .and_then(|v| v.as_u64())
222 .map(|v| v as usize);
223
224 match handler.list_tasks(cursor, limit, ctx).await {
225 Ok(result) => match serde_json::to_value(&result) {
226 Ok(v) => JsonRpcOutgoing::success(id, v),
227 Err(e) => JsonRpcOutgoing::error(id, McpError::internal(e.to_string())),
228 },
229 Err(err) => JsonRpcOutgoing::error(id, err),
230 }
231 }
232
233 "tasks/get" => {
234 let params = request.params.unwrap_or_default();
235 let Some(task_id) = params.get("taskId").and_then(|v| v.as_str()) else {
236 return JsonRpcOutgoing::error(id, McpError::invalid_params("Missing taskId"));
237 };
238
239 match handler.get_task(task_id, ctx).await {
240 Ok(result) => match serde_json::to_value(&result) {
241 Ok(v) => JsonRpcOutgoing::success(id, v),
242 Err(e) => JsonRpcOutgoing::error(id, McpError::internal(e.to_string())),
243 },
244 Err(err) => JsonRpcOutgoing::error(id, err),
245 }
246 }
247
248 "tasks/cancel" => {
249 let params = request.params.unwrap_or_default();
250 let Some(task_id) = params.get("taskId").and_then(|v| v.as_str()) else {
251 return JsonRpcOutgoing::error(id, McpError::invalid_params("Missing taskId"));
252 };
253
254 match handler.cancel_task(task_id, ctx).await {
255 Ok(result) => match serde_json::to_value(&result) {
256 Ok(v) => JsonRpcOutgoing::success(id, v),
257 Err(e) => JsonRpcOutgoing::error(id, McpError::internal(e.to_string())),
258 },
259 Err(err) => JsonRpcOutgoing::error(id, err),
260 }
261 }
262
263 "tasks/result" => {
264 let params = request.params.unwrap_or_default();
265 let Some(task_id) = params.get("taskId").and_then(|v| v.as_str()) else {
266 return JsonRpcOutgoing::error(id, McpError::invalid_params("Missing taskId"));
267 };
268
269 match handler.get_task_result(task_id, ctx).await {
270 Ok(result) => JsonRpcOutgoing::success(id, result),
271 Err(err) => JsonRpcOutgoing::error(id, err),
272 }
273 }
274
275 "ping" => JsonRpcOutgoing::success(id, serde_json::json!({})),
277
278 _ => JsonRpcOutgoing::error(id, McpError::method_not_found(&request.method)),
280 }
281}
282
283fn build_initialize_result<H: McpHandler>(
292 info: &ServerInfo,
293 handler: &H,
294 protocol_version: &str,
295) -> Value {
296 let has_tools = !handler.list_tools().is_empty();
297 let has_resources = !handler.list_resources().is_empty();
298 let has_prompts = !handler.list_prompts().is_empty();
299
300 let mut capabilities = serde_json::Map::new();
302
303 if has_tools {
304 capabilities.insert(
305 "tools".to_string(),
306 serde_json::json!({ "listChanged": true }),
307 );
308 }
309
310 if has_resources {
311 capabilities.insert(
312 "resources".to_string(),
313 serde_json::json!({ "listChanged": true }),
314 );
315 }
316
317 if has_prompts {
318 capabilities.insert(
319 "prompts".to_string(),
320 serde_json::json!({ "listChanged": true }),
321 );
322 }
323
324 let mut server_info = serde_json::Map::new();
338 server_info.insert("name".to_string(), serde_json::json!(info.name));
339 server_info.insert("version".to_string(), serde_json::json!(info.version));
340
341 let mut result = serde_json::Map::new();
343 result.insert(
344 "protocolVersion".to_string(),
345 serde_json::json!(protocol_version),
346 );
347 result.insert("capabilities".to_string(), Value::Object(capabilities));
348 result.insert("serverInfo".to_string(), Value::Object(server_info));
349
350 Value::Object(result)
351}
352
353pub fn parse_request(input: &str) -> Result<JsonRpcIncoming, McpError> {
357 JsonRpcIncoming::parse(input).map_err(|e| McpError::parse_error(e.to_string()))
358}
359
360pub fn serialize_response(response: &JsonRpcOutgoing) -> Result<alloc::string::String, McpError> {
364 response
365 .to_json()
366 .map_err(|e| McpError::internal(e.to_string()))
367}
368
369#[cfg(test)]
370mod tests {
371 use super::*;
372 use crate::error::McpResult;
373 use crate::marker::MaybeSend;
374 use core::future::Future;
375 use turbomcp_types::{Prompt, PromptResult, Resource, ResourceResult, Tool, ToolResult};
376
377 #[derive(Clone)]
378 struct TestHandler;
379
380 impl McpHandler for TestHandler {
381 fn server_info(&self) -> ServerInfo {
382 ServerInfo::new("test-router", "1.0.0")
383 }
384
385 fn list_tools(&self) -> Vec<Tool> {
386 vec![Tool::new("greet", "Say hello")]
387 }
388
389 fn list_resources(&self) -> Vec<Resource> {
390 vec![]
391 }
392
393 fn list_prompts(&self) -> Vec<Prompt> {
394 vec![]
395 }
396
397 fn call_tool<'a>(
398 &'a self,
399 name: &'a str,
400 args: Value,
401 _ctx: &'a RequestContext,
402 ) -> impl Future<Output = McpResult<ToolResult>> + MaybeSend + 'a {
403 let name = name.to_string();
404 async move {
405 match name.as_str() {
406 "greet" => {
407 let who = args.get("name").and_then(|v| v.as_str()).unwrap_or("World");
408 Ok(ToolResult::text(alloc::format!("Hello, {}!", who)))
409 }
410 _ => Err(McpError::tool_not_found(&name)),
411 }
412 }
413 }
414
415 fn read_resource<'a>(
416 &'a self,
417 uri: &'a str,
418 _ctx: &'a RequestContext,
419 ) -> impl Future<Output = McpResult<ResourceResult>> + MaybeSend + 'a {
420 let uri = uri.to_string();
421 async move { Err(McpError::resource_not_found(&uri)) }
422 }
423
424 fn get_prompt<'a>(
425 &'a self,
426 name: &'a str,
427 _args: Option<Value>,
428 _ctx: &'a RequestContext,
429 ) -> impl Future<Output = McpResult<PromptResult>> + MaybeSend + 'a {
430 let name = name.to_string();
431 async move { Err(McpError::prompt_not_found(&name)) }
432 }
433 }
434
435 #[test]
436 fn test_parse_request() {
437 let input = r#"{"jsonrpc": "2.0", "id": 1, "method": "ping"}"#;
438 let request = parse_request(input).unwrap();
439 assert_eq!(request.method, "ping");
440 assert_eq!(request.id, Some(serde_json::json!(1)));
441 }
442
443 #[test]
444 fn test_serialize_response() {
445 let response = JsonRpcOutgoing::success(Some(serde_json::json!(1)), serde_json::json!({}));
446 let serialized = serialize_response(&response).unwrap();
447 assert!(serialized.contains("\"jsonrpc\":\"2.0\""));
448 assert!(serialized.contains("\"id\":1"));
449 }
450
451 #[tokio::test]
452 async fn test_route_initialize() {
453 let handler = TestHandler;
454 let ctx = RequestContext::stdio();
455 let config = RouteConfig::default();
456 let request = JsonRpcIncoming {
457 jsonrpc: "2.0".to_string(),
458 id: Some(serde_json::json!(1)),
459 method: "initialize".to_string(),
460 params: Some(serde_json::json!({
461 "protocolVersion": "2025-11-25",
462 "clientInfo": {
463 "name": "test-client",
464 "version": "1.0.0"
465 },
466 "capabilities": {}
467 })),
468 };
469
470 let response = route_request(&handler, request, &ctx, &config).await;
471 assert!(response.result.is_some());
472 assert!(response.error.is_none());
473
474 let result = response.result.unwrap();
475 assert_eq!(result["serverInfo"]["name"], "test-router");
476 assert!(result["capabilities"]["tools"].is_object());
477 assert_eq!(result["capabilities"]["tools"]["listChanged"], true);
478 }
479
480 #[tokio::test]
481 async fn test_route_initialize_missing_client_info() {
482 let handler = TestHandler;
483 let ctx = RequestContext::stdio();
484 let config = RouteConfig::default();
485 let request = JsonRpcIncoming {
486 jsonrpc: "2.0".to_string(),
487 id: Some(serde_json::json!(1)),
488 method: "initialize".to_string(),
489 params: Some(serde_json::json!({
490 "protocolVersion": "2025-11-25"
491 })),
492 };
493
494 let response = route_request(&handler, request, &ctx, &config).await;
495 assert!(response.error.is_some());
496 let error = response.error.unwrap();
497 assert_eq!(error.code, -32602); }
499
500 #[tokio::test]
501 async fn test_route_tools_list() {
502 let handler = TestHandler;
503 let ctx = RequestContext::stdio();
504 let config = RouteConfig::default();
505 let request = JsonRpcIncoming {
506 jsonrpc: "2.0".to_string(),
507 id: Some(serde_json::json!(1)),
508 method: "tools/list".to_string(),
509 params: None,
510 };
511
512 let response = route_request(&handler, request, &ctx, &config).await;
513 assert!(response.result.is_some());
514
515 let result = response.result.unwrap();
516 let tools = result["tools"].as_array().unwrap();
517 assert_eq!(tools.len(), 1);
518 assert_eq!(tools[0]["name"], "greet");
519 }
520
521 #[tokio::test]
522 async fn test_route_tools_call() {
523 let handler = TestHandler;
524 let ctx = RequestContext::stdio();
525 let config = RouteConfig::default();
526 let request = JsonRpcIncoming {
527 jsonrpc: "2.0".to_string(),
528 id: Some(serde_json::json!(1)),
529 method: "tools/call".to_string(),
530 params: Some(serde_json::json!({
531 "name": "greet",
532 "arguments": {"name": "Alice"}
533 })),
534 };
535
536 let response = route_request(&handler, request, &ctx, &config).await;
537 assert!(response.result.is_some());
538 assert!(response.error.is_none());
539 }
540
541 #[tokio::test]
542 async fn test_route_ping() {
543 let handler = TestHandler;
544 let ctx = RequestContext::stdio();
545 let config = RouteConfig::default();
546 let request = JsonRpcIncoming {
547 jsonrpc: "2.0".to_string(),
548 id: Some(serde_json::json!(1)),
549 method: "ping".to_string(),
550 params: None,
551 };
552
553 let response = route_request(&handler, request, &ctx, &config).await;
554 assert!(response.result.is_some());
555 assert!(response.error.is_none());
556 }
557
558 #[tokio::test]
559 async fn test_route_notification() {
560 let handler = TestHandler;
561 let ctx = RequestContext::stdio();
562 let config = RouteConfig::default();
563 let request = JsonRpcIncoming {
564 jsonrpc: "2.0".to_string(),
565 id: None,
566 method: "notifications/initialized".to_string(),
567 params: None,
568 };
569
570 let response = route_request(&handler, request, &ctx, &config).await;
571 assert!(!response.should_send());
572 }
573
574 #[tokio::test]
575 async fn test_route_unknown_method() {
576 let handler = TestHandler;
577 let ctx = RequestContext::stdio();
578 let config = RouteConfig::default();
579 let request = JsonRpcIncoming {
580 jsonrpc: "2.0".to_string(),
581 id: Some(serde_json::json!(1)),
582 method: "unknown/method".to_string(),
583 params: None,
584 };
585
586 let response = route_request(&handler, request, &ctx, &config).await;
587 assert!(response.error.is_some());
588 let error = response.error.unwrap();
589 assert_eq!(error.code, -32601); }
591
592 #[tokio::test]
593 async fn test_route_with_custom_protocol_version() {
594 let handler = TestHandler;
595 let ctx = RequestContext::stdio();
596 let config = RouteConfig {
597 protocol_version: Some("2025-11-25"),
598 };
599 let request = JsonRpcIncoming {
600 jsonrpc: "2.0".to_string(),
601 id: Some(serde_json::json!(1)),
602 method: "initialize".to_string(),
603 params: Some(serde_json::json!({
604 "protocolVersion": "2025-11-25",
605 "clientInfo": {
606 "name": "test-client",
607 "version": "1.0.0"
608 }
609 })),
610 };
611
612 let response = route_request(&handler, request, &ctx, &config).await;
613 let result = response.result.unwrap();
614 assert_eq!(result["protocolVersion"], "2025-11-25");
615 }
616}