1pub mod client;
68pub mod config;
69pub mod error;
70pub mod server;
71pub mod transport;
72
73pub use client::McpClient;
75pub use config::Config;
76pub use error::{Error, Result};
77pub use server::{ServerId, ServerProcess, ServerStatus};
78
79use std::collections::HashMap;
80use std::path::Path;
81use tracing;
82use transport::StdioTransport; pub struct McpRunner {
90 config: Config,
92 servers: HashMap<ServerId, ServerProcess>,
94 server_names: HashMap<String, ServerId>,
96}
97
98impl McpRunner {
99 #[tracing::instrument(skip(path), fields(config_path = ?path.as_ref()))]
103 pub fn from_config_file(path: impl AsRef<Path>) -> Result<Self> {
104 tracing::info!("Loading configuration from file");
105 let config = Config::from_file(path)?;
106 Ok(Self::new(config))
107 }
108
109 #[tracing::instrument(skip(config))]
113 pub fn from_config_str(config: &str) -> Result<Self> {
114 tracing::info!("Loading configuration from string");
115 let config = Config::parse_from_str(config)?;
116 Ok(Self::new(config))
117 }
118
119 #[tracing::instrument(skip(config), fields(num_servers = config.mcp_servers.len()))]
123 pub fn new(config: Config) -> Self {
124 tracing::info!("Creating new McpRunner");
125 Self {
126 config,
127 servers: HashMap::new(),
128 server_names: HashMap::new(),
129 }
130 }
131
132 #[tracing::instrument(skip(self), fields(server_name = %name))]
136 pub async fn start_server(&mut self, name: &str) -> Result<ServerId> {
137 if let Some(id) = self.server_names.get(name) {
139 tracing::debug!(server_id = %id, "Server already running");
140 return Ok(*id);
141 }
142
143 tracing::info!("Attempting to start server");
144 let config = self
146 .config
147 .mcp_servers
148 .get(name)
149 .ok_or_else(|| {
150 tracing::error!("Configuration not found for server");
151 Error::ServerNotFound(name.to_string())
152 })?
153 .clone();
154
155 let mut server = ServerProcess::new(name.to_string(), config);
157 let id = server.id();
158 tracing::debug!(server_id = %id, "Created ServerProcess instance");
159
160 server.start().await.map_err(|e| {
161 tracing::error!(error = %e, "Failed to start server process");
162 e
163 })?;
164
165 tracing::debug!(server_id = %id, "Storing running server process");
167 self.servers.insert(id, server);
168 self.server_names.insert(name.to_string(), id);
169
170 tracing::info!(server_id = %id, "Server started successfully");
171 Ok(id)
172 }
173
174 #[tracing::instrument(skip(self))]
178 pub async fn start_all_servers(&mut self) -> Result<Vec<ServerId>> {
179 tracing::info!("Starting all configured servers");
180 let server_names: Vec<String> = self
182 .config
183 .mcp_servers
184 .keys()
185 .map(|k| k.to_string())
186 .collect();
187 tracing::debug!(servers_to_start = ?server_names);
188
189 let mut ids = Vec::new();
190 let mut errors = Vec::new();
191
192 for name in server_names {
193 match self.start_server(&name).await {
194 Ok(id) => ids.push(id),
195 Err(e) => {
196 tracing::error!(server_name = %name, error = %e, "Failed to start server");
197 errors.push((name, e));
198 }
199 }
200 }
201
202 if !errors.is_empty() {
203 tracing::warn!(num_failed = errors.len(), "Some servers failed to start");
204 return Err(errors.remove(0).1);
205 }
206
207 tracing::info!(num_started = ids.len(), "Finished starting all servers");
208 Ok(ids)
209 }
210
211 #[tracing::instrument(skip(self), fields(server_id = %id))]
215 pub async fn stop_server(&mut self, id: ServerId) -> Result<()> {
216 tracing::info!("Attempting to stop server");
217 if let Some(mut server) = self.servers.remove(&id) {
218 let name = server.name().to_string();
219 tracing::debug!(server_name = %name, "Found server process to stop");
220 self.server_names.remove(&name);
221
222 server.stop().await.map_err(|e| {
223 tracing::error!(error = %e, "Failed to stop server process");
224 e
225 })?;
226
227 tracing::info!("Server stopped successfully");
228 Ok(())
229 } else {
230 tracing::warn!("Attempted to stop a server that was not found or not running");
231 Err(Error::ServerNotFound(format!("{:?}", id)))
232 }
233 }
234
235 #[tracing::instrument(skip(self), fields(server_id = %id))]
239 pub fn server_status(&self, id: ServerId) -> Result<ServerStatus> {
240 tracing::debug!("Getting server status");
241 self.servers
242 .get(&id)
243 .map(|server| {
244 let status = server.status();
245 tracing::trace!(status = ?status);
246 status
247 })
248 .ok_or_else(|| {
249 tracing::warn!("Status requested for unknown server");
250 Error::ServerNotFound(format!("{:?}", id))
251 })
252 }
253
254 #[tracing::instrument(skip(self), fields(server_name = %name))]
258 pub fn get_server_id(&self, name: &str) -> Result<ServerId> {
259 tracing::debug!("Getting server ID by name");
260 self.server_names.get(name).copied().ok_or_else(|| {
261 tracing::warn!("Server ID requested for unknown server name");
262 Error::ServerNotFound(name.to_string())
263 })
264 }
265
266 #[tracing::instrument(skip(self), fields(server_id = %id))]
270 pub fn get_client(&mut self, id: ServerId) -> Result<McpClient> {
271 tracing::info!("Getting client for server");
272 let server = self.servers.get_mut(&id).ok_or_else(|| {
273 tracing::error!("Client requested for unknown or stopped server");
274 Error::ServerNotFound(format!("{:?}", id))
275 })?;
276 let server_name = server.name().to_string();
277 tracing::debug!(server_name = %server_name, "Found server process");
278
279 tracing::debug!("Taking stdin/stdout from server process");
280 let stdin = server.take_stdin().map_err(|e| {
281 tracing::error!(error = %e, "Failed to take stdin from server");
282 e
283 })?;
284 let stdout = server.take_stdout().map_err(|e| {
285 tracing::error!(error = %e, "Failed to take stdout from server");
286 e
287 })?;
288
289 tracing::debug!("Creating StdioTransport and McpClient");
290 let transport = StdioTransport::new(server_name.clone(), stdin, stdout);
291 let client = McpClient::new(server_name, transport);
292
293 tracing::info!("Client created successfully");
294 Ok(client)
295 }
296}