//! 桌面进程内嵌 HTTP MCP server。 //! //! 复用 AppState.db 与 GUI 共享同一数据库连接,在 tauri 后台任务里常驻监听 //! 127.0.0.1:/mcp,多 Claude 会话 HTTP 直连同一桌面进程读写 DevFlow 数据。 //! //! 运行时状态(McpStatus)以 `tauri::State>` 管理: //! - setup 阶段 manage 初始 `{ running: false, port }` //! - spawn 内 bind 成功/失败/异常退出时更新 running //! - 前端经 `mcp_get_status` IPC 查询(Mutex 保证跨线程一致) use std::sync::{Arc, Mutex}; use serde::Serialize; use tauri::{AppHandle, Manager, State}; use df_mcp::server_http::{self, McpHttpState}; use df_storage::db::Database; /// 前端可查的 MCP 状态。 #[derive(Clone, Serialize)] pub struct McpStatus { /// server 是否正在监听(bind 成功为 true;绑定失败/异常退出为 false) pub running: bool, /// 实际监听端口(env `DEVFLOW_MCP_PORT` 覆盖,默认 18765) pub port: u16, } /// managed state 别名:跨线程共享的 MCP 运行时状态。 /// setup 中 `app.manage(Mutex::new(McpStatus { running: false, port }))` 注入, /// spawn 内更新、`mcp_get_status` 读取。 pub type McpStatusState = Mutex; /// 解析 MCP 监听端口:优先 env `DEVFLOW_MCP_PORT`,否则默认 18765。 /// 供 setup(spawn 前 manage 初始状态)与 mcp_get_status(兜底)共用,保证一致。 pub fn mcp_port() -> u16 { std::env::var("DEVFLOW_MCP_PORT") .ok() .and_then(|p| p.parse::().ok()) .unwrap_or(server_http::DEFAULT_MCP_PORT) } /// 在 tauri 后台任务里启动 HTTP MCP server(复用 AppState.db,与 GUI 共享连接)。 /// /// 前置条件:setup 中已 `app.manage(Mutex::new(McpStatus{...}))`(本函数内只更新不注入)。 /// bind 成功/失败/异常退出都会同步更新 managed 状态,前端 mcp_get_status 可查实时值。 pub fn spawn_mcp_http(app: &AppHandle, db: Arc) { // 工具调用回调:复用现有 df-data-changed 机制,让 GUI 前端在 MCP 写库后自动刷新。 // 非变更类工具(如 list/get)在 emit_data_changed 内部映射不到即 noop,零副作用。 let app_for_cb = app.clone(); let on_tool_call: Option> = Some(Arc::new(move |tool_name: &str| { crate::commands::ai::audit::emit_data_changed(&app_for_cb, tool_name); })); let state = McpHttpState::new(db, false, on_tool_call); let port = mcp_port(); // 后台 task 持有 app clone,用于 bind 结果写回 managed 状态 let app_for_status = app.clone(); tauri::async_runtime::spawn(async move { let listener = match tokio::net::TcpListener::bind(("127.0.0.1", port)).await { Ok(l) => l, Err(e) => { tracing::error!(port, err = %e, "[mcp-http] 绑定端口失败, MCP server 不可用(可设 DEVFLOW_MCP_PORT 换端口)"); set_mcp_running(&app_for_status, false); return; } }; // 绑定成功,更新状态为 running(供前端查询) set_mcp_running(&app_for_status, true); tracing::info!(port, "[mcp-http] MCP server listening on 127.0.0.1:{port}/mcp"); if let Err(e) = server_http::serve_on(listener, state).await { tracing::error!(err = %e, "[mcp-http] server 异常退出"); set_mcp_running(&app_for_status, false); } }); } /// 更新 managed McpStatus.running。state 未注入/try_state 失败/锁中毒时静默跳过 /// (状态保持原值,非致命,防 panic 拖垮后台任务)。 fn set_mcp_running(app: &AppHandle, running: bool) { if let Some(state) = app.try_state::() { if let Ok(mut guard) = state.lock() { guard.running = running; } } } /// IPC command:前端查询 MCP server 状态。 #[tauri::command] pub fn mcp_get_status(state: State<'_, McpStatusState>) -> McpStatus { match state.lock() { Ok(guard) => McpStatus { running: guard.running, port: guard.port, }, // 锁中毒(理论上不应发生):降级返回静态配置,防 panic Err(_) => McpStatus { running: false, port: mcp_port(), }, } }