#[cfg(not(feature = "mcp"))]
use crate::config::AgentConfig;
#[cfg(feature = "mcp")]
mod full {
use std::path::Path;
use std::path::PathBuf;
use crate::config::AgentConfig;
use crate::runtime::cli::cli_effective_work_dir;
use crate::mcp::server::ToolCallbacks;
fn build_mcp_callbacks() -> ToolCallbacks {
ToolCallbacks::new(
|cfg: &AgentConfig, no_tools: bool| {
if no_tools {
return Vec::new();
}
let mut defs = crate::tools::build_tools();
crate::tool_call_explain::annotate_tool_defs_for_explain_card(&mut defs, cfg);
defs
},
|name: &str, args_json: &str, work_dir: &Path, cfg: &AgentConfig| {
let ctx = crate::tools::tool_context_for(
cfg,
&cfg.command_exec.allowed_commands,
work_dir,
);
let args_for_tool = match crate::tool_call_explain::require_explain_for_mutation(
cfg, name, args_json,
) {
Ok(cow) => cow.into_owned(),
Err(msg) => return Err(msg),
};
Ok(crate::tools::run_tool(name, &args_for_tool, &ctx))
},
)
}
pub async fn run_mcp_list(cfg: &AgentConfig, probe: bool) {
let resolved = crate::mcp::resolve_mcp_config(cfg);
if probe {
print_mcp_probe_skips(&crate::mcp::try_open_turn_handle(&resolved).await);
}
if !resolved.global_enabled {
println!("MCP:user-data 中 global_enabled=false,本进程无 MCP 工具。");
return;
}
let enabled: Vec<_> = resolved.enabled_servers().collect();
if enabled.is_empty() {
println!(
"MCP:未配置已启用的服务器(stdio 或远程 url;见 ~/.local/share/crabmate/mcp_servers.json 或 Web 设置 → MCP)。"
);
return;
}
let runtime = crate::mcp::mcp_servers_runtime_status(&resolved).await;
let connected: Vec<_> = runtime.iter().filter(|s| s.connected).collect();
if connected.is_empty() {
print_mcp_list_empty_hint(probe);
print_mcp_disconnected_rows(&runtime);
return;
}
println!(
"MCP:本进程内已缓存 {}/{} 个已启用服务器",
connected.len(),
enabled.len()
);
for st in connected {
print_mcp_connected_row(st);
}
}
fn print_mcp_probe_skips(opened: &crate::mcp::McpTurnOpenResult) {
if opened.skipped.is_empty() {
return;
}
println!("MCP:部分服务器连接失败:");
for s in &opened.skipped {
let kind = crate::mcp::classify_mcp_connect_error(&s.error);
println!(" [{}] {} — [{kind}] {}", s.id, s.name, s.error);
}
}
fn print_mcp_list_empty_hint(probe: bool) {
if probe {
println!(
"MCP:已尝试连接,但无可用缓存会话(见日志 target=crabmate)。\
常见原因:stdio 子进程失败、远程 DNS/TLS/401、握手失败或 tools/list 为空。"
);
} else {
println!(
"MCP:本进程内尚无已缓存会话(stdio / 远程)。\
请先经 Web / Client 对 `serve` 跑至少一轮对话以建立缓存,\
或使用 `crabmate mcp list --probe` 尝试立即连接。"
);
}
}
fn transport_label(transport: &str) -> &str {
if transport.is_empty() { "?" } else { transport }
}
fn print_mcp_disconnected_rows(runtime: &[crate::mcp::McpServerRuntimeStatus]) {
for st in runtime {
if !st.enabled {
continue;
}
let transport = transport_label(&st.transport);
match (&st.last_error, &st.last_error_kind) {
(Some(err), Some(kind)) => println!(
" [{}] {} (slug={} transport={transport}) — 未连接 [{kind}]:{err}",
st.id, st.name, st.slug
),
(Some(err), None) => println!(
" [{}] {} (slug={} transport={transport}) — 未连接:{err}",
st.id, st.name, st.slug
),
_ => println!(
" [{}] {} (slug={} transport={transport}) — 未连接",
st.id, st.name, st.slug
),
}
}
}
fn print_mcp_connected_row(st: &crate::mcp::McpServerRuntimeStatus) {
let transport = transport_label(&st.transport);
println!(
" [{}] {} slug={} transport={transport} tools={}",
st.id,
st.name,
st.slug,
st.openai_tool_names.len()
);
for name in &st.openai_tool_names {
println!(" {name}");
}
}
pub async fn run_mcp_serve(
cfg: &AgentConfig,
workspace_cli: &Option<String>,
no_tools: bool,
port: u16,
) -> Result<(), String> {
let workspace: PathBuf =
cli_effective_work_dir(workspace_cli, &cfg.command_exec.run_command_working_dir);
if port > 0 {
let cb = build_mcp_callbacks();
crate::mcp::server::run_tcp_mcp_server(cfg.clone(), workspace, no_tools, port, cb).await
} else {
let cb = build_mcp_callbacks();
crate::mcp::server::run_stdio_mcp_server(cfg.clone(), workspace, no_tools, cb).await
}
}
}
#[cfg(feature = "mcp")]
pub use full::{run_mcp_list, run_mcp_serve};
#[cfg(not(feature = "mcp"))]
pub async fn run_mcp_list(_cfg: &AgentConfig, _probe: bool) {
println!(
"本 crabmate 二进制未启用 `mcp` Cargo feature,不支持 MCP 列表/探测。请使用 `cargo build --features mcp` 重新编译。"
);
}
#[cfg(not(feature = "mcp"))]
#[allow(dead_code)]
pub async fn run_mcp_serve(
_cfg: &AgentConfig,
_workspace_cli: &Option<String>,
_no_tools: bool,
_port: u16,
) -> Result<(), String> {
Err("本 crabmate 二进制未启用 `mcp` Cargo feature,不支持 `mcp serve`".to_string())
}