use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct McpConfig {
#[serde(default)]
pub mcp_servers: HashMap<String, ServerEntry>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub settings: Option<McpSettings>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ServerEntry {
#[serde(default)]
pub command: Option<String>,
#[serde(default)]
pub args: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub env: Option<HashMap<String, String>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cwd: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub lifecycle: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub idle_timeout: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub direct_tools: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub exclude_tools: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub url: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub headers: Option<HashMap<String, String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct McpSettings {
#[serde(default = "default_tool_prefix")]
pub tool_prefix: String,
#[serde(default = "default_idle_timeout")]
pub idle_timeout: u64,
#[serde(default)]
pub direct_tools: bool,
}
fn default_tool_prefix() -> String {
"server".to_string()
}
fn default_idle_timeout() -> u64 {
10
}
impl Default for McpSettings {
fn default() -> Self {
Self {
tool_prefix: default_tool_prefix(),
idle_timeout: default_idle_timeout(),
direct_tools: false,
}
}
}
pub fn format_tool_name(tool_name: &str, server_name: &str, prefix_mode: &str) -> String {
match prefix_mode {
"none" => tool_name.to_string(),
"short" => {
let short = server_name
.trim_end_matches("mcp")
.trim_end_matches("-mcp")
.trim_end_matches('_');
let p = short.replace('-', "_");
if p.is_empty() {
tool_name.to_string()
} else {
format!("{}_{}", p, tool_name)
}
}
_ => {
let p = server_name.replace('-', "_");
format!("{}_{}", p, tool_name)
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CachedTool {
pub name: String,
pub description: Option<String>,
pub input_schema: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerCacheEntry {
pub config_hash: u64,
pub tools: Vec<CachedTool>,
pub cached_at: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MetadataCache {
pub version: u32,
pub servers: HashMap<String, ServerCacheEntry>,
}