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::mcp::{McpClient, McpServer, McpTool};
33use crate::tools::ToolRegistry;
34use crate::llm::ToolSpec;
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(
196 id,
197 -32602,
198 format!("Tool not found: {tool_name}"),
199 ),
200 }
201}
202
203pub struct McpServerRunner {
210 tool_specs: Vec<ToolSpec>,
211 tools: ToolRegistry,
212}
213
214impl McpServerRunner {
215 pub fn new(tools: ToolRegistry) -> Self {
220 let tool_specs = tools.specs();
221 Self { tool_specs, tools }
222 }
223
224 pub async fn run(&self) -> Result<()> {
226 let stdin = tokio::io::stdin();
227 let stdout = tokio::io::stdout();
228 self.run_on(BufReader::new(stdin), stdout).await
229 }
230
231 pub async fn run_on<R, W>(&self, reader: R, writer: W) -> Result<()>
233 where
234 R: tokio::io::AsyncBufRead + Unpin,
235 W: tokio::io::AsyncWrite + Unpin,
236 {
237 let mut lines = BufReader::new(reader).lines();
238 let mut out = writer;
239
240 while let Some(line) = lines.next_line().await? {
241 let line = line.trim().to_string();
242 if line.is_empty() {
243 continue;
244 }
245
246 let request: JsonRpcRequest = match serde_json::from_str(&line) {
248 Ok(req) => req,
249 Err(_) => {
250 let resp = JsonRpcResponse::parse_error();
251 let json = serde_json::to_string(&resp)?;
252 out.write_all(json.as_bytes()).await?;
253 out.write_all(b"\n").await?;
254 out.flush().await?;
255 continue;
256 }
257 };
258
259 if let Some(response) = dispatch_request(&request, &self.tool_specs, &self.tools).await {
261 let json = serde_json::to_string(&response)?;
262 out.write_all(json.as_bytes()).await?;
263 out.write_all(b"\n").await?;
264 out.flush().await?;
265 }
266 }
267
268 Ok(())
269 }
270}
271
272pub struct McpServerManager {
278 servers: Vec<McpServer>,
280 clients: Mutex<HashMap<String, Arc<Mutex<McpClient>>>>,
282}
283
284impl McpServerManager {
285 pub fn new(servers: Vec<McpServer>) -> Self {
289 Self {
290 servers,
291 clients: Mutex::new(HashMap::new()),
292 }
293 }
294
295 #[instrument(skip_all, name = "mcp.register_all")]
305 pub async fn register_all(&self, registry: &mut ToolRegistry) -> Result<Vec<(String, usize)>> {
306 let mut results = Vec::new();
307
308 for server in &self.servers {
309 let name = server.name.clone();
310 info!(server = %name, "Starting MCP server");
311
312 let client = McpClient::spawn(server).await.map_err(|e| {
313 Error::Tool {
314 name: format!("mcp_server:{name}"),
315 message: format!("Failed to start MCP server: {e}"),
316 }
317 })?;
318
319 let client = Arc::new(Mutex::new(client));
320
321 let tool_specs = client
323 .lock()
324 .await
325 .list_tools()
326 .await
327 .map_err(|e| Error::Tool {
328 name: format!("mcp_server:{name}"),
329 message: format!("Failed to discover tools from MCP server: {e}"),
330 })?;
331
332 let tool_count = tool_specs.len();
333 info!(
334 server = %name,
335 count = tool_count,
336 "Discovered MCP tools"
337 );
338
339 for spec in &tool_specs {
341 let tool = McpTool::new(client.clone(), spec.clone(), &name);
342 registry.register_mut(Arc::new(tool));
343 info!(
344 server = %name,
345 tool = %spec.name,
346 "Registered MCP tool"
347 );
348 }
349
350 self.clients.lock().await.insert(name.clone(), client);
352 results.push((name, tool_count));
353 }
354
355 Ok(results)
356 }
357
358 pub async fn shutdown(&self) {
363 let mut clients = self.clients.lock().await;
364 let names: Vec<String> = clients.keys().cloned().collect();
365 for name in &names {
366 info!(server = %name, "Shutting down MCP server");
367 clients.remove(name);
368 }
369 }
370
371 pub async fn running_count(&self) -> usize {
373 self.clients.lock().await.len()
374 }
375}
376
377#[cfg(test)]
378mod tests {
379 use super::*;
380 use crate::mcp::McpServer;
381
382 #[tokio::test]
384 async fn empty_servers_registers_nothing() {
385 let manager = McpServerManager::new(vec![]);
386 let mut registry = ToolRegistry::local();
387 let results = manager.register_all(&mut registry).await.unwrap();
388 assert!(results.is_empty());
389 assert!(registry.names().is_empty());
390 }
391
392 #[test]
394 fn tool_name_format() {
395 let server = "my-server";
396 let tool = "read_file";
397 let namespaced = format!("mcp__{}__{}", server, tool);
398 assert_eq!(namespaced, "mcp__my-server__read_file");
399 }
400
401 #[test]
404 fn sse_server_config_detection() {
405 let server = McpServer {
406 name: "test-sse".into(),
407 command: String::new(),
408 args: vec![],
409 url: Some("http://localhost:8080/sse".into()),
410 };
411 assert!(server.url.is_some());
414 assert!(server.command.is_empty());
415 }
416
417 #[test]
419 fn stdio_server_config_detection() {
420 let server = McpServer {
421 name: "test-stdio".into(),
422 command: "echo".into(),
423 args: vec!["hello".into()],
424 url: None,
425 };
426 assert!(!server.command.is_empty());
427 assert!(server.url.is_none());
428 }
429}