mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-08-04 03:32:25 +08:00
fix(mcp): improve upsert and import robustness
- Remove server from live config when app is disabled during upsert - Merge enabled flags instead of overwriting when importing from multiple apps - Normalize Gemini MCP type field (url-only → sse, command → stdio) - Use atomic write for Codex config updates - Add tests for disable-removal, multi-app merge, and Gemini SSE import
This commit is contained in:
@@ -42,8 +42,8 @@ fn write_json_value(path: &Path, value: &Value) -> Result<(), AppError> {
|
||||
///
|
||||
/// 执行反向格式转换以保持与统一 MCP 结构的兼容性:
|
||||
/// - httpUrl → url + type: "http"
|
||||
/// - 仅有 url 字段 → 保持不变(SSE 类型)
|
||||
/// - 仅有 command 字段 → 保持不变(stdio 类型)
|
||||
/// - 仅有 url 字段 → 补齐 type: "sse"(Gemini 以字段名推断传输类型)
|
||||
/// - 仅有 command 字段 → 补齐 type: "stdio"
|
||||
pub fn read_mcp_servers_map() -> Result<std::collections::HashMap<String, Value>, AppError> {
|
||||
let path = user_config_path();
|
||||
if !path.exists() {
|
||||
@@ -65,8 +65,15 @@ pub fn read_mcp_servers_map() -> Result<std::collections::HashMap<String, Value>
|
||||
obj.insert("url".to_string(), http_url);
|
||||
obj.insert("type".to_string(), Value::String("http".to_string()));
|
||||
}
|
||||
// 如果有 url 但没有 type,不添加 type(默认为 SSE)
|
||||
// 如果有 command 但没有 type,不添加 type(默认为 stdio)
|
||||
|
||||
// Gemini CLI 不使用 type 字段:这里补齐成统一结构,便于校验与导入
|
||||
if obj.get("type").is_none() {
|
||||
if obj.contains_key("command") {
|
||||
obj.insert("type".to_string(), Value::String("stdio".to_string()));
|
||||
} else if obj.contains_key("url") {
|
||||
obj.insert("type".to_string(), Value::String("sse".to_string()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -376,7 +376,8 @@ pub fn sync_single_server_to_codex(
|
||||
doc["mcp_servers"][id] = Item::Table(toml_table);
|
||||
|
||||
// 写回文件
|
||||
std::fs::write(&config_path, doc.to_string()).map_err(|e| AppError::io(&config_path, e))?;
|
||||
let new_text = doc.to_string();
|
||||
crate::config::write_text_file(&config_path, &new_text)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -412,7 +413,8 @@ pub fn remove_server_from_codex(id: &str) -> Result<(), AppError> {
|
||||
}
|
||||
|
||||
// 写回文件
|
||||
std::fs::write(&config_path, doc.to_string()).map_err(|e| AppError::io(&config_path, e))?;
|
||||
let new_text = doc.to_string();
|
||||
crate::config::write_text_file(&config_path, &new_text)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -17,8 +17,27 @@ impl McpService {
|
||||
|
||||
/// 添加或更新 MCP 服务器
|
||||
pub fn upsert_server(state: &AppState, server: McpServer) -> Result<(), AppError> {
|
||||
// 读取旧状态:用于处理“编辑时取消勾选某个应用”的场景(需要从对应 live 配置中移除)
|
||||
let prev_apps = state
|
||||
.db
|
||||
.get_all_mcp_servers()?
|
||||
.get(&server.id)
|
||||
.map(|s| s.apps.clone())
|
||||
.unwrap_or_default();
|
||||
|
||||
state.db.save_mcp_server(&server)?;
|
||||
|
||||
// 处理禁用:若旧版本启用但新版本取消,则需要从该应用的 live 配置移除
|
||||
if prev_apps.claude && !server.apps.claude {
|
||||
Self::remove_server_from_app(state, &server.id, &AppType::Claude)?;
|
||||
}
|
||||
if prev_apps.codex && !server.apps.codex {
|
||||
Self::remove_server_from_app(state, &server.id, &AppType::Codex)?;
|
||||
}
|
||||
if prev_apps.gemini && !server.apps.gemini {
|
||||
Self::remove_server_from_app(state, &server.id, &AppType::Gemini)?;
|
||||
}
|
||||
|
||||
// 同步到各个启用的应用
|
||||
Self::sync_server_to_apps(state, &server)?;
|
||||
|
||||
@@ -190,10 +209,22 @@ impl McpService {
|
||||
// 如果有导入的服务器,保存到数据库
|
||||
if count > 0 {
|
||||
if let Some(servers) = &temp_config.mcp.servers {
|
||||
let mut existing = state.db.get_all_mcp_servers()?;
|
||||
for server in servers.values() {
|
||||
state.db.save_mcp_server(server)?;
|
||||
// 同步到 Claude live 配置
|
||||
Self::sync_server_to_apps(state, server)?;
|
||||
// 已存在:仅启用 Claude,不覆盖其他字段(与导入模块语义保持一致)
|
||||
let to_save = if let Some(existing_server) = existing.get(&server.id) {
|
||||
let mut merged = existing_server.clone();
|
||||
merged.apps.claude = true;
|
||||
merged
|
||||
} else {
|
||||
server.clone()
|
||||
};
|
||||
|
||||
state.db.save_mcp_server(&to_save)?;
|
||||
existing.insert(to_save.id.clone(), to_save.clone());
|
||||
|
||||
// 同步到对应应用 live 配置
|
||||
Self::sync_server_to_apps(state, &to_save)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -212,10 +243,22 @@ impl McpService {
|
||||
// 如果有导入的服务器,保存到数据库
|
||||
if count > 0 {
|
||||
if let Some(servers) = &temp_config.mcp.servers {
|
||||
let mut existing = state.db.get_all_mcp_servers()?;
|
||||
for server in servers.values() {
|
||||
state.db.save_mcp_server(server)?;
|
||||
// 同步到 Codex live 配置
|
||||
Self::sync_server_to_apps(state, server)?;
|
||||
// 已存在:仅启用 Codex,不覆盖其他字段(与导入模块语义保持一致)
|
||||
let to_save = if let Some(existing_server) = existing.get(&server.id) {
|
||||
let mut merged = existing_server.clone();
|
||||
merged.apps.codex = true;
|
||||
merged
|
||||
} else {
|
||||
server.clone()
|
||||
};
|
||||
|
||||
state.db.save_mcp_server(&to_save)?;
|
||||
existing.insert(to_save.id.clone(), to_save.clone());
|
||||
|
||||
// 同步到对应应用 live 配置
|
||||
Self::sync_server_to_apps(state, &to_save)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -234,10 +277,22 @@ impl McpService {
|
||||
// 如果有导入的服务器,保存到数据库
|
||||
if count > 0 {
|
||||
if let Some(servers) = &temp_config.mcp.servers {
|
||||
let mut existing = state.db.get_all_mcp_servers()?;
|
||||
for server in servers.values() {
|
||||
state.db.save_mcp_server(server)?;
|
||||
// 同步到 Gemini live 配置
|
||||
Self::sync_server_to_apps(state, server)?;
|
||||
// 已存在:仅启用 Gemini,不覆盖其他字段(与导入模块语义保持一致)
|
||||
let to_save = if let Some(existing_server) = existing.get(&server.id) {
|
||||
let mut merged = existing_server.clone();
|
||||
merged.apps.gemini = true;
|
||||
merged
|
||||
} else {
|
||||
server.clone()
|
||||
};
|
||||
|
||||
state.db.save_mcp_server(&to_save)?;
|
||||
existing.insert(to_save.id.clone(), to_save.clone());
|
||||
|
||||
// 同步到对应应用 live 配置
|
||||
Self::sync_server_to_apps(state, &to_save)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user