1use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5use std::path::PathBuf;
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct McpConfig {
10 pub enabled: bool,
12
13 pub default_timeout_ms: u64,
15
16 pub max_servers: usize,
18
19 pub max_retries: u32,
21
22 pub retry_delay_ms: u64,
24
25 pub servers: Vec<McpServerConfig>,
27
28 pub global_env: HashMap<String, String>,
30
31 pub work_dir: Option<PathBuf>,
33
34 pub debug_logging: bool,
36}
37
38#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct McpServerConfig {
41 pub name: String,
43
44 pub description: Option<String>,
46
47 pub command: String,
49
50 pub args: Vec<String>,
52
53 pub cwd: Option<PathBuf>,
55
56 pub enabled: bool,
58
59 pub timeout_ms: Option<u64>,
61
62 pub env: HashMap<String, String>,
64
65 pub auto_start: bool,
67
68 pub tags: Vec<String>,
70
71 pub priority: i32,
73
74 pub allow_filesystem: bool,
76
77 pub allow_network: bool,
79
80 pub max_execution_time_ms: u64,
82
83 pub trust_level: u8,
85}
86
87impl Default for McpConfig {
88 fn default() -> Self {
89 Self {
90 enabled: false,
91 default_timeout_ms: 30_000,
92 max_servers: 10,
93 max_retries: 3,
94 retry_delay_ms: 1_000,
95 servers: vec![],
96 global_env: HashMap::new(),
97 work_dir: None,
98 debug_logging: false,
99 }
100 }
101}
102
103impl Default for McpServerConfig {
104 fn default() -> Self {
105 Self {
106 name: String::new(),
107 description: None,
108 command: String::new(),
109 args: vec![],
110 cwd: None,
111 enabled: true,
112 timeout_ms: None,
113 env: HashMap::new(),
114 auto_start: true,
115 tags: vec![],
116 priority: 0,
117 allow_filesystem: false,
118 allow_network: false,
119 max_execution_time_ms: 30_000,
120 trust_level: 1,
121 }
122 }
123}
124
125impl McpConfig {
126 pub fn development() -> Self {
128 Self {
129 enabled: true,
130 default_timeout_ms: 30_000,
131 max_servers: 10,
132 max_retries: 3,
133 retry_delay_ms: 1_000,
134 servers: vec![],
135 global_env: HashMap::new(),
136 work_dir: Some(std::env::temp_dir()),
137 debug_logging: true,
138 }
139 }
140
141 pub fn production() -> Self {
143 Self {
144 enabled: true,
145 default_timeout_ms: 15_000,
146 max_servers: 5,
147 max_retries: 2,
148 retry_delay_ms: 2_000,
149 servers: vec![],
150 global_env: HashMap::new(),
151 work_dir: None,
152 debug_logging: false,
153 }
154 }
155
156 pub fn validate(&self) -> Result<(), String> {
158 if self.max_servers == 0 {
159 return Err("max_servers must be greater than 0".to_string());
160 }
161
162 if self.default_timeout_ms == 0 {
163 return Err("default_timeout_ms must be greater than 0".to_string());
164 }
165
166 for server in &self.servers {
168 server.validate()?;
169 }
170
171 let mut names = std::collections::HashSet::new();
173 for server in &self.servers {
174 if !names.insert(&server.name) {
175 return Err(format!("Duplicate server name: '{}'", server.name));
176 }
177 }
178
179 Ok(())
180 }
181
182 pub fn find_server(&self, name: &str) -> Option<&McpServerConfig> {
184 self.servers.iter().find(|s| s.name == name)
185 }
186
187 pub fn enabled_servers(&self) -> impl Iterator<Item = &McpServerConfig> {
189 self.servers.iter().filter(|s| s.enabled)
190 }
191}
192
193impl McpServerConfig {
194 pub fn validate(&self) -> Result<(), String> {
196 if self.name.is_empty() {
197 return Err("Server name cannot be empty".to_string());
198 }
199
200 if self.command.is_empty() {
201 return Err(format!("Server '{}' command cannot be empty", self.name));
202 }
203
204 if let Some(timeout) = self.timeout_ms {
205 if timeout == 0 {
206 return Err(format!(
207 "Server '{}' timeout must be greater than 0",
208 self.name
209 ));
210 }
211 }
212
213 if self.trust_level == 0 || self.trust_level > 5 {
214 return Err(format!(
215 "Server '{}' trust_level must be between 1 and 5",
216 self.name
217 ));
218 }
219
220 Ok(())
221 }
222
223 pub fn has_tag(&self, tag: &str) -> bool {
225 self.tags.contains(&tag.to_string())
226 }
227
228 pub fn display_name(&self) -> &str {
230 self.description.as_deref().unwrap_or(&self.name)
231 }
232}
233
234#[cfg(test)]
235mod tests {
236 use super::*;
237
238 #[test]
239 fn test_config_defaults() {
240 let config = McpConfig::default();
241 assert!(!config.enabled);
242 assert_eq!(config.max_servers, 10);
243
244 let dev_config = McpConfig::development();
245 assert!(dev_config.enabled);
246 assert!(dev_config.debug_logging);
247
248 let prod_config = McpConfig::production();
249 assert!(prod_config.enabled);
250 assert!(!prod_config.debug_logging);
251 assert_eq!(prod_config.default_timeout_ms, 15_000);
252 }
253
254 #[test]
255 fn test_server_config_validation() {
256 let mut server = McpServerConfig {
257 name: "test".to_string(),
258 command: "python".to_string(),
259 args: vec!["-m".to_string(), "mcp_server".to_string()],
260 ..Default::default()
261 };
262
263 assert!(server.validate().is_ok());
264
265 server.name.clear();
267 assert!(server.validate().is_err());
268
269 server.name = "test".to_string();
271 server.command.clear();
272 assert!(server.validate().is_err());
273
274 server.command = "python".to_string();
276 server.trust_level = 0;
277 assert!(server.validate().is_err());
278
279 server.trust_level = 6;
280 assert!(server.validate().is_err());
281
282 server.trust_level = 3;
283 assert!(server.validate().is_ok());
284 }
285
286 #[test]
287 fn test_config_validation() {
288 let mut config = McpConfig::development();
289 assert!(config.validate().is_ok());
290
291 config.max_servers = 0;
293 assert!(config.validate().is_err());
294
295 config.max_servers = 10;
297 config.servers = vec![
298 McpServerConfig {
299 name: "duplicate".to_string(),
300 command: "python".to_string(),
301 ..Default::default()
302 },
303 McpServerConfig {
304 name: "duplicate".to_string(),
305 command: "node".to_string(),
306 ..Default::default()
307 },
308 ];
309 assert!(config.validate().is_err());
310 }
311
312 #[test]
313 fn test_server_helpers() {
314 let server = McpServerConfig {
315 name: "test".to_string(),
316 description: Some("Test Server".to_string()),
317 command: "python".to_string(),
318 tags: vec!["development".to_string(), "testing".to_string()],
319 trust_level: 3,
320 ..Default::default()
321 };
322
323 assert!(server.has_tag("development"));
324 assert!(server.has_tag("testing"));
325 assert!(!server.has_tag("production"));
326 assert_eq!(server.display_name(), "Test Server");
327 }
328
329 #[test]
330 fn test_config_server_finding() {
331 let mut config = McpConfig::development();
332 config.servers = vec![
333 McpServerConfig {
334 name: "server1".to_string(),
335 command: "python".to_string(),
336 enabled: true,
337 ..Default::default()
338 },
339 McpServerConfig {
340 name: "server2".to_string(),
341 command: "node".to_string(),
342 enabled: false,
343 ..Default::default()
344 },
345 ];
346
347 assert!(config.find_server("server1").is_some());
348 assert!(config.find_server("server2").is_some());
349 assert!(config.find_server("nonexistent").is_none());
350
351 let enabled_servers: Vec<_> = config.enabled_servers().collect();
352 assert_eq!(enabled_servers.len(), 1);
353 assert_eq!(enabled_servers[0].name, "server1");
354 }
355}