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