//! MCP Server configuration structures use serde::{Deserialize, Serialize}; use std::collections::HashMap; /// MCP integration configuration #[derive(Debug, Clone, Default, Deserialize, Serialize)] pub struct McpConfig { /// Whether MCP integration is enabled #[serde(default)] pub enabled: bool, /// List of MCP servers to connect #[serde(default)] pub servers: Vec, } /// Configuration for a single MCP server #[derive(Debug, Clone, Deserialize, Serialize)] pub struct McpServerConfig { /// Unique name for this server (used in tool naming) pub name: String, /// Transport configuration pub transport: McpTransportConfig, /// Whether this server is enabled #[serde(default = "default_server_enabled")] pub enabled: bool, /// Optional description for the server #[serde(default)] pub description: Option, } fn default_server_enabled() -> bool { true } /// Transport configuration for connecting to MCP servers #[derive(Debug, Clone, Deserialize, Serialize)] #[serde(tag = "type", rename_all = "snake_case")] pub enum McpTransportConfig { /// Stdio transport: spawn a child process Stdio { /// Command to execute (e.g., "npx", "cargo") command: String, /// Arguments to pass to the command #[serde(default)] args: Vec, /// Optional environment variables to set #[serde(default)] env: HashMap, }, /// HTTP transport: connect to a remote server Http { /// URL of the MCP server endpoint url: String, /// Optional headers to include in requests #[serde(default)] headers: HashMap, }, } impl McpServerConfig { /// Create a stdio server config pub fn stdio(name: impl Into, command: impl Into, args: Vec) -> Self { Self { name: name.into(), transport: McpTransportConfig::Stdio { command: command.into(), args, env: HashMap::new(), }, enabled: true, description: None, } } /// Create an HTTP server config pub fn http(name: impl Into, url: impl Into) -> Self { Self { name: name.into(), transport: McpTransportConfig::Http { url: url.into(), headers: HashMap::new(), }, enabled: true, description: None, } } } impl McpConfig { /// Get enabled servers pub fn enabled_servers(&self) -> Vec<&McpServerConfig> { self.servers.iter().filter(|s| s.enabled).collect() } /// Check if there are any enabled servers pub fn has_enabled_servers(&self) -> bool { self.enabled && self.servers.iter().any(|s| s.enabled) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_stdio_config_creation() { let config = McpServerConfig::stdio( "filesystem", "npx", vec!["-y", "@modelcontextprotocol/server-filesystem", "/tmp"], ); assert_eq!(config.name, "filesystem"); assert!(config.enabled); assert!(matches!(config.transport, McpTransportConfig::Stdio { .. })); } #[test] fn test_http_config_creation() { let config = McpServerConfig::http("custom", "http://localhost:8000/mcp"); assert_eq!(config.name, "custom"); assert!(config.enabled); assert!(matches!(config.transport, McpTransportConfig::Http { .. })); } #[test] fn test_config_deserialization() { let json = r#"{ "enabled": true, "servers": [ { "name": "filesystem", "transport": { "type": "stdio", "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"] } }, { "name": "http-server", "enabled": false, "transport": { "type": "http", "url": "http://localhost:8000/mcp", "headers": { "Authorization": "Bearer token" } } } ] }"#; let config: McpConfig = serde_json::from_str(json).unwrap(); assert!(config.enabled); assert_eq!(config.servers.len(), 2); assert_eq!(config.enabled_servers().len(), 1); let fs_server = &config.servers[0]; assert_eq!(fs_server.name, "filesystem"); assert!(fs_server.enabled); let http_server = &config.servers[1]; assert_eq!(http_server.name, "http-server"); assert!(!http_server.enabled); } }