use indexmap::IndexMap;
use crate::source::Spanned;
#[derive(Debug, Clone, Default)]
pub struct RawMcpConfig {
pub servers: IndexMap<Spanned<String>, Spanned<RawMcpServer>>,
}
#[derive(Debug, Clone, Default)]
pub struct RawMcpServer {
pub command: Option<Spanned<String>>,
pub args: Option<Spanned<Vec<Spanned<String>>>>,
pub env: Option<Spanned<IndexMap<Spanned<String>, Spanned<String>>>>,
pub cwd: Option<Spanned<String>>,
pub url: Option<Spanned<String>>,
pub transport: Option<Spanned<String>>,
}
impl RawMcpConfig {
pub fn new() -> Self {
Self::default()
}
pub fn server_count(&self) -> usize {
self.servers.len()
}
pub fn get_server(&self, name: &str) -> Option<&Spanned<RawMcpServer>> {
self.servers
.iter()
.find(|(k, _)| k.value == name)
.map(|(_, v)| v)
}
pub fn has_server(&self, name: &str) -> bool {
self.get_server(name).is_some()
}
pub fn server_names(&self) -> impl Iterator<Item = &str> {
self.servers.keys().map(|k| k.value.as_str())
}
}
impl RawMcpServer {
pub fn with_command(command: impl Into<String>) -> Self {
Self {
command: Some(Spanned::dummy(command.into())),
..Default::default()
}
}
pub fn with_url(url: impl Into<String>) -> Self {
Self {
url: Some(Spanned::dummy(url.into())),
transport: Some(Spanned::dummy("sse".to_string())),
..Default::default()
}
}
pub fn is_sse(&self) -> bool {
self.url.is_some()
|| self
.transport
.as_ref()
.map(|t| t.value == "sse")
.unwrap_or(false)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::source::{FileId, Span};
fn make_span(start: u32, end: u32) -> Span {
Span::new(FileId(0), start, end)
}
#[test]
fn test_mcp_config_empty() {
let config = RawMcpConfig::new();
assert_eq!(config.server_count(), 0);
assert!(!config.has_server("novanet"));
}
#[test]
fn test_mcp_config_with_servers() {
let mut config = RawMcpConfig::new();
config.servers.insert(
Spanned::new("novanet".to_string(), make_span(0, 7)),
Spanned::new(
RawMcpServer::with_command("cargo run -p novanet-mcp"),
make_span(10, 50),
),
);
config.servers.insert(
Spanned::new("external".to_string(), make_span(60, 68)),
Spanned::new(
RawMcpServer::with_url("http://localhost:8080"),
make_span(70, 100),
),
);
assert_eq!(config.server_count(), 2);
assert!(config.has_server("novanet"));
assert!(config.has_server("external"));
assert!(!config.has_server("unknown"));
let names: Vec<&str> = config.server_names().collect();
assert!(names.contains(&"novanet"));
assert!(names.contains(&"external"));
}
#[test]
fn test_mcp_server_sse() {
let stdio_server = RawMcpServer::with_command("cargo run");
assert!(!stdio_server.is_sse());
let sse_server = RawMcpServer::with_url("http://localhost:8080");
assert!(sse_server.is_sse());
}
}