新增: MCP HTTP transport + 系统托盘 + 实体解析(会话前基线收尾)

- df-mcp 加 streamable HTTP transport 层(axum 0.7 + tower,server_http.rs),lib.rs pub mod 接线

- src-tauri 加 mcp.rs(spawn_mcp_http + mcp_get_status IPC + mcp-server CLI)+ tray.rs(系统托盘 show_main/setup_tray),lib.rs 集成 + main.rs mcp-server 参数路由

- AI 工具加 entity_resolve.rs(实体解析,tools/mod.rs pub mod 接线)
This commit is contained in:
lxy
2026-08-05 22:14:27 +08:00
parent c480627ba6
commit a91e950874
16 changed files with 2238 additions and 94 deletions
+99
View File
@@ -0,0 +1,99 @@
//! 桌面进程内嵌 HTTP MCP server。
//!
//! 复用 AppState.db 与 GUI 共享同一数据库连接,在 tauri 后台任务里常驻监听
//! 127.0.0.1:<port>/mcp,多 Claude 会话 HTTP 直连同一桌面进程读写 DevFlow 数据。
//!
//! 运行时状态(McpStatus)以 `tauri::State<Mutex<McpStatus>>` 管理:
//! - 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<McpStatus>;
/// 解析 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::<u16>().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<Database>) {
// 工具调用回调:复用现有 df-data-changed 机制,让 GUI 前端在 MCP 写库后自动刷新。
// 非变更类工具(如 list/get)在 emit_data_changed 内部映射不到即 noop,零副作用。
let app_for_cb = app.clone();
let on_tool_call: Option<Arc<dyn Fn(&str) + Send + Sync>> = 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::<McpStatusState>() {
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(),
},
}
}