1pub mod client;
28pub mod protocol;
29
30pub use client::McpClient;
31pub use protocol::*;
32
33use std::collections::HashMap;
34use std::sync::Arc;
35
36use anyhow::{Result, anyhow};
37use tokio::sync::RwLock;
38
39pub struct McpBridge {
62 servers: parking_lot::RwLock<Vec<McpServer>>,
64 clients: RwLock<HashMap<String, Arc<McpClient>>>,
66 tool_cache: RwLock<HashMap<String, Vec<McpTool>>>,
68}
69
70impl McpBridge {
71 pub fn new() -> Self {
73 Self {
74 servers: parking_lot::RwLock::new(Vec::new()),
75 clients: RwLock::new(HashMap::new()),
76 tool_cache: RwLock::new(HashMap::new()),
77 }
78 }
79
80 pub fn register_server(&self, server: McpServer) {
87 let mut servers = self.servers.write();
88 let name = server.name.clone();
89 if let Some(existing) = servers.iter_mut().find(|s| s.name == name) {
90 tracing::warn!(
91 server = %name,
92 "Overwriting duplicate MCP server registration"
93 );
94 *existing = server;
95 } else {
96 servers.push(server);
97 }
98 }
99
100 pub fn servers(&self) -> Vec<String> {
102 self.servers.read().iter().map(|s| s.name.clone()).collect()
103 }
104
105 pub fn get_server(&self, name: &str) -> Option<McpServer> {
107 self.servers.read().iter().find(|s| s.name == name).cloned()
108 }
109
110 pub async fn initialize_all(&self) -> Result<()> {
115 let mut errors = Vec::new();
116
117 let server_list: Vec<McpServer> = self.servers.read().iter().cloned().collect();
118 for server in server_list {
119 if !server.enabled {
120 tracing::debug!(server = %server.name, "Skipping disabled MCP server");
121 continue;
122 }
123
124 let client = Arc::new(McpClient::new(server.clone()));
125 match client.initialize().await {
126 Ok(()) => {
127 self.clients
128 .write()
129 .await
130 .insert(server.name.clone(), client);
131 tracing::info!(server = %server.name, "MCP server started");
132 }
133 Err(e) => {
134 tracing::error!(server = %server.name, error = %e, "Failed to initialize MCP server");
135 errors.push(format!("{}: {}", server.name, e));
136 }
137 }
138 }
139
140 if errors.is_empty() {
141 Ok(())
142 } else {
143 Err(anyhow!("MCP initialization failed: {}", errors.join("; ")))
144 }
145 }
146
147 pub async fn initialize_server(&self, name: &str) -> Result<()> {
149 let server = self
150 .servers
151 .read()
152 .iter()
153 .find(|s| s.name == name)
154 .cloned()
155 .ok_or_else(|| anyhow!("MCP server '{name}' not found"))?;
156
157 let client = Arc::new(McpClient::new(server));
158 client.initialize().await?;
159
160 self.clients.write().await.insert(name.to_string(), client);
161 Ok(())
162 }
163
164 pub async fn client(&self, name: &str) -> Option<Arc<McpClient>> {
166 self.clients.read().await.get(name).cloned()
167 }
168
169 pub async fn list_tools(&self) -> Result<Vec<McpTool>> {
177 let clients: Vec<(String, Arc<McpClient>)> = self
178 .clients
179 .read()
180 .await
181 .iter()
182 .map(|(k, v)| (k.clone(), v.clone()))
183 .collect();
184
185 let mut all_tools = Vec::new();
186 for (name, client) in &clients {
187 if let Ok(mcp_tools) = client.list_tools().await {
188 let start = all_tools.len();
189 all_tools.extend(mcp_tools);
190 *self
191 .tool_cache
192 .write()
193 .await
194 .entry(name.clone())
195 .or_insert_with(Vec::new) = all_tools[start..].to_vec();
196 }
197 }
198
199 Ok(all_tools)
200 }
201
202 pub async fn cached_tools(&self, server_name: &str) -> Option<Vec<McpTool>> {
204 self.tool_cache.read().await.get(server_name).cloned()
205 }
206
207 pub async fn call_tool(
211 &self,
212 server_name: &str,
213 tool_name: &str,
214 args: serde_json::Value,
215 ) -> Result<McpToolCallResult> {
216 let client = {
217 let clients = self.clients.read().await;
218 clients
219 .get(server_name)
220 .cloned()
221 .ok_or_else(|| anyhow!("MCP server '{server_name}' not connected"))?
222 };
223
224 client.call_tool(tool_name, args).await
225 }
226
227 pub async fn shutdown_all(&self) -> Result<()> {
229 let mut clients = self.clients.write().await;
230
231 for (name, client) in clients.drain() {
232 if let Err(e) = client.shutdown().await {
233 tracing::warn!(server = %name, error = %e, "Error shutting down MCP server");
234 }
235 }
236
237 self.tool_cache.write().await.clear();
238 Ok(())
239 }
240
241 pub async fn refresh_tools(&self, server_name: &str) -> Result<Vec<McpTool>> {
245 let client = {
246 let clients = self.clients.read().await;
247 clients
248 .get(server_name)
249 .cloned()
250 .ok_or_else(|| anyhow!("MCP server '{server_name}' not connected"))?
251 };
252
253 let mcp_tools = client.refresh_tools().await?;
254
255 *self
256 .tool_cache
257 .write()
258 .await
259 .entry(server_name.to_string())
260 .or_insert_with(Vec::new) = mcp_tools.clone();
261
262 Ok(mcp_tools)
263 }
264
265 pub async fn clear_cache(&self, server_name: &str) {
267 self.tool_cache.write().await.remove(server_name);
268 }
269
270 pub async fn remove_server(&self, name: &str) -> Result<()> {
272 if let Some(client) = self.clients.write().await.remove(name)
274 && let Err(e) = client.shutdown().await
275 {
276 tracing::warn!(server = %name, error = %e, "Error shutting down MCP server during removal");
277 }
278 let found = {
280 let mut servers = self.servers.write();
281 let len_before = servers.len();
282 servers.retain(|s| s.name != name);
283 servers.len() != len_before
284 };
285 if !found {
286 return Err(anyhow!("MCP server '{name}' not found"));
287 }
288 self.tool_cache.write().await.remove(name);
290 Ok(())
291 }
292 pub async fn update_server(
299 &self,
300 name: &str,
301 command: String,
302 args: Vec<String>,
303 env: HashMap<String, String>,
304 enabled: bool,
305 ) -> Result<McpServer> {
306 if let Some(client) = self.clients.write().await.remove(name)
309 && let Err(e) = client.shutdown().await
310 {
311 tracing::warn!(server = %name, error = %e, "Error shutting down MCP server during update");
312 }
313 self.tool_cache.write().await.remove(name);
314
315 let server = McpServer {
318 name: name.to_string(),
319 command,
320 args,
321 env,
322 enabled,
323 };
324 self.register_server(server.clone());
325 Ok(server)
326 }
327
328 pub async fn toggle_server(&self, name: &str) -> Result<bool> {
330 let new_state = {
333 let mut servers = self.servers.write();
334 let server = servers
335 .iter_mut()
336 .find(|s| s.name == name)
337 .ok_or_else(|| anyhow!("MCP server '{name}' not found"))?;
338 server.enabled = !server.enabled;
339 server.enabled
340 };
341
342 if !new_state {
344 if let Some(client) = self.clients.write().await.remove(name)
345 && let Err(e) = client.shutdown().await
346 {
347 tracing::warn!(server = %name, error = %e, "Error shutting down MCP server on disable");
348 }
349 self.tool_cache.write().await.remove(name);
350 }
351
352 Ok(new_state)
353 }
354
355 pub async fn clear_all_caches(&self) {
357 self.tool_cache.write().await.clear();
358 }
359}
360
361impl Default for McpBridge {
362 fn default() -> Self {
363 Self::new()
364 }
365}
366
367#[cfg(test)]
372mod tests {
373 use super::*;
374 use tokio::time::Duration;
375
376 #[test]
379 fn test_mcp_server_builder() {
380 let server = McpServer::new("test-server", "npx")
381 .with_args(vec!["-y".to_string(), "@anthropic/mcp-server".to_string()])
382 .with_env("DEBUG", "true");
383
384 assert_eq!(server.name, "test-server");
385 assert_eq!(server.command, "npx");
386 assert_eq!(server.args, vec!["-y", "@anthropic/mcp-server"]);
387 assert_eq!(server.env.get("DEBUG"), Some(&"true".to_string()));
388 assert!(server.enabled);
389 }
390
391 #[tokio::test]
394 async fn test_update_server_replaces_config() {
395 let bridge = McpBridge::new();
396 bridge.register_server(
397 McpServer::new("fs", "npx")
398 .with_args(vec!["old".to_string()])
399 .with_env("OLD", "1"),
400 );
401
402 let mut env = HashMap::new();
404 env.insert("NEW".to_string(), "2".to_string());
405 let updated = bridge
406 .update_server(
407 "fs",
408 "node".to_string(),
409 vec!["new.js".to_string()],
410 env,
411 false,
412 )
413 .await
414 .expect("update");
415
416 assert_eq!(updated.command, "node");
417 assert_eq!(updated.args, vec!["new.js"]);
418 assert_eq!(updated.env.get("NEW"), Some(&"2".to_string()));
419 assert!(!updated.enabled);
420
421 let stored = bridge.get_server("fs").expect("get_server");
422 assert_eq!(stored.command, "node");
423 assert!(!stored.enabled);
424 assert!(!stored.env.contains_key("OLD"));
426 }
427
428 #[tokio::test]
429 async fn test_update_server_unknown_name_returns_error() {
430 let bridge = McpBridge::new();
431 let result = bridge
432 .update_server("nope", "x".to_string(), vec![], HashMap::new(), true)
433 .await;
434 assert!(result.is_ok());
440 }
441
442 #[test]
445 fn test_mcp_request_serialization() {
446 let request = McpRequest::new("tools/list");
447 let json = serde_json::to_string(&request).unwrap();
448
449 assert!(json.contains(r#""method":"tools/list""#));
450 assert!(json.contains(r#""jsonrpc":"2.0""#));
451 }
452
453 #[test]
454 fn test_mcp_request_with_params() {
455 let request = McpRequest::new("tools/call").with_params(serde_json::json!({
456 "name": "my_tool",
457 "arguments": {"arg1": "value1"}
458 }));
459
460 let json = serde_json::to_string(&request).unwrap();
461 assert!(json.contains("my_tool"));
462 assert!(json.contains("arg1"));
463 }
464
465 #[test]
466 fn test_mcp_request_to_jsonl() {
467 let request = McpRequest::new("initialize");
468 let jsonl = request.to_jsonl().unwrap();
469
470 assert_eq!(jsonl.last(), Some(&b'\n'));
472
473 let json_str = String::from_utf8_lossy(&jsonl[..jsonl.len() - 1]);
475 let parsed: McpRequest = serde_json::from_str(&json_str).unwrap();
476 assert_eq!(parsed.method, "initialize");
477 }
478
479 #[test]
480 fn test_mcp_response_result() {
481 let response = McpResponse {
482 jsonrpc: "2.0".to_string(),
483 id: serde_json::json!(1),
484 result: Some(serde_json::json!({"tools": []})),
485 error: None,
486 };
487
488 assert!(!response.is_error());
489 let result = response.clone().into_result().unwrap();
490 assert!(result.get("tools").is_some());
491 }
492
493 #[test]
494 fn test_mcp_response_error() {
495 let response = McpResponse {
496 jsonrpc: "2.0".to_string(),
497 id: serde_json::json!(2),
498 result: None,
499 error: Some(McpError::internal_error("Something went wrong")),
500 };
501
502 assert!(response.is_error());
503 let err = response.into_result().unwrap_err();
504 assert!(err.to_string().contains("internal error"));
505 }
506
507 #[test]
508 fn test_mcp_error_codes() {
509 assert_eq!(McpError::parse_error().code, -32700);
510 assert_eq!(McpError::invalid_request("test").code, -32600);
511 assert_eq!(McpError::method_not_found().code, -32601);
512 assert_eq!(McpError::invalid_params().code, -32602);
513 assert_eq!(McpError::internal_error("x").code, -32603);
514 assert_eq!(McpError::server_error("x").code, -32000);
515 }
516
517 #[test]
520 fn test_bridge_registration() {
521 let bridge = McpBridge::new();
522
523 bridge.register_server(McpServer::new("test", "echo"));
524
525 assert_eq!(bridge.servers(), vec!["test"]);
526 assert!(bridge.get_server("test").is_some());
527 assert!(bridge.get_server("missing").is_none());
528 }
529
530 #[tokio::test]
533 async fn test_mcp_client_non_existent_command() {
534 let server = McpServer::new("ghost", "nonexistent-binary-xyz");
535 let client = McpClient::new(server);
536
537 let result = client.initialize().await;
538 assert!(result.is_err());
539 assert!(result.unwrap_err().to_string().contains("Failed to spawn"));
540 }
541
542 #[tokio::test]
543 async fn test_mcp_client_shutdown_no_panic() {
544 let server = McpServer::new("test-shutdown", "echo");
545 let client = McpClient::new(server);
546
547 client.shutdown().await.expect("shutdown should succeed");
549 assert!(!client.is_initialized().await);
550 }
551
552 #[tokio::test]
553 async fn test_mcp_client_with_timeout() {
554 let server = McpServer::new("test", "sleep").with_args(vec!["999".to_string()]);
555 let client = McpClient::new(server).with_timeout(Duration::from_millis(100));
556
557 let result = client.initialize().await;
559 assert!(result.is_err());
561 }
562
563 #[tokio::test]
566 async fn test_bridge_initialize_all_empty() {
567 let bridge = McpBridge::new();
568 bridge
569 .initialize_all()
570 .await
571 .expect("empty bridge should initialize");
572 }
573
574 #[tokio::test]
575 async fn test_bridge_initialize_all_fails_gracefully() {
576 let bridge = McpBridge::new();
577 bridge.register_server(McpServer::new("ghost", "nonexistent-cmd-xyz"));
578 bridge.register_server(McpServer::new("ghost2", "nonexistent-cmd-abc"));
579
580 let result = bridge.initialize_all().await;
581 assert!(result.is_err());
583 }
584
585 #[tokio::test]
586 async fn test_bridge_shutdown_all_empty() {
587 let bridge = McpBridge::new();
588 bridge
589 .shutdown_all()
590 .await
591 .expect("empty bridge shutdown should succeed");
592 }
593
594 #[tokio::test]
595 async fn test_bridge_call_tool_no_server() {
596 let bridge = McpBridge::new();
597 let result = bridge
598 .call_tool("ghost", "tool", serde_json::json!({}))
599 .await;
600 assert!(result.is_err());
601 assert!(result.unwrap_err().to_string().contains("not connected"));
602 }
603
604 #[tokio::test]
605 async fn test_bridge_initialize_server_not_found() {
606 let bridge = McpBridge::new();
607 let result = bridge.initialize_server("missing").await;
608 assert!(result.is_err());
609 }
610
611 #[test]
612 fn test_mcp_client_debug() {
613 let server = McpServer::new("debug-test", "echo");
614 let client = McpClient::new(server);
615 let debug = format!("{client:?}");
616 assert!(debug.contains("debug-test"));
617 }
618
619 #[tokio::test]
622 async fn test_mcp_client_double_init_ignored() {
623 let server = McpServer::new("echo", "echo");
624 let client = McpClient::new(server);
625
626 let _ = client.initialize().await;
628 let _ = client.is_initialized().await;
630 }
631}