1use anyhow::{anyhow, Result};
6use async_trait::async_trait;
7use serde_json::Value;
8use std::collections::HashMap;
9use std::sync::Arc;
10use tokio::sync::{Mutex, RwLock};
11
12use super::client::McpClient;
13use super::config::{McpConfig, McpServerConfig};
14use super::proxy::McpToolWrapper;
15use crate::tools::{ToolDefinition, Tool};
16use crate::approval::RiskLevel;
17
18pub struct LazyMcpTool {
24 definition: ToolDefinition,
26
27 server_name: String,
29
30 server_config: McpServerConfig,
32
33 actual_tool: Arc<Mutex<Option<Arc<McpToolWrapper>>>>,
35
36 client: Arc<Mutex<Option<Arc<McpClient>>>>,
38}
39
40impl LazyMcpTool {
41 pub fn new(
43 server_name: String,
44 server_config: McpServerConfig,
45 tool_name: String,
46 tool_description: String,
47 tool_input_schema: Value,
48 ) -> Self {
49 let definition = ToolDefinition {
51 name: tool_name,
52 description: tool_description,
53 parameters: tool_input_schema,
54 is_priority: false,
55 };
56
57 Self {
58 definition,
59 server_name,
60 server_config,
61 actual_tool: Arc::new(Mutex::new(None)),
62 client: Arc::new(Mutex::new(None)),
63 }
64 }
65
66 async fn ensure_started(&self) -> Result<Arc<McpToolWrapper>> {
68 {
70 let tool_lock = self.actual_tool.lock().await;
71 if let Some(tool) = tool_lock.as_ref() {
72 return Ok(tool.clone());
73 }
74 }
75
76 tracing::info!("Starting lazy MCP server '{}' for tool '{}'",
78 self.server_name, self.definition.name);
79
80 let transport_config = self.server_config.to_transport_config()?;
81 let client = Arc::new(McpClient::connect(&self.server_name, transport_config).await?);
82
83 let mcp_tools = client.list_tools().await?;
85
86 let tool_name_without_prefix = self.definition.name.clone();
88 let mcp_tool = mcp_tools.into_iter()
89 .find(|t| {
90 let name = t.name.clone();
92 name == tool_name_without_prefix ||
93 name == format!("{}_{}", self.server_name, tool_name_without_prefix)
94 })
95 .ok_or_else(|| anyhow!(
96 "Tool '{}' not found in MCP server '{}'",
97 self.definition.name, self.server_name
98 ))?;
99
100 let wrapper = Arc::new(McpToolWrapper::new(
102 client.clone(),
103 mcp_tool,
104 self.server_name.clone()
105 ));
106
107 {
109 let mut tool_lock = self.actual_tool.lock().await;
110 *tool_lock = Some(wrapper.clone());
111 }
112 {
113 let mut client_lock = self.client.lock().await;
114 *client_lock = Some(client);
115 }
116
117 tracing::info!("Lazy MCP server '{}' started successfully", self.server_name);
118
119 Ok(wrapper)
120 }
121
122 pub async fn shutdown(&self) -> Result<()> {
124 let client_lock = self.client.lock().await;
125 if let Some(client) = client_lock.as_ref() {
126 client.shutdown().await?;
127 tracing::info!("Lazy MCP server '{}' stopped", self.server_name);
128 }
129 Ok(())
130 }
131}
132
133#[async_trait]
134impl Tool for LazyMcpTool {
135 fn definition(&self) -> ToolDefinition {
136 self.definition.clone()
137 }
138
139 async fn execute(&self, params: Value) -> Result<String> {
140 let tool = self.ensure_started().await?;
142
143 tool.execute(params).await
145 }
146
147 fn risk_level(&self) -> RiskLevel {
148 RiskLevel::Mutating
150 }
151}
152
153pub struct LazyMcpLoader {
159 config: McpConfig,
161
162 #[allow(dead_code)]
164 tools: Vec<LazyMcpTool>,
165}
166
167impl LazyMcpLoader {
168 pub fn from_config(config: McpConfig) -> Self {
170 let tools = Vec::new();
171
172 Self {
177 config,
178 tools,
179 }
180 }
181
182 pub async fn discover_tools(&self) -> Result<Vec<LazyMcpTool>> {
185 Ok(Vec::new())
193 }
194
195 pub fn config(&self) -> &McpConfig {
197 &self.config
198 }
199}
200
201pub struct McpToolPlaceholder {
207 server_name: String,
209
210 server_config: McpServerConfig,
212
213 tools: Arc<RwLock<Option<Vec<Arc<McpToolWrapper>>>>>,
215
216 client: Arc<RwLock<Option<Arc<McpClient>>>>,
218}
219
220impl McpToolPlaceholder {
221 pub fn new(server_name: String, server_config: McpServerConfig) -> Self {
223 Self {
224 server_name,
225 server_config,
226 tools: Arc::new(RwLock::new(None)),
227 client: Arc::new(RwLock::new(None)),
228 }
229 }
230
231 pub async fn start(&self) -> Result<Vec<Arc<McpToolWrapper>>> {
233 {
235 let tools_lock = self.tools.read().await;
236 if let Some(tools) = tools_lock.as_ref() {
237 return Ok(tools.clone());
238 }
239 }
240
241 tracing::info!("Starting MCP server '{}' on demand", self.server_name);
243
244 let transport_config = self.server_config.to_transport_config()?;
245 let client = Arc::new(McpClient::connect(&self.server_name, transport_config).await?);
246
247 if !client.supports_tools().await {
249 return Err(anyhow!("MCP server '{}' does not support tools", self.server_name));
250 }
251
252 let mcp_tools = client.list_tools().await?;
253
254 let tools: Vec<Arc<McpToolWrapper>> = mcp_tools.into_iter()
256 .map(|tool| Arc::new(McpToolWrapper::new(
257 client.clone(),
258 tool,
259 self.server_name.clone()
260 )))
261 .collect();
262
263 {
265 let mut tools_lock = self.tools.write().await;
266 *tools_lock = Some(tools.clone());
267 }
268 {
269 let mut client_lock = self.client.write().await;
270 *client_lock = Some(client);
271 }
272
273 tracing::info!("MCP server '{}' started with {} tools", self.server_name, tools.len());
274
275 Ok(tools)
276 }
277
278 pub async fn shutdown(&self) -> Result<()> {
280 let client_lock = self.client.read().await;
281 if let Some(client) = client_lock.as_ref() {
282 client.shutdown().await?;
283 }
284 Ok(())
285 }
286
287 pub async fn is_started(&self) -> bool {
289 self.tools.read().await.is_some()
290 }
291
292 pub fn server_name(&self) -> &str {
294 &self.server_name
295 }
296}
297
298pub struct McpToolRegistry {
310 placeholders: HashMap<String, Arc<McpToolPlaceholder>>,
312}
313
314impl McpToolRegistry {
315 pub fn from_config(config: &McpConfig) -> Self {
317 let placeholders = config.servers
318 .iter()
319 .filter(|(_, cfg)| cfg.enabled)
320 .map(|(name, cfg)| {
321 (name.clone(), Arc::new(McpToolPlaceholder::new(
322 name.clone(),
323 cfg.clone()
324 )))
325 })
326 .collect();
327
328 Self { placeholders }
329 }
330
331 pub fn new() -> Self {
333 Self {
334 placeholders: HashMap::new(),
335 }
336 }
337
338 pub fn add_server(&mut self, name: String, config: McpServerConfig) {
340 self.placeholders.insert(
341 name.clone(),
342 Arc::new(McpToolPlaceholder::new(name, config))
343 );
344 }
345
346 pub async fn remove_server(&mut self, name: &str) -> Result<()> {
348 if let Some(placeholder) = self.placeholders.remove(name) {
349 placeholder.shutdown().await?;
350 }
351 Ok(())
352 }
353
354 pub fn get_server(&self, name: &str) -> Option<Arc<McpToolPlaceholder>> {
356 self.placeholders.get(name).cloned()
357 }
358
359 pub fn server_names(&self) -> Vec<&String> {
361 self.placeholders.keys().collect()
362 }
363
364 pub async fn started_servers(&self) -> Vec<String> {
366 let mut started = Vec::new();
367 for (name, placeholder) in &self.placeholders {
368 if placeholder.is_started().await {
369 started.push(name.clone());
370 }
371 }
372 started
373 }
374
375 pub async fn server_status(&self) -> HashMap<String, ServerStatus> {
377 let mut status = HashMap::new();
378 for (name, placeholder) in &self.placeholders {
379 let is_started = placeholder.is_started().await;
380 status.insert(name.clone(), ServerStatus {
381 name: name.clone(),
382 is_started,
383 tool_count: if is_started {
384 placeholder.tools.read().await.as_ref().map(|t| t.len()).unwrap_or(0)
385 } else {
386 0
387 },
388 });
389 }
390 status
391 }
392
393 pub async fn start_all(&self) -> Result<HashMap<String, Vec<Arc<McpToolWrapper>>>> {
395 let mut results = HashMap::new();
396
397 for (name, placeholder) in &self.placeholders {
398 let tools = placeholder.start().await?;
399 results.insert(name.clone(), tools);
400 }
401
402 Ok(results)
403 }
404
405 pub async fn shutdown_all(&self) {
407 for placeholder in self.placeholders.values() {
408 if let Err(e) = placeholder.shutdown().await {
409 tracing::error!("Failed to shutdown MCP server '{}': {}",
410 placeholder.server_name(), e);
411 }
412 }
413 }
414
415 pub fn add_from_cli_arg(&mut self, arg: &str) -> Result<()> {
427 let (name, command, args) = parse_cli_mcp_arg(arg)?;
428
429 let config = McpServerConfig {
430 command: Some(command),
431 args,
432 url: None,
433 enabled: true,
434 ..Default::default()
435 };
436
437 self.add_server(name, config);
438 Ok(())
439 }
440
441 pub fn add_from_cli_args(&mut self, args: &[String]) -> Result<()> {
443 for arg in args {
444 self.add_from_cli_arg(arg)?;
445 }
446 Ok(())
447 }
448}
449
450impl Default for McpToolRegistry {
451 fn default() -> Self {
452 Self::new()
453 }
454}
455
456#[derive(Debug, Clone)]
458pub struct ServerStatus {
459 pub name: String,
460 pub is_started: bool,
461 pub tool_count: usize,
462}
463
464fn parse_cli_mcp_arg(arg: &str) -> Result<(String, String, Vec<String>)> {
479 let arg = arg.trim();
480 if arg.is_empty() {
481 return Err(anyhow!("Empty MCP server argument"));
482 }
483
484 if let Some((name, rest)) = arg.split_once(':') {
486 let name = name.trim().to_string();
487 let rest = rest.trim();
488
489 let parts = shell_words::split(rest)
491 .map_err(|e| anyhow!("Failed to parse command: {}", e))?;
492
493 if parts.is_empty() {
494 return Err(anyhow!("Missing command for MCP server '{}'", name));
495 }
496
497 let command = parts[0].clone();
498 let args = parts[1..].to_vec();
499
500 return Ok((name, command, args));
501 }
502
503 let parts = shell_words::split(arg)
505 .map_err(|e| anyhow!("Failed to parse command: {}", e))?;
506
507 if parts.is_empty() {
508 return Err(anyhow!("Empty MCP server argument"));
509 }
510
511 let command = parts[0].clone();
512 let name = command.clone(); let args = parts[1..].to_vec();
514
515 Ok((name, command, args))
516}