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!(
185 server.env[0],
186 ("NEO4J_URI".to_string(), "bolt://localhost:7687".to_string())
187 );
188 }
189
190 #[test]
191 fn test_config_add_find() {
192 let mut config = McpConfig::new();
193 config.add_server(McpServer::stdio("neo4j", "npx", vec![]));
194 config.add_server(McpServer::stdio("github", "npx", vec![]));
195
196 assert!(config.find_server("neo4j").is_some());
197 assert!(config.find_server("github").is_some());
198 assert!(config.find_server("unknown").is_none());
199 }
200
201 #[test]
202 fn test_config_enabled_servers() {
203 let mut config = McpConfig::new();
204 config.add_server(McpServer::stdio("enabled1", "cmd", vec![]));
205 config.add_server(McpServer::stdio("disabled", "cmd", vec![]).with_enabled(false));
206 config.add_server(McpServer::stdio("enabled2", "cmd", vec![]));
207
208 let enabled: Vec<_> = config.enabled_servers().collect();
209 assert_eq!(enabled.len(), 2);
210 assert!(enabled.iter().all(|s| s.enabled));
211 }
212
213 #[test]
214 fn test_config_merge() {
215 let mut config1 = McpConfig::new();
216 config1.add_server(McpServer::stdio("neo4j", "old-cmd", vec![]));
217 config1.add_server(McpServer::stdio("github", "gh-cmd", vec![]));
218
219 let mut config2 = McpConfig::new();
220 config2.add_server(McpServer::stdio("neo4j", "new-cmd", vec![])); config2.add_server(McpServer::stdio("slack", "slack-cmd", vec![])); config1.merge(config2);
224
225 assert_eq!(config1.servers.len(), 3);
226 assert_eq!(
227 config1.find_server("neo4j").unwrap().command,
228 Some("new-cmd".to_string())
229 );
230 }
231}