1use std::collections::HashMap;
26use std::sync::Arc;
27
28use tokio::sync::Mutex;
29use tracing::{info, instrument};
30
31use crate::error::{Error, Result};
32use crate::llm::ToolSpec;
33use crate::mcp::{McpClient, McpServer, McpTool};
34use crate::tools::ToolRegistry;
35use serde::{Deserialize, Serialize};
36use serde_json::Value;
37use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
38
39#[derive(Debug, Deserialize)]
45pub struct JsonRpcRequest {
46 pub jsonrpc: String,
47 #[serde(default)]
48 pub id: Option<Value>,
49 pub method: String,
50 #[serde(default)]
51 pub params: Value,
52}
53
54#[derive(Debug, Serialize)]
56pub struct JsonRpcResponse {
57 pub jsonrpc: String,
58 pub id: Value,
59 #[serde(skip_serializing_if = "Option::is_none")]
60 pub result: Option<Value>,
61 #[serde(skip_serializing_if = "Option::is_none")]
62 pub error: Option<JsonRpcError>,
63}
64
65#[derive(Debug, Serialize)]
67pub struct JsonRpcError {
68 pub code: i32,
69 pub message: String,
70}
71
72impl JsonRpcResponse {
73 pub fn success(id: Value, result: Value) -> Self {
75 Self {
76 jsonrpc: "2.0".to_string(),
77 id,
78 result: Some(result),
79 error: None,
80 }
81 }
82
83 pub fn error(id: Value, code: i32, message: String) -> Self {
85 Self {
86 jsonrpc: "2.0".to_string(),
87 id,
88 result: None,
89 error: Some(JsonRpcError { code, message }),
90 }
91 }
92
93 pub fn parse_error() -> Self {
95 Self::error(Value::Null, -32700, "Parse error".to_string())
96 }
97
98 pub fn method_not_found(id: Value) -> Self {
100 Self::error(id, -32601, "Method not found".to_string())
101 }
102}
103
104pub async fn dispatch_request(
111 request: &JsonRpcRequest,
112 tool_specs: &[ToolSpec],
113 tools: &ToolRegistry,
114) -> Option<JsonRpcResponse> {
115 match request.method.as_str() {
116 "initialize" => Some(handle_initialize(request)),
117 "notifications/initialized" => None,
118 "tools/list" => Some(handle_tools_list(request, tool_specs)),
119 "tools/call" => Some(handle_tools_call(request, tools).await),
120 _ => {
121 let id = request.id.clone().unwrap_or(Value::Null);
122 Some(JsonRpcResponse::method_not_found(id))
123 }
124 }
125}
126
127fn handle_initialize(request: &JsonRpcRequest) -> JsonRpcResponse {
129 let id = request.id.clone().unwrap_or(Value::Null);
130 JsonRpcResponse::success(
131 id,
132 serde_json::json!({
133 "protocolVersion": "2024-11-05",
134 "capabilities": {
135 "tools": {}
136 },
137 "serverInfo": {
138 "name": "recursive-mcp-server",
139 "version": "0.1.0"
140 }
141 }),
142 )
143}
144
145fn handle_tools_list(request: &JsonRpcRequest, tool_specs: &[ToolSpec]) -> JsonRpcResponse {
147 let id = request.id.clone().unwrap_or(Value::Null);
148 let tools: Vec<Value> = tool_specs
149 .iter()
150 .map(|spec| {
151 serde_json::json!({
152 "name": spec.name,
153 "description": spec.description,
154 "inputSchema": spec.parameters,
155 })
156 })
157 .collect();
158 JsonRpcResponse::success(id, serde_json::json!({ "tools": tools }))
159}
160
161async fn handle_tools_call(request: &JsonRpcRequest, tools: &ToolRegistry) -> JsonRpcResponse {
163 let id = request.id.clone().unwrap_or(Value::Null);
164 let tool_name = request
165 .params
166 .get("name")
167 .and_then(|v| v.as_str())
168 .unwrap_or("");
169 let arguments = request
170 .params
171 .get("arguments")
172 .cloned()
173 .unwrap_or(serde_json::json!({}));
174
175 if tool_name.is_empty() {
176 return JsonRpcResponse::error(id, -32602, "Missing tool name".to_string());
177 }
178
179 match tools.get(tool_name) {
180 Some(tool) => match tool.execute(arguments).await {
181 Ok(text) => JsonRpcResponse::success(
182 id,
183 serde_json::json!({
184 "content": [{"type": "text", "text": text}]
185 }),
186 ),
187 Err(e) => JsonRpcResponse::success(
188 id,
189 serde_json::json!({
190 "isError": true,
191 "content": [{"type": "text", "text": e.to_string()}]
192 }),
193 ),
194 },
195 None => JsonRpcResponse::error(id, -32602, format!("Tool not found: {tool_name}")),
196 }
197}
198
199pub struct McpServerRunner {
206 tool_specs: Vec<ToolSpec>,
207 tools: ToolRegistry,
208}
209
210impl McpServerRunner {
211 pub fn new(tools: ToolRegistry) -> Self {
216 let tool_specs = tools.specs();
217 Self { tool_specs, tools }
218 }
219
220 pub async fn run(&self) -> Result<()> {
222 let stdin = tokio::io::stdin();
223 let stdout = tokio::io::stdout();
224 self.run_on(BufReader::new(stdin), stdout).await
225 }
226
227 pub async fn run_on<R, W>(&self, reader: R, writer: W) -> Result<()>
229 where
230 R: tokio::io::AsyncBufRead + Unpin,
231 W: tokio::io::AsyncWrite + Unpin,
232 {
233 let mut lines = BufReader::new(reader).lines();
234 let mut out = writer;
235
236 while let Some(line) = lines.next_line().await? {
237 let line = line.trim().to_string();
238 if line.is_empty() {
239 continue;
240 }
241
242 let request: JsonRpcRequest = match serde_json::from_str(&line) {
244 Ok(req) => req,
245 Err(_) => {
246 let resp = JsonRpcResponse::parse_error();
247 let json = serde_json::to_string(&resp)?;
248 out.write_all(json.as_bytes()).await?;
249 out.write_all(b"\n").await?;
250 out.flush().await?;
251 continue;
252 }
253 };
254
255 if let Some(response) = dispatch_request(&request, &self.tool_specs, &self.tools).await
257 {
258 let json = serde_json::to_string(&response)?;
259 out.write_all(json.as_bytes()).await?;
260 out.write_all(b"\n").await?;
261 out.flush().await?;
262 }
263 }
264
265 Ok(())
266 }
267}
268
269pub struct McpServerManager {
275 servers: Vec<McpServer>,
277 clients: Mutex<HashMap<String, Arc<Mutex<McpClient>>>>,
279}
280
281impl McpServerManager {
282 pub fn new(servers: Vec<McpServer>) -> Self {
286 Self {
287 servers,
288 clients: Mutex::new(HashMap::new()),
289 }
290 }
291
292 #[instrument(skip_all, name = "mcp.register_all")]
302 pub async fn register_all(&self, registry: &mut ToolRegistry) -> Result<Vec<(String, usize)>> {
303 let mut results = Vec::new();
304
305 for server in &self.servers {
306 let name = server.name.clone();
307 info!(server = %name, "Starting MCP server");
308
309 let client = McpClient::spawn(server).await.map_err(|e| Error::Tool {
310 name: format!("mcp_server:{name}"),
311 call_id: None,
312 message: format!("Failed to start MCP server: {e}"),
313 })?;
314
315 let client = Arc::new(Mutex::new(client));
316
317 let tool_specs = client
319 .lock()
320 .await
321 .list_tools()
322 .await
323 .map_err(|e| Error::Tool {
324 name: format!("mcp_server:{name}"),
325 call_id: None,
326 message: format!("Failed to discover tools from MCP server: {e}"),
327 })?;
328
329 let tool_count = tool_specs.len();
330 info!(
331 server = %name,
332 count = tool_count,
333 "Discovered MCP tools"
334 );
335
336 for spec in &tool_specs {
338 let tool = McpTool::new(client.clone(), spec.clone(), &name);
339 registry.register_mut(Arc::new(tool));
340 info!(
341 server = %name,
342 tool = %spec.name,
343 "Registered MCP tool"
344 );
345 }
346
347 self.clients.lock().await.insert(name.clone(), client);
349 results.push((name, tool_count));
350 }
351
352 Ok(results)
353 }
354
355 pub async fn shutdown(&self) {
360 let mut clients = self.clients.lock().await;
361 let names: Vec<String> = clients.keys().cloned().collect();
362 for name in &names {
363 info!(server = %name, "Shutting down MCP server");
364 clients.remove(name);
365 }
366 }
367
368 pub async fn running_count(&self) -> usize {
370 self.clients.lock().await.len()
371 }
372}
373
374#[cfg(test)]
375mod tests {
376 use super::*;
377 use crate::mcp::McpServer;
378
379 #[test]
380 fn success_response_has_result_and_no_error() {
381 use serde_json::{json, Value};
383 let r = JsonRpcResponse::success(Value::from(1i64), json!({"ok": true}));
384 assert!(r.result.is_some(), "success must have a result");
385 assert!(r.error.is_none(), "success must not have an error");
386 assert_eq!(r.jsonrpc, "2.0");
387 }
388
389 #[test]
390 fn parse_error_uses_negative_code() {
391 let r = JsonRpcResponse::parse_error();
393 let err = r.error.expect("parse_error must have an error");
394 assert_eq!(err.code, -32700, "parse error code must be -32700");
395 }
396
397 #[test]
398 fn method_not_found_uses_correct_negative_code() {
399 use serde_json::Value;
401 let r = JsonRpcResponse::method_not_found(Value::from(42i64));
402 let err = r.error.expect("method_not_found must have an error");
403 assert_eq!(err.code, -32601, "method not found code must be -32601");
404 }
405
406 #[tokio::test]
408 async fn empty_servers_registers_nothing() {
409 let manager = McpServerManager::new(vec![]);
410 let mut registry = ToolRegistry::local();
411 let results = manager.register_all(&mut registry).await.unwrap();
412 assert!(results.is_empty());
413 assert!(registry.names().is_empty());
414 }
415
416 #[test]
418 fn tool_name_format() {
419 let server = "my-server";
420 let tool = "Read";
421 let namespaced = format!("mcp__{}__{}", server, tool);
422 assert_eq!(namespaced, "mcp__my-server__Read");
423 }
424
425 #[test]
428 fn sse_server_config_detection() {
429 let server = McpServer {
430 name: "test-sse".into(),
431 command: String::new(),
432 args: vec![],
433 url: Some("http://localhost:8080/sse".into()),
434 env: None,
435 };
436 assert!(server.url.is_some());
439 assert!(server.command.is_empty());
440 }
441
442 #[test]
444 fn stdio_server_config_detection() {
445 let server = McpServer {
446 name: "test-stdio".into(),
447 command: "echo".into(),
448 args: vec!["hello".into()],
449 url: None,
450 env: None,
451 };
452 assert!(!server.command.is_empty());
453 assert!(server.url.is_none());
454 }
455
456 #[test]
457 fn error_response_has_error_and_no_result() {
458 let r = JsonRpcResponse::error(Value::from(1i64), -32600, "bad request".to_string());
460 assert!(r.error.is_some(), "error response must have error field");
461 assert!(
462 r.result.is_none(),
463 "error response must not have result field"
464 );
465 let err = r.error.unwrap();
466 assert_eq!(err.code, -32600);
467 assert_eq!(err.message, "bad request");
468 }
469
470 #[tokio::test]
471 async fn dispatch_notifications_returns_none() {
472 let reg = ToolRegistry::local();
474 let specs = reg.specs();
475 let request = JsonRpcRequest {
476 jsonrpc: "2.0".to_string(),
477 id: None,
478 method: "notifications/initialized".to_string(),
479 params: Value::Null,
480 };
481 let result = dispatch_request(&request, &specs, ®).await;
482 assert!(
483 result.is_none(),
484 "notifications/initialized must return None (no response)"
485 );
486 }
487
488 #[tokio::test]
489 async fn dispatch_unknown_method_returns_method_not_found() {
490 let reg = ToolRegistry::local();
492 let specs = reg.specs();
493 let request = JsonRpcRequest {
494 jsonrpc: "2.0".to_string(),
495 id: Some(Value::from(99i64)),
496 method: "totally/unknown".to_string(),
497 params: Value::Null,
498 };
499 let result = dispatch_request(&request, &specs, ®).await;
500 assert!(result.is_some(), "unknown method must return Some");
501 let r = result.unwrap();
502 let err = r.error.expect("unknown method must return error");
503 assert_eq!(err.code, -32601, "must use -32601 for method not found");
504 }
505
506 #[tokio::test]
507 async fn dispatch_initialize_returns_protocol_version() {
508 let reg = ToolRegistry::local();
510 let specs = reg.specs();
511 let request = JsonRpcRequest {
512 jsonrpc: "2.0".to_string(),
513 id: Some(Value::from(1i64)),
514 method: "initialize".to_string(),
515 params: Value::Null,
516 };
517 let result = dispatch_request(&request, &specs, ®).await;
518 assert!(result.is_some(), "initialize must return Some response");
519 let r = result.unwrap();
520 assert!(r.error.is_none(), "initialize must not return error");
521 let res = r.result.expect("initialize must have result");
522 assert_eq!(
523 res["protocolVersion"], "2024-11-05",
524 "initialize result must contain protocolVersion"
525 );
526 }
527
528 #[tokio::test]
529 async fn dispatch_tools_list_returns_tool_names() {
530 let tmp = tempfile::TempDir::new().unwrap();
533 let reg = crate::tools::build_standard_tools(tmp.path(), &[], 30);
534 let specs = reg.specs();
535 let request = JsonRpcRequest {
536 jsonrpc: "2.0".to_string(),
537 id: Some(Value::from(2i64)),
538 method: "tools/list".to_string(),
539 params: Value::Null,
540 };
541 let result = dispatch_request(&request, &specs, ®).await;
542 assert!(result.is_some(), "tools/list must return Some response");
543 let r = result.unwrap();
544 let res = r.result.expect("tools/list must have result");
545 let tools = res["tools"]
546 .as_array()
547 .expect("result must have tools array");
548 assert!(
549 !tools.is_empty(),
550 "tools/list must return at least one tool"
551 );
552 let has_read = tools.iter().any(|t| t["name"].as_str() == Some("Read"));
553 assert!(has_read, "tools/list must include 'Read' tool");
554 }
555
556 #[tokio::test]
557 async fn handle_tools_call_missing_name_returns_error() {
558 let reg = ToolRegistry::local();
560 let specs = reg.specs();
561 let request = JsonRpcRequest {
562 jsonrpc: "2.0".to_string(),
563 id: Some(Value::from(3i64)),
564 method: "tools/call".to_string(),
565 params: serde_json::json!({ "arguments": {} }), };
567 let result = dispatch_request(&request, &specs, ®).await;
568 let r = result.expect("tools/call must return Some");
569 let err = r.error.expect("missing tool name must return error");
570 assert_eq!(err.code, -32602, "missing name must use -32602");
571 }
572
573 #[tokio::test]
574 async fn handle_tools_call_unknown_tool_uses_negative_error_code() {
575 let tmp = tempfile::TempDir::new().unwrap();
577 let reg = crate::tools::build_standard_tools(tmp.path(), &[], 30);
578 let specs = reg.specs();
579 let request = JsonRpcRequest {
580 jsonrpc: "2.0".to_string(),
581 id: Some(Value::from(4i64)),
582 method: "tools/call".to_string(),
583 params: serde_json::json!({ "name": "no_such_tool_xyz", "arguments": {} }),
584 };
585 let result = dispatch_request(&request, &specs, ®).await;
586 let r = result.expect("tools/call must return Some for unknown tool");
587 let err = r.error.expect("unknown tool must return an error response");
588 assert_eq!(
589 err.code, -32602,
590 "unknown tool error code must be -32602 (negative); got: {}",
591 err.code
592 );
593 assert!(
594 err.message.contains("no_such_tool_xyz"),
595 "error message must mention the tool name; got: {}",
596 err.message
597 );
598 }
599}