use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::Once;
use std::time::Duration;
use anyhow::{Result, bail};
use clap::Parser;
use serde::Deserialize;
use tracing::{error, info, warn};
use crate::client::support::{LoggingArgs, init_logging_with_config};
use crate::proxy::ToolFilter;
static INIT_PANIC_HOOK: Once = Once::new();
const MAX_RETRIES: u32 = 0;
const INITIAL_RETRY_DELAY_SECS: u64 = 3;
const MAX_RETRY_DELAY_SECS: u64 = 30;
#[derive(clap::ValueEnum, Clone, Debug, Default)]
pub enum ProxyProtocol {
Sse,
#[default]
Stream,
}
#[derive(Parser, Debug, Clone)]
pub struct ProxyArgs {
#[arg(short, long, default_value = "8080", help = "监听端口")]
pub port: u16,
#[arg(long, default_value = "127.0.0.1", help = "监听地址")]
pub host: String,
#[arg(short, long, help = "MCP 服务名称(多服务配置时必需)")]
pub name: Option<String>,
#[arg(long, conflicts_with = "config_file", help = "MCP 服务配置 JSON")]
pub config: Option<String>,
#[arg(long, conflicts_with = "config", help = "MCP 服务配置文件路径")]
pub config_file: Option<PathBuf>,
#[arg(long, value_enum, default_value = "stream", help = "输出协议类型")]
pub protocol: ProxyProtocol,
#[arg(long, default_value = "/sse", help = "SSE 端点路径")]
pub sse_path: String,
#[arg(long, default_value = "/message", help = "消息端点路径")]
pub message_path: String,
#[arg(long, value_delimiter = ',', help = "工具白名单(逗号分隔)")]
pub allow_tools: Option<Vec<String>>,
#[arg(long, value_delimiter = ',', help = "工具黑名单(逗号分隔)")]
pub deny_tools: Option<Vec<String>>,
#[command(flatten)]
pub logging: LoggingArgs,
}
#[derive(Deserialize, Debug)]
struct McpConfig {
#[serde(rename = "mcpServers")]
mcp_servers: HashMap<String, StdioConfig>,
}
#[derive(Deserialize, Debug, Clone)]
struct StdioConfig {
command: String,
args: Option<Vec<String>>,
env: Option<HashMap<String, String>>,
}
struct ParsedConfig {
name: String,
config: StdioConfig,
}
pub async fn run_proxy_command(args: ProxyArgs, verbose: bool, quiet: bool) -> Result<()> {
INIT_PANIC_HOOK.call_once(|| {
std::panic::set_hook(Box::new(|panic_info| {
let backtrace = std::backtrace::Backtrace::capture();
error!(
"[PANIC] Program panic - Location: {}:{}, Message: {:?}",
panic_info.location().map(|l| l.file()).unwrap_or("unknown"),
panic_info.location().map(|l| l.line()).unwrap_or(0),
panic_info.payload().downcast_ref::<String>()
);
error!("[PANIC] Stack trace:\\n{:?}", backtrace);
}));
});
if args.allow_tools.is_some() && args.deny_tools.is_some() {
bail!("--allow-tools 和 --deny-tools 不能同时使用,请只选择其中一个");
}
let parsed = parse_config(&args)?;
{
let mirror = mcp_common::mirror::MirrorConfig::from_env();
if !mirror.is_empty() {
mirror.apply_to_process_env();
}
}
init_logging_with_config(&args.logging, Some(&parsed.name), quiet, verbose)?;
let tool_filter = if let Some(allow_tools) = args.allow_tools.clone() {
ToolFilter::allow(allow_tools)
} else if let Some(deny_tools) = args.deny_tools.clone() {
ToolFilter::deny(deny_tools)
} else {
ToolFilter::default()
};
let protocol_name = match args.protocol {
ProxyProtocol::Sse => "SSE",
ProxyProtocol::Stream => "Streamable HTTP",
};
info!(
"[Service startup] MCP Proxy service startup - protocol: {}, service name: {}, command: {} {:?}",
protocol_name,
parsed.name,
parsed.config.command,
parsed.config.args.as_ref().unwrap_or(&vec![])
);
if let Some(ref allow_tools) = args.allow_tools {
info!("[Service startup] Tool whitelist: {:?}", allow_tools);
}
if let Some(ref deny_tools) = args.deny_tools {
info!("[Service startup] Tool blacklist: {:?}", deny_tools);
}
if !quiet {
eprintln!("🚀 MCP Proxy Service");
eprintln!("Protocol type: {}", protocol_name);
eprintln!("Service name: {}", parsed.name);
eprintln!(
"Command: {} {:?}",
parsed.config.command,
parsed.config.args.as_ref().unwrap_or(&vec![])
);
if verbose && let Some(ref env) = parsed.config.env {
eprintln!("Environment variable: {:?}", env);
}
if let Some(ref allow_tools) = args.allow_tools {
eprintln!("Tool whitelist: {:?}", allow_tools);
}
if let Some(ref deny_tools) = args.deny_tools {
eprintln!("Tool blacklist: {:?}", deny_tools);
}
}
let bind_addr = format!("{}:{}", args.host, args.port);
let std_listener = std::net::TcpListener::bind(&bind_addr)
.map_err(|e| anyhow::anyhow!("端口绑定失败 {}: {}", bind_addr, e))?;
std_listener
.set_nonblocking(true)
.map_err(|e| anyhow::anyhow!("设置非阻塞失败: {}", e))?;
info!("[Port Binding] Binded {}", bind_addr);
let mut retry_count: u32 = 0;
let mut retry_delay = Duration::from_secs(INITIAL_RETRY_DELAY_SECS);
loop {
let result = run_proxy_server(
&args,
&parsed,
&std_listener,
tool_filter.clone(),
verbose,
quiet,
)
.await;
match result {
Ok(_) => {
info!(
"[Service stopped] MCP Proxy service stopped normally - service name: {}",
parsed.name
);
if !quiet {
eprintln!("🛑 Service has been stopped");
}
break;
}
Err(e) => {
retry_count += 1;
#[allow(clippy::absurd_extreme_comparisons)]
if MAX_RETRIES > 0 && retry_count >= MAX_RETRIES {
error!(
"[Service Termination] Maximum number of retries reached {}, service name: {}, last error: {}",
MAX_RETRIES, parsed.name, e
);
return Err(e);
}
let retry_info = if MAX_RETRIES == 0 {
format!("第{}次", retry_count)
} else {
format!("第{}/{}次", retry_count, MAX_RETRIES)
};
error!(
"[Service exception] MCP Proxy service exited abnormally - service name: {}, error: {}, {} and restarts after seconds ({})",
parsed.name,
e,
retry_delay.as_secs(),
retry_info
);
eprintln!(
"⚠️ Service exception: {}, {}, restart after seconds ({})...",
e,
retry_delay.as_secs(),
retry_info
);
tokio::time::sleep(retry_delay).await;
retry_delay =
std::cmp::min(retry_delay * 2, Duration::from_secs(MAX_RETRY_DELAY_SECS));
warn!(
"[Service Restart] Restarting MCP Proxy service - Service name: {}",
parsed.name
);
if !quiet {
eprintln!("🔄 Restarting service...");
}
continue;
}
}
}
Ok(())
}
async fn run_proxy_server(
args: &ProxyArgs,
parsed: &ParsedConfig,
std_listener: &std::net::TcpListener,
tool_filter: ToolFilter,
_verbose: bool,
quiet: bool,
) -> Result<()> {
let resolved_command = mcp_common::resolve_windows_command(&parsed.config.command);
if resolved_command != parsed.config.command {
info!(
"[Command parsing] Windows command parsed: {} -> {}",
parsed.config.command, resolved_command
);
}
match args.protocol {
ProxyProtocol::Sse => {
let config = mcp_sse_proxy::McpServiceConfig {
name: parsed.name.clone(),
command: resolved_command,
args: parsed.config.args.clone(),
env: parsed.config.env.clone(),
tool_filter: Some(tool_filter),
};
mcp_sse_proxy::run_sse_server_from_config(config, std_listener, quiet).await
}
ProxyProtocol::Stream => {
let config = mcp_streamable_proxy::McpServiceConfig {
name: parsed.name.clone(),
command: resolved_command,
args: parsed.config.args.clone(),
env: parsed.config.env.clone(),
tool_filter: Some(tool_filter),
};
mcp_streamable_proxy::run_stream_server_from_config(config, std_listener, quiet).await
}
}
}
fn parse_config(args: &ProxyArgs) -> Result<ParsedConfig> {
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!("必须提供 --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 服务");
}
if servers.len() == 1 {
let (name, config) = servers.into_iter().next().unwrap();
Ok(ParsedConfig { name, config })
} else if let Some(ref name) = args.name {
servers
.get(name)
.cloned()
.map(|config| ParsedConfig {
name: name.clone(),
config,
})
.ok_or_else(|| {
anyhow::anyhow!(
"服务 '{}' 不存在。可用服务: {:?}",
name,
servers.keys().collect::<Vec<_>>()
)
})
} else {
bail!(
"配置包含多个服务 {:?},请使用 --name 指定要启动的服务",
servers.keys().collect::<Vec<_>>()
);
}
}