use super::oauth_config::OAuthConfig;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(tag = "type")]
#[allow(clippy::large_enum_variant)]
pub enum McpServerConfig {
#[serde(rename = "builtin")]
Builtin {
name: String,
timeout_seconds: u64,
tools: Vec<String>,
},
#[serde(rename = "http")]
Http {
name: String,
url: String,
#[serde(skip_serializing_if = "Option::is_none")]
auth_token: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
oauth: Option<OAuthConfig>,
timeout_seconds: u64,
tools: Vec<String>,
},
#[serde(rename = "stdin")]
Stdin {
name: String,
command: String,
args: Vec<String>,
timeout_seconds: u64,
tools: Vec<String>,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Hash)]
pub enum McpConnectionType {
Builtin,
Stdin,
Http,
}
impl McpServerConfig {
pub fn name(&self) -> &str {
match self {
McpServerConfig::Builtin { name, .. } => name,
McpServerConfig::Http { name, .. } => name,
McpServerConfig::Stdin { name, .. } => name,
}
}
pub fn connection_type(&self) -> McpConnectionType {
match self {
McpServerConfig::Builtin { .. } => McpConnectionType::Builtin,
McpServerConfig::Http { .. } => McpConnectionType::Http,
McpServerConfig::Stdin { .. } => McpConnectionType::Stdin,
}
}
pub fn timeout_seconds(&self) -> u64 {
match self {
McpServerConfig::Builtin {
timeout_seconds, ..
} => *timeout_seconds,
McpServerConfig::Http {
timeout_seconds, ..
} => *timeout_seconds,
McpServerConfig::Stdin {
timeout_seconds, ..
} => *timeout_seconds,
}
}
pub fn tools(&self) -> &[String] {
match self {
McpServerConfig::Builtin { tools, .. } => tools,
McpServerConfig::Http { tools, .. } => tools,
McpServerConfig::Stdin { tools, .. } => tools,
}
}
pub fn url(&self) -> Option<&str> {
match self {
McpServerConfig::Http { url, .. } => Some(url),
_ => None,
}
}
pub fn auth_token(&self) -> Option<&str> {
match self {
McpServerConfig::Http { auth_token, .. } => auth_token.as_deref(),
_ => None,
}
}
pub fn oauth_config(&self) -> Option<&OAuthConfig> {
match self {
McpServerConfig::Http { oauth, .. } => oauth.as_ref(),
_ => None,
}
}
pub fn has_oauth_config(&self) -> bool {
self.oauth_config().is_some()
}
pub fn is_oauth_enabled(&self) -> bool {
self.oauth_config().is_some()
}
pub fn requires_auth(&self) -> bool {
self.auth_token().is_some() || self.is_oauth_enabled()
}
pub fn command(&self) -> Option<&str> {
match self {
McpServerConfig::Stdin { command, .. } => Some(command),
_ => None,
}
}
pub fn args(&self) -> &[String] {
match self {
McpServerConfig::Stdin { args, .. } => args,
_ => &[],
}
}
pub fn builtin(name: &str, timeout_seconds: u64, tools: Vec<String>) -> Self {
Self::Builtin {
name: name.to_string(),
timeout_seconds,
tools,
}
}
pub fn http(
name: &str,
url: &str,
timeout_seconds: u64,
tools: Vec<String>,
auth_token: Option<String>,
oauth: Option<OAuthConfig>,
) -> Self {
Self::Http {
name: name.to_string(),
url: url.to_string(),
auth_token,
oauth,
timeout_seconds,
tools,
}
}
pub fn stdin(
name: &str,
command: &str,
args: Vec<String>,
timeout_seconds: u64,
tools: Vec<String>,
) -> Self {
Self::Stdin {
name: name.to_string(),
command: command.to_string(),
args,
timeout_seconds,
tools,
}
}
pub fn validate(&self) -> Result<(), String> {
match self {
McpServerConfig::Builtin { name, .. } => {
if name.is_empty() {
return Err("Builtin server name cannot be empty".to_string());
}
}
McpServerConfig::Http {
name, url, oauth, ..
} => {
if name.is_empty() {
return Err("HTTP server name cannot be empty".to_string());
}
if url.is_empty() {
return Err("HTTP server URL cannot be empty".to_string());
}
if let Some(oauth_config) = oauth {
oauth_config
.validate()
.map_err(|e| format!("OAuth configuration validation failed: {}", e))?;
}
}
McpServerConfig::Stdin { name, command, .. } => {
if name.is_empty() {
return Err("Stdin server name cannot be empty".to_string());
}
if command.is_empty() {
return Err("Stdin server command cannot be empty".to_string());
}
}
}
Ok(())
}
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct McpConfig {
pub servers: Vec<McpServerConfig>,
pub allowed_tools: Vec<String>,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
pub struct RoleMcpConfig {
pub server_refs: Vec<String>,
pub allowed_tools: Vec<String>,
}
impl RoleMcpConfig {
pub fn is_enabled(&self) -> bool {
!self.server_refs.is_empty()
}
pub fn get_enabled_servers(&self, global_servers: &[McpServerConfig]) -> Vec<McpServerConfig> {
if self.server_refs.is_empty() {
return Vec::new();
}
let mut result = Vec::new();
for server_name in &self.server_refs {
if let Some(server_config) = global_servers.iter().find(|s| s.name() == *server_name) {
let mut server = server_config.clone();
if !self.allowed_tools.is_empty() {
let filtered_tools = self.expand_patterns_for_server(server_name);
server = match server {
McpServerConfig::Builtin {
name,
timeout_seconds,
..
} => McpServerConfig::Builtin {
name,
timeout_seconds,
tools: filtered_tools,
},
McpServerConfig::Http {
name,
url,
auth_token,
oauth,
timeout_seconds,
tools: _,
} => McpServerConfig::Http {
name,
url,
auth_token,
oauth,
timeout_seconds,
tools: filtered_tools,
},
McpServerConfig::Stdin {
name,
command,
args,
timeout_seconds,
..
} => McpServerConfig::Stdin {
name,
command,
args,
timeout_seconds,
tools: filtered_tools,
},
};
}
result.push(server);
} else {
println!(
"DEBUG: Server '{server_name}' referenced by role but not found in global registry"
);
}
}
result
}
fn expand_patterns_for_server(&self, server_name: &str) -> Vec<String> {
let mut expanded_tools = Vec::new();
for pattern in &self.allowed_tools {
if let Some((server_prefix, tool_pattern)) = pattern.split_once(':') {
if server_prefix == server_name {
if tool_pattern == "*" {
return Vec::new();
} else if tool_pattern.ends_with('*') {
expanded_tools.push(tool_pattern.to_string());
} else {
expanded_tools.push(tool_pattern.to_string());
}
}
} else {
expanded_tools.push(pattern.clone());
}
}
expanded_tools
}
}