1use anyhow::{Result, anyhow};
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::approval::RiskLevel;
16use crate::tools::{Tool, ToolDefinition};
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!(
78 "Starting lazy MCP server '{}' for tool '{}'",
79 self.server_name,
80 self.definition.name
81 );
82
83 let transport_config = self.server_config.to_transport_config()?;
84 let client = Arc::new(McpClient::connect(&self.server_name, transport_config).await?);
85
86 let mcp_tools = client.list_tools().await?;
88
89 let tool_name_without_prefix = self.definition.name.clone();
91 let mcp_tool = mcp_tools
92 .into_iter()
93 .find(|t| {
94 let name = t.name.clone();
96 name == tool_name_without_prefix
97 || name == format!("{}_{}", self.server_name, tool_name_without_prefix)
98 })
99 .ok_or_else(|| {
100 anyhow!(
101 "Tool '{}' not found in MCP server '{}'",
102 self.definition.name,
103 self.server_name
104 )
105 })?;
106
107 let wrapper = Arc::new(McpToolWrapper::new(
109 client.clone(),
110 mcp_tool,
111 self.server_name.clone(),
112 ));
113
114 {
116 let mut tool_lock = self.actual_tool.lock().await;
117 *tool_lock = Some(wrapper.clone());
118 }
119 {
120 let mut client_lock = self.client.lock().await;
121 *client_lock = Some(client);
122 }
123
124 tracing::info!(
125 "Lazy MCP server '{}' started successfully",
126 self.server_name
127 );
128
129 Ok(wrapper)
130 }
131
132 pub async fn shutdown(&self) -> Result<()> {
134 let client_lock = self.client.lock().await;
135 if let Some(client) = client_lock.as_ref() {
136 client.shutdown().await?;
137 tracing::info!("Lazy MCP server '{}' stopped", self.server_name);
138 }
139 Ok(())
140 }
141}
142
143#[async_trait]
144impl Tool for LazyMcpTool {
145 fn definition(&self) -> ToolDefinition {
146 self.definition.clone()
147 }
148
149 async fn execute(&self, params: Value) -> Result<String> {
150 let tool = self.ensure_started().await?;
152
153 tool.execute(params).await
155 }
156
157 fn risk_level(&self) -> RiskLevel {
158 RiskLevel::Mutating
160 }
161}
162
163pub struct LazyMcpLoader {
169 config: McpConfig,
171
172 #[allow(dead_code)]
174 tools: Vec<LazyMcpTool>,
175}
176
177impl LazyMcpLoader {
178 pub fn from_config(config: McpConfig) -> Self {
180 let tools = Vec::new();
181
182 Self { config, tools }
187 }
188
189 pub async fn discover_tools(&self) -> Result<Vec<LazyMcpTool>> {
192 Ok(Vec::new())
200 }
201
202 pub fn config(&self) -> &McpConfig {
204 &self.config
205 }
206}
207
208pub struct McpToolPlaceholder {
214 server_name: String,
216
217 server_config: McpServerConfig,
219
220 tools: Arc<RwLock<Option<Vec<Arc<McpToolWrapper>>>>>,
222
223 client: Arc<RwLock<Option<Arc<McpClient>>>>,
225}
226
227impl McpToolPlaceholder {
228 pub fn new(server_name: String, server_config: McpServerConfig) -> Self {
230 Self {
231 server_name,
232 server_config,
233 tools: Arc::new(RwLock::new(None)),
234 client: Arc::new(RwLock::new(None)),
235 }
236 }
237
238 pub async fn start(&self) -> Result<Vec<Arc<McpToolWrapper>>> {
240 {
242 let tools_lock = self.tools.read().await;
243 if let Some(tools) = tools_lock.as_ref() {
244 return Ok(tools.clone());
245 }
246 }
247
248 tracing::info!("Starting MCP server '{}' on demand", self.server_name);
250
251 let transport_config = self.server_config.to_transport_config()?;
252 let client = Arc::new(McpClient::connect(&self.server_name, transport_config).await?);
253
254 if !client.supports_tools().await {
256 return Err(anyhow!(
257 "MCP server '{}' does not support tools",
258 self.server_name
259 ));
260 }
261
262 let mcp_tools = client.list_tools().await?;
263
264 let tools: Vec<Arc<McpToolWrapper>> = mcp_tools
266 .into_iter()
267 .map(|tool| {
268 Arc::new(McpToolWrapper::new(
269 client.clone(),
270 tool,
271 self.server_name.clone(),
272 ))
273 })
274 .collect();
275
276 {
278 let mut tools_lock = self.tools.write().await;
279 *tools_lock = Some(tools.clone());
280 }
281 {
282 let mut client_lock = self.client.write().await;
283 *client_lock = Some(client);
284 }
285
286 tracing::info!(
287 "MCP server '{}' started with {} tools",
288 self.server_name,
289 tools.len()
290 );
291
292 Ok(tools)
293 }
294
295 pub async fn shutdown(&self) -> Result<()> {
297 let client_lock = self.client.read().await;
298 if let Some(client) = client_lock.as_ref() {
299 client.shutdown().await?;
300 }
301 Ok(())
302 }
303
304 pub async fn is_started(&self) -> bool {
306 self.tools.read().await.is_some()
307 }
308
309 pub fn server_name(&self) -> &str {
311 &self.server_name
312 }
313}
314
315pub struct McpToolRegistry {
327 placeholders: HashMap<String, Arc<McpToolPlaceholder>>,
329}
330
331impl McpToolRegistry {
332 pub fn from_config(config: &McpConfig) -> Self {
334 let placeholders = config
335 .servers
336 .iter()
337 .filter(|(_, cfg)| cfg.enabled)
338 .map(|(name, cfg)| {
339 (
340 name.clone(),
341 Arc::new(McpToolPlaceholder::new(name.clone(), cfg.clone())),
342 )
343 })
344 .collect();
345
346 Self { placeholders }
347 }
348
349 pub fn new() -> Self {
351 Self {
352 placeholders: HashMap::new(),
353 }
354 }
355
356 pub fn add_server(&mut self, name: String, config: McpServerConfig) {
358 self.placeholders.insert(
359 name.clone(),
360 Arc::new(McpToolPlaceholder::new(name, config)),
361 );
362 }
363
364 pub async fn remove_server(&mut self, name: &str) -> Result<()> {
366 if let Some(placeholder) = self.placeholders.remove(name) {
367 placeholder.shutdown().await?;
368 }
369 Ok(())
370 }
371
372 pub fn get_server(&self, name: &str) -> Option<Arc<McpToolPlaceholder>> {
374 self.placeholders.get(name).cloned()
375 }
376
377 pub fn server_names(&self) -> Vec<&String> {
379 self.placeholders.keys().collect()
380 }
381
382 pub async fn started_servers(&self) -> Vec<String> {
384 let mut started = Vec::new();
385 for (name, placeholder) in &self.placeholders {
386 if placeholder.is_started().await {
387 started.push(name.clone());
388 }
389 }
390 started
391 }
392
393 pub async fn server_status(&self) -> HashMap<String, ServerStatus> {
395 let mut status = HashMap::new();
396 for (name, placeholder) in &self.placeholders {
397 let is_started = placeholder.is_started().await;
398 status.insert(
399 name.clone(),
400 ServerStatus {
401 name: name.clone(),
402 is_started,
403 tool_count: if is_started {
404 placeholder
405 .tools
406 .read()
407 .await
408 .as_ref()
409 .map(|t| t.len())
410 .unwrap_or(0)
411 } else {
412 0
413 },
414 },
415 );
416 }
417 status
418 }
419
420 pub async fn start_all(&self) -> Result<HashMap<String, Vec<Arc<McpToolWrapper>>>> {
422 let mut results = HashMap::new();
423
424 for (name, placeholder) in &self.placeholders {
425 let tools = placeholder.start().await?;
426 results.insert(name.clone(), tools);
427 }
428
429 Ok(results)
430 }
431
432 pub async fn shutdown_all(&self) {
434 for placeholder in self.placeholders.values() {
435 if let Err(e) = placeholder.shutdown().await {
436 tracing::error!(
437 "Failed to shutdown MCP server '{}': {}",
438 placeholder.server_name(),
439 e
440 );
441 }
442 }
443 }
444
445 pub fn add_from_cli_arg(&mut self, arg: &str) -> Result<()> {
457 let (name, command, args) = parse_cli_mcp_arg(arg)?;
458
459 let config = McpServerConfig {
460 command: Some(command),
461 args,
462 url: None,
463 enabled: true,
464 ..Default::default()
465 };
466
467 self.add_server(name, config);
468 Ok(())
469 }
470
471 pub fn add_from_cli_args(&mut self, args: &[String]) -> Result<()> {
473 for arg in args {
474 self.add_from_cli_arg(arg)?;
475 }
476 Ok(())
477 }
478}
479
480impl Default for McpToolRegistry {
481 fn default() -> Self {
482 Self::new()
483 }
484}
485
486#[derive(Debug, Clone)]
488pub struct ServerStatus {
489 pub name: String,
490 pub is_started: bool,
491 pub tool_count: usize,
492}
493
494fn parse_cli_mcp_arg(arg: &str) -> Result<(String, String, Vec<String>)> {
509 let arg = arg.trim();
510 if arg.is_empty() {
511 return Err(anyhow!("Empty MCP server argument"));
512 }
513
514 if let Some((name, rest)) = arg.split_once(':') {
516 let name = name.trim().to_string();
517 let rest = rest.trim();
518
519 let parts =
521 shell_words::split(rest).map_err(|e| anyhow!("Failed to parse command: {}", e))?;
522
523 if parts.is_empty() {
524 return Err(anyhow!("Missing command for MCP server '{}'", name));
525 }
526
527 let command = parts[0].clone();
528 let args = parts[1..].to_vec();
529
530 return Ok((name, command, args));
531 }
532
533 let parts = shell_words::split(arg).map_err(|e| anyhow!("Failed to parse command: {}", e))?;
535
536 if parts.is_empty() {
537 return Err(anyhow!("Empty MCP server argument"));
538 }
539
540 let command = parts[0].clone();
541 let name = command.clone(); let args = parts[1..].to_vec();
543
544 Ok((name, command, args))
545}