1#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
7pub enum McpServerType {
8 #[default]
10 Stdio,
11 Sse,
13 WebSocket,
15}
16
17#[derive(Debug, Clone, Default)]
21pub struct McpServer {
22 pub name: String,
24 pub server_type: McpServerType,
26 pub command: Option<String>,
28 pub args: Vec<String>,
30 pub env: Vec<(String, String)>,
32 pub url: Option<String>,
34 pub enabled: bool,
36}
37
38impl McpServer {
39 pub fn stdio(name: impl Into<String>, command: impl Into<String>, args: Vec<&str>) -> Self {
51 Self {
52 name: name.into(),
53 server_type: McpServerType::Stdio,
54 command: Some(command.into()),
55 args: args.into_iter().map(String::from).collect(),
56 env: Vec::new(),
57 url: None,
58 enabled: true,
59 }
60 }
61
62 pub fn sse(name: impl Into<String>, url: impl Into<String>) -> Self {
73 Self {
74 name: name.into(),
75 server_type: McpServerType::Sse,
76 command: None,
77 args: Vec::new(),
78 env: Vec::new(),
79 url: Some(url.into()),
80 enabled: true,
81 }
82 }
83
84 pub fn with_env(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
86 self.env.push((key.into(), value.into()));
87 self
88 }
89
90 pub fn with_enabled(mut self, enabled: bool) -> Self {
92 self.enabled = enabled;
93 self
94 }
95}
96
97#[derive(Debug, Clone, PartialEq, Eq)]
99pub enum McpSource {
100 SpnGlobal,
102 SpnProject,
104 ClaudeCode,
106 Cursor,
108 VsCode,
110 Discovered,
112}
113
114#[derive(Debug, Clone, Default)]
116pub struct McpConfig {
117 pub servers: Vec<McpServer>,
119 pub source: Option<McpSource>,
121}
122
123impl McpConfig {
124 pub fn new() -> Self {
126 Self::default()
127 }
128
129 pub fn add_server(&mut self, server: McpServer) {
131 self.servers.push(server);
132 }
133
134 pub fn find_server(&self, name: &str) -> Option<&McpServer> {
136 self.servers.iter().find(|s| s.name == name)
137 }
138
139 pub fn enabled_servers(&self) -> impl Iterator<Item = &McpServer> {
141 self.servers.iter().filter(|s| s.enabled)
142 }
143
144 pub fn merge(&mut self, other: McpConfig) {
148 for server in other.servers {
149 self.servers.retain(|s| s.name != server.name);
151 self.servers.push(server);
152 }
153 }
154}
155
156#[cfg(test)]
157mod tests {
158 use super::*;
159
160 #[test]
161 fn test_stdio_server() {
162 let server = McpServer::stdio("test", "node", vec!["server.js"]);
163 assert_eq!(server.name, "test");
164 assert_eq!(server.server_type, McpServerType::Stdio);
165 assert_eq!(server.command, Some("node".to_string()));
166 assert_eq!(server.args, vec!["server.js"]);
167 assert!(server.enabled);
168 }
169
170 #[test]
171 fn test_sse_server() {
172 let server = McpServer::sse("remote", "http://localhost:3000");
173 assert_eq!(server.server_type, McpServerType::Sse);
174 assert_eq!(server.url, Some("http://localhost:3000".to_string()));
175 }
176
177 #[test]
178 fn test_server_with_env() {
179 let server = McpServer::stdio("neo4j", "npx", vec!["-y", "@anthropic/mcp-neo4j"])
180 .with_env("NEO4J_URI", "bolt://localhost:7687")
181 .with_env("NEO4J_PASSWORD", "secret");
182
183 assert_eq!(server.env.len(), 2);
184 assert_eq!(server.env[0], ("NEO4J_URI".to_string(), "bolt://localhost:7687".to_string()));
185 }
186
187 #[test]
188 fn test_config_add_find() {
189 let mut config = McpConfig::new();
190 config.add_server(McpServer::stdio("neo4j", "npx", vec![]));
191 config.add_server(McpServer::stdio("github", "npx", vec![]));
192
193 assert!(config.find_server("neo4j").is_some());
194 assert!(config.find_server("github").is_some());
195 assert!(config.find_server("unknown").is_none());
196 }
197
198 #[test]
199 fn test_config_enabled_servers() {
200 let mut config = McpConfig::new();
201 config.add_server(McpServer::stdio("enabled1", "cmd", vec![]));
202 config.add_server(McpServer::stdio("disabled", "cmd", vec![]).with_enabled(false));
203 config.add_server(McpServer::stdio("enabled2", "cmd", vec![]));
204
205 let enabled: Vec<_> = config.enabled_servers().collect();
206 assert_eq!(enabled.len(), 2);
207 assert!(enabled.iter().all(|s| s.enabled));
208 }
209
210 #[test]
211 fn test_config_merge() {
212 let mut config1 = McpConfig::new();
213 config1.add_server(McpServer::stdio("neo4j", "old-cmd", vec![]));
214 config1.add_server(McpServer::stdio("github", "gh-cmd", vec![]));
215
216 let mut config2 = McpConfig::new();
217 config2.add_server(McpServer::stdio("neo4j", "new-cmd", vec![])); config2.add_server(McpServer::stdio("slack", "slack-cmd", vec![])); config1.merge(config2);
221
222 assert_eq!(config1.servers.len(), 3);
223 assert_eq!(config1.find_server("neo4j").unwrap().command, Some("new-cmd".to_string()));
224 }
225}