PicoBot/src/mcp/config.rs

173 lines
4.9 KiB
Rust

//! 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<McpServerConfig>,
}
/// 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<String>,
}
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<String>,
/// Optional environment variables to set
#[serde(default)]
env: HashMap<String, String>,
},
/// 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<String, String>,
},
}
impl McpServerConfig {
/// Create a stdio server config
pub fn stdio(name: impl Into<String>, command: impl Into<String>, args: Vec<String>) -> 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<String>, url: impl Into<String>) -> 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);
}
}