1use crate::Tool;
3use serde_json::{json, Value};
4use std::collections::HashMap;
5use std::sync::Arc;
6use tokio::sync::Mutex;
7
8use super::connection::McpConnection;
9use super::tool::McpTool;
10use super::McpServerConfig;
11
12pub struct McpConnectTool {
20 configs: HashMap<String, McpServerConfig>,
21 connected: Arc<Mutex<std::collections::HashSet<String>>>,
22}
23
24impl McpConnectTool {
25 pub fn new(configs: HashMap<String, McpServerConfig>) -> Self {
26 Self {
27 configs,
28 connected: Arc::new(Mutex::new(std::collections::HashSet::new())),
29 }
30 }
31}
32
33#[async_trait::async_trait]
34impl Tool for McpConnectTool {
35 fn origin(&self) -> crate::tools::ToolOrigin {
36 crate::tools::ToolOrigin::Builtin
37 }
38
39 fn name(&self) -> &str {
40 "connect_mcp_server"
41 }
42
43 fn description(&self) -> &str {
44 "Connect to an MCP server and load its tools. Call this before using tools from an external MCP server. Available servers are listed in the description below. Once connected, the server's tools become available for the rest of the session."
45 }
46
47 fn parameters(&self) -> Value {
48 let server_names: Vec<&str> = self.configs.keys().map(|s| s.as_str()).collect();
49 let server_list = server_names.join(", ");
50 json!({
51 "type": "object",
52 "properties": {
53 "server": {
54 "type": "string",
55 "description": format!("Name of the MCP server to connect to. Available: {}", server_list)
56 }
57 },
58 "required": ["server"]
59 })
60 }
61
62 async fn execute(&self, params: Value, ctx: crate::ToolContext) -> crate::Result<String> {
63 let server_name = params["server"]
64 .as_str()
65 .ok_or_else(|| crate::RuntimeError::Tool("Missing 'server' parameter".to_string()))?;
66
67 {
69 let mut connected = self.connected.lock().await;
70 if connected.contains(server_name) {
71 return Ok(format!("Server '{}' is already connected.", server_name));
72 }
73 connected.insert(server_name.to_string());
75 }
76
77 let config = self.configs.get(server_name).ok_or_else(|| {
78 let available: Vec<&str> = self.configs.keys().map(|s| s.as_str()).collect();
79 crate::RuntimeError::Tool(format!(
80 "Unknown MCP server '{}'. Available: {}",
81 server_name,
82 available.join(", ")
83 ))
84 })?;
85
86 tracing::info!(server = %server_name, "Lazy-connecting to MCP server");
87
88 let mut conn = match McpConnection::start(config).await {
89 Ok(c) => c,
90 Err(e) => {
91 self.connected.lock().await.remove(server_name);
93 return Err(crate::RuntimeError::Tool(format!(
94 "Failed to connect to MCP server '{}': {}",
95 server_name, e
96 )));
97 }
98 };
99
100 let tools = match conn.list_tools().await {
101 Ok(t) => t,
102 Err(e) => {
103 self.connected.lock().await.remove(server_name);
104 return Err(crate::RuntimeError::Tool(format!(
105 "Failed to list tools from '{}': {}",
106 server_name, e
107 )));
108 }
109 };
110
111 let tool_count = tools.len();
112 let connection = Arc::new(Mutex::new(conn));
113 let mut tool_names = Vec::new();
114 let mut new_tools: Vec<Arc<dyn crate::Tool>> = Vec::new();
115
116 for tool_def in tools {
117 let prefixed_name = format!("ext__{}__{}", server_name, tool_def.name);
118 tool_names.push(format!(
119 "{} — {}",
120 tool_def.name,
121 tool_def.description.chars().take(60).collect::<String>()
122 ));
123
124 let mcp_tool = McpTool {
125 tool_name: prefixed_name,
126 server_tool_name: tool_def.name.clone(),
127 server_name: server_name.to_string(),
128 description: format!("[MCP:{}] {}", server_name, tool_def.description),
129 input_schema: tool_def.input_schema,
130 connection: Arc::clone(&connection),
131 };
132
133 new_tools.push(Arc::new(mcp_tool));
134 }
135
136 if let Some(ref tx) = ctx.capabilities.tool_register_tx {
138 let _ = tx.send(new_tools);
139 }
140
141 tracing::info!(server = %server_name, tools = tool_count, "MCP server connected (lazy)");
142
143 let tool_list = tool_names.join("\n • ");
144 Ok(format!(
145 "Connected to '{}' — {} tools now available:\n • {}",
146 server_name, tool_count, tool_list
147 ))
148 }
149}