mermaid_cli/mcp/
server_manager.rs1use anyhow::{Result, anyhow};
7use std::collections::HashMap;
8use tracing::{info, warn};
9
10use super::client::{ContentBlock, McpClient, McpToolDef, McpToolResult};
11use super::transport::StdioTransport;
12use crate::app::McpServerConfig;
13
14pub struct McpServerManager {
16 servers: HashMap<String, McpClient>,
18 tools: Vec<(String, McpToolDef)>,
20}
21
22impl McpServerManager {
23 pub async fn start(configs: &HashMap<String, McpServerConfig>) -> Self {
27 let mut servers = HashMap::new();
28 let mut all_tools = Vec::new();
29
30 for (name, config) in configs {
31 info!(
32 "Starting MCP server: {} ({} {})",
33 name,
34 config.command,
35 config.args.join(" ")
36 );
37
38 match Self::start_one(name, config).await {
39 Ok((client, tools)) => {
40 let tool_count = tools.len();
41 for tool in &tools {
42 all_tools.push((name.clone(), tool.clone()));
43 }
44 info!(
45 "MCP server '{}' ready: {} tools ({})",
46 name,
47 tool_count,
48 client
49 .server_info
50 .as_ref()
51 .map(|s| s.name.as_str())
52 .unwrap_or("?")
53 );
54 servers.insert(name.clone(), client);
55 },
56 Err(e) => {
57 warn!("Failed to start MCP server '{}': {}", name, e);
58 },
59 }
60 }
61
62 Self {
63 servers,
64 tools: all_tools,
65 }
66 }
67
68 async fn start_one(
70 name: &str,
71 config: &McpServerConfig,
72 ) -> Result<(McpClient, Vec<McpToolDef>)> {
73 let transport = StdioTransport::spawn(&config.command, &config.args, &config.env).await?;
74 let mut client = McpClient::new(transport);
75
76 client
77 .initialize()
78 .await
79 .map_err(|e| anyhow!("MCP server '{}' initialization failed: {}", name, e))?;
80
81 let tools = client
82 .list_tools()
83 .await
84 .map_err(|e| anyhow!("MCP server '{}' tool discovery failed: {}", name, e))?;
85
86 Ok((client, tools))
87 }
88
89 pub fn get_all_tools(&self) -> &[(String, McpToolDef)] {
91 &self.tools
92 }
93
94 pub fn has_servers(&self) -> bool {
96 !self.servers.is_empty()
97 }
98
99 pub async fn call_tool(
110 &self,
111 server_name: &str,
112 tool_name: &str,
113 arguments: &serde_json::Value,
114 ) -> Result<McpToolResult> {
115 let client = self
116 .servers
117 .get(server_name)
118 .ok_or_else(|| anyhow!("MCP server '{}' not found or not running", server_name))?;
119
120 client.call_tool(tool_name, arguments).await
121 }
122
123 pub fn format_tool_result(result: &McpToolResult) -> (String, Option<Vec<String>>) {
129 let mut text_parts = Vec::new();
130 let mut images = Vec::new();
131
132 for block in &result.content {
133 match block {
134 ContentBlock::Text(text) => text_parts.push(text.clone()),
135 ContentBlock::Image { data, .. } => images.push(data.clone()),
136 ContentBlock::Audio { data, mime_type } => {
137 images.push(data.clone());
138 text_parts.push(format!("[audio attachment: {}]", mime_type));
139 },
140 ContentBlock::ResourceLink {
141 uri,
142 name,
143 description,
144 mime_type,
145 } => {
146 let label = name.as_deref().unwrap_or(uri.as_str());
147 let desc = description.as_deref().unwrap_or("");
148 let mime = mime_type.as_deref().unwrap_or("");
149 text_parts.push(format!(
150 "[resource link: {} ({}) — {} → {}]",
151 label, mime, desc, uri
152 ));
153 },
154 ContentBlock::Resource {
155 uri,
156 mime_type,
157 text,
158 blob,
159 } => {
160 let mime = mime_type.as_deref().unwrap_or("");
161 if let Some(t) = text {
162 text_parts.push(format!("[resource {}]:\n{}", uri, t));
163 } else if let Some(b) = blob {
164 text_parts.push(format!(
165 "[resource {} ({}): {} bytes of base64]",
166 uri,
167 mime,
168 b.len()
169 ));
170 } else {
171 text_parts.push(format!("[resource {} ({})]", uri, mime));
172 }
173 },
174 }
175 }
176
177 let text = if text_parts.is_empty() {
178 if result.is_error {
179 "MCP tool returned an error with no message".to_string()
180 } else {
181 "MCP tool returned no text content".to_string()
182 }
183 } else {
184 text_parts.join("\n")
185 };
186
187 let images = if images.is_empty() {
188 None
189 } else {
190 Some(images)
191 };
192
193 (text, images)
194 }
195
196 pub async fn shutdown(&self) {
198 for (name, client) in &self.servers {
199 info!("Shutting down MCP server: {}", name);
200 client.shutdown().await;
201 }
202 }
203}