use crate::{db, db::log_event}; use serde_json::{json, Value}; use std::path::PathBuf; const MCP_KEY_PREFIX: &str = "lian-jing:group-"; // ── 路径解析 ────────────────────────────────────────────────────────────────── /// 解析项目根目录路径(WSL 项目自动转为 Windows UNC 路径) fn project_root(project_id: &str) -> Result { let conn = db::pool().get().map_err(|e| e.to_string())?; let row: rusqlite::Result<(Option, Option, Option)> = conn.query_row( "SELECT win_path, wsl_path, platform FROM project_workspaces WHERE id = ?1", [project_id], |r| Ok((r.get(0)?, r.get(1)?, r.get(2)?)), ); let (win_path, wsl_path, platform) = row.map_err(|_| format!("项目 {} 不存在", project_id))?; if platform.as_deref() == Some("wsl") { let wsl = wsl_path.ok_or_else(|| "WSL 项目未配置 wsl_path".to_string())?; let distro: String = conn .query_row( "SELECT value FROM settings WHERE key = 'wsl_distro'", [], |r| r.get(0), ) .unwrap_or_else(|_| "Ubuntu".to_string()); let inner = wsl.trim_start_matches('/').replace('/', "\\"); Ok(PathBuf::from(format!( "\\\\wsl.localhost\\{}\\{}", distro, inner ))) } else { let win = win_path.ok_or_else(|| "Windows 项目未配置 win_path".to_string())?; Ok(PathBuf::from(win)) } } fn settings_path(project_id: &str) -> Result { Ok(project_root(project_id)?.join(".claude").join("settings.json")) } // ── JSON 读写 ───────────────────────────────────────────────────────────────── fn read_json(path: &PathBuf) -> Result { if !path.exists() { return Ok(json!({})); } let content = std::fs::read_to_string(path) .map_err(|e| format!("读取 settings.json 失败: {}", e))?; if content.trim().is_empty() { return Ok(json!({})); } serde_json::from_str(&content).map_err(|e| { format!( "settings.json 格式错误,请手动修复后重试({}): {}", path.display(), e ) }) } fn write_json(path: &PathBuf, value: &Value) -> Result<(), String> { if let Some(parent) = path.parent() { std::fs::create_dir_all(parent) .map_err(|e| format!("创建 .claude 目录失败: {}", e))?; } let content = serde_json::to_string_pretty(value).map_err(|e| e.to_string())?; std::fs::write(path, content).map_err(|e| format!("写入 settings.json 失败: {}", e)) } // ── 公开接口 ────────────────────────────────────────────────────────────────── /// 向项目的 .claude/settings.json 注入产品组 MCP 配置 pub fn inject_project_mcp(project_id: &str, group_id: &str) -> Result<(), String> { let port = crate::mcp_server::read_port(); let path = settings_path(project_id)?; let mut settings = read_json(&path)?; let servers = settings .as_object_mut() .ok_or("settings.json 顶层必须是 JSON 对象")? .entry("mcpServers") .or_insert(json!({})); let key = format!("{}{}", MCP_KEY_PREFIX, group_id); servers[&key] = json!({ "type": "http", "url": format!("http://localhost:{}/mcp/group/{}", port, group_id) }); write_json(&path, &settings).inspect(|_| { let ctx = serde_json::json!({"project_id": project_id, "group_id": group_id}).to_string(); log_event("mcp_inject", "info", "MCP 配置注入成功", Some(&ctx)); }) } /// 从项目的 .claude/settings.json 移除产品组 MCP 配置 pub fn remove_project_mcp(project_id: &str, group_id: &str) -> Result<(), String> { let path = settings_path(project_id)?; if !path.exists() { return Ok(()); } let mut settings = read_json(&path)?; let key = format!("{}{}", MCP_KEY_PREFIX, group_id); if let Some(servers) = settings .get_mut("mcpServers") .and_then(|v| v.as_object_mut()) { servers.remove(&key); } write_json(&path, &settings) } /// 端口变更后重新注入所有产品组成员的 MCP 配置 /// 返回注入失败的错误列表(部分失败不影响其他项目) pub fn reinject_all_mcp() -> Vec { let conn = match db::pool().get() { Ok(c) => c, Err(e) => return vec![e.to_string()], }; let mappings: Vec<(String, String)> = { let mut stmt = match conn.prepare("SELECT project_id, group_id FROM project_group_map") { Ok(s) => s, Err(e) => return vec![e.to_string()], }; stmt.query_map([], |row| Ok((row.get(0)?, row.get(1)?))) .map(|iter| iter.filter_map(|r| r.ok()).collect()) .unwrap_or_default() }; let mut errors = Vec::new(); for (project_id, group_id) in mappings { if let Err(e) = inject_project_mcp(&project_id, &group_id) { errors.push(format!("项目 {}: {}", project_id, e)); } } errors }