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 message: format!("Failed to start MCP server: {e}"),
312 })?;
313
314 let client = Arc::new(Mutex::new(client));
315
316 let tool_specs = client
318 .lock()
319 .await
320 .list_tools()
321 .await
322 .map_err(|e| Error::Tool {
323 name: format!("mcp_server:{name}"),
324 message: format!("Failed to discover tools from MCP server: {e}"),
325 })?;
326
327 let tool_count = tool_specs.len();
328 info!(
329 server = %name,
330 count = tool_count,
331 "Discovered MCP tools"
332 );
333
334 for spec in &tool_specs {
336 let tool = McpTool::new(client.clone(), spec.clone(), &name);
337 registry.register_mut(Arc::new(tool));
338 info!(
339 server = %name,
340 tool = %spec.name,
341 "Registered MCP tool"
342 );
343 }
344
345 self.clients.lock().await.insert(name.clone(), client);
347 results.push((name, tool_count));
348 }
349
350 Ok(results)
351 }
352
353 pub async fn shutdown(&self) {
358 let mut clients = self.clients.lock().await;
359 let names: Vec<String> = clients.keys().cloned().collect();
360 for name in &names {
361 info!(server = %name, "Shutting down MCP server");
362 clients.remove(name);
363 }
364 }
365
366 pub async fn running_count(&self) -> usize {
368 self.clients.lock().await.len()
369 }
370}
371
372#[cfg(test)]
373mod tests {
374 use super::*;
375 use crate::mcp::McpServer;
376
377 #[tokio::test]
379 async fn empty_servers_registers_nothing() {
380 let manager = McpServerManager::new(vec![]);
381 let mut registry = ToolRegistry::local();
382 let results = manager.register_all(&mut registry).await.unwrap();
383 assert!(results.is_empty());
384 assert!(registry.names().is_empty());
385 }
386
387 #[test]
389 fn tool_name_format() {
390 let server = "my-server";
391 let tool = "read_file";
392 let namespaced = format!("mcp__{}__{}", server, tool);
393 assert_eq!(namespaced, "mcp__my-server__read_file");
394 }
395
396 #[test]
399 fn sse_server_config_detection() {
400 let server = McpServer {
401 name: "test-sse".into(),
402 command: String::new(),
403 args: vec![],
404 url: Some("http://localhost:8080/sse".into()),
405 };
406 assert!(server.url.is_some());
409 assert!(server.command.is_empty());
410 }
411
412 #[test]
414 fn stdio_server_config_detection() {
415 let server = McpServer {
416 name: "test-stdio".into(),
417 command: "echo".into(),
418 args: vec!["hello".into()],
419 url: None,
420 };
421 assert!(!server.command.is_empty());
422 assert!(server.url.is_none());
423 }
424}