use anyhow::{Result, bail};
use serde::Deserialize;
use std::collections::HashMap;
use super::args::ConvertArgs;
#[derive(Debug, Clone)]
pub enum McpConfigSource {
DirectUrl { url: String },
RemoteService {
name: String,
url: String,
protocol: Option<crate::client::protocol::McpProtocol>,
headers: HashMap<String, String>,
timeout: Option<u64>,
},
LocalCommand {
name: String,
command: String,
args: Vec<String>,
env: HashMap<String, String>,
},
}
#[derive(Deserialize, Debug)]
struct McpConfig {
#[serde(rename = "mcpServers")]
mcp_servers: HashMap<String, McpServerInnerConfig>,
}
#[derive(Deserialize, Debug, Clone)]
#[serde(untagged)]
enum McpServerInnerConfig {
Command(StdioConfig),
Url(UrlConfig),
}
#[derive(Deserialize, Debug, Clone)]
struct StdioConfig {
command: String,
args: Option<Vec<String>>,
env: Option<HashMap<String, String>>,
}
#[derive(Deserialize, Debug, Clone)]
struct UrlConfig {
#[serde(skip_serializing_if = "Option::is_none")]
url: Option<String>,
#[serde(
skip_serializing_if = "Option::is_none",
default,
rename = "baseUrl",
alias = "baseurl",
alias = "base_url"
)]
base_url: Option<String>,
#[serde(default, rename = "type", alias = "Type")]
r#type: Option<String>,
pub headers: Option<HashMap<String, String>>,
#[serde(default, alias = "authToken", alias = "auth_token")]
pub auth_token: Option<String>,
pub timeout: Option<u64>,
}
impl UrlConfig {
fn get_url(&self) -> Option<&str> {
self.url.as_deref().or(self.base_url.as_deref())
}
}
pub fn parse_convert_config(args: &ConvertArgs) -> Result<McpConfigSource> {
if let Some(ref url) = args.url {
return Ok(McpConfigSource::DirectUrl { url: url.clone() });
}
let json_str = if let Some(ref config) = args.config {
config.clone()
} else if let Some(ref path) = args.config_file {
std::fs::read_to_string(path).map_err(|e| anyhow::anyhow!("读取配置文件失败: {}", e))?
} else {
bail!("必须提供 URL、--config 或 --config-file 参数之一");
};
let mcp_config: McpConfig = serde_json::from_str(&json_str).map_err(|e| {
anyhow::anyhow!(
"配置解析失败: {}。配置必须是标准 MCP 格式,包含 mcpServers 字段",
e
)
})?;
let servers = mcp_config.mcp_servers;
if servers.is_empty() {
bail!("配置中没有找到任何 MCP 服务");
}
let (name, inner_config) = if let Some(ref name) = args.name {
let config = servers.get(name).cloned().ok_or_else(|| {
anyhow::anyhow!(
"服务 '{}' 不存在。可用服务: {:?}",
name,
servers.keys().collect::<Vec<_>>()
)
})?;
(name.clone(), config)
} else if servers.len() == 1 {
servers.into_iter().next().unwrap()
} else {
bail!(
"配置包含多个服务 {:?},请使用 --name 指定要使用的服务",
servers.keys().collect::<Vec<_>>()
);
};
match inner_config {
McpServerInnerConfig::Command(stdio) => Ok(McpConfigSource::LocalCommand {
name,
command: stdio.command,
args: stdio.args.unwrap_or_default(),
env: stdio.env.unwrap_or_default(),
}),
McpServerInnerConfig::Url(url_config) => {
let url = url_config
.get_url()
.ok_or_else(|| anyhow::anyhow!("URL 配置缺少 url 或 baseUrl 字段"))?
.to_string();
let protocol =
url_config
.r#type
.as_ref()
.and_then(|t| match t.to_ascii_lowercase().as_str() {
"sse" => Some(crate::client::protocol::McpProtocol::Sse),
"http" | "stream" | "streamablehttp" | "streamable-http"
| "streamable_http" => Some(crate::client::protocol::McpProtocol::Stream),
_ => None,
});
let mut headers = url_config.headers.clone().unwrap_or_default();
if let Some(auth_token) = &url_config.auth_token {
headers.insert("Authorization".to_string(), auth_token.clone());
}
Ok(McpConfigSource::RemoteService {
name,
url,
protocol,
headers,
timeout: url_config.timeout,
})
}
}
}
pub fn merge_headers(
config_headers: HashMap<String, String>,
cli_headers: &[(String, String)],
cli_auth: Option<&String>,
) -> HashMap<String, String> {
let mut merged = config_headers;
for (key, value) in cli_headers {
merged.insert(key.clone(), value.clone());
}
if let Some(auth_value) = cli_auth {
merged.insert("Authorization".to_string(), auth_value.clone());
}
merged
}