fix(mcp): migrate import functions to unified v3.7.0 structure

- Rewrite import_from_claude/codex/gemini to write directly to mcp.servers
- Implement skip-on-error strategy for fault tolerance (single invalid item no longer aborts entire batch)
- Smart merge logic: existing servers only enable corresponding app, preserve other configs
- Remove deprecated markers from service layer
- Export McpApps type for test usage
- Update mcp_commands tests to use unified structure

Fixes runtime import issue where data was written to legacy structure (mcp.claude/codex.servers)
but unified panel reads from new structure (mcp.servers), causing "imported but invisible" bug.
This commit is contained in:
Jason
2025-11-14 23:33:54 +08:00
parent 09f80d82bc
commit ea8f2095e2
4 changed files with 201 additions and 212 deletions

View File

@@ -1,10 +1,10 @@
use std::{fs, sync::RwLock};
use std::{collections::HashMap, fs, sync::RwLock};
use serde_json::json;
use cc_switch_lib::{
get_claude_mcp_path, get_claude_settings_path, import_default_config_test_hook, AppError,
AppState, AppType, McpService, MultiAppConfig,
AppState, AppType, McpApps, McpServer, McpService, MultiAppConfig,
};
#[path = "support.rs"]
@@ -126,16 +126,12 @@ fn import_mcp_from_claude_creates_config_and_enables_servers() {
);
let guard = state.config.read().expect("lock config");
let claude_servers = &guard.mcp.claude.servers;
let entry = claude_servers
.get("echo")
.expect("server imported into config.json");
// v3.7.0: 检查统一结构
let servers = guard.mcp.servers.as_ref().expect("unified servers should exist");
let entry = servers.get("echo").expect("server imported into unified structure");
assert!(
entry
.get("enabled")
.and_then(|v| v.as_bool())
.unwrap_or(false),
"imported server should be marked enabled"
entry.apps.claude,
"imported server should have Claude app enabled"
);
drop(guard);
@@ -181,43 +177,61 @@ fn import_mcp_from_claude_invalid_json_preserves_state() {
fn set_mcp_enabled_for_codex_writes_live_config() {
let _guard = test_mutex().lock().expect("acquire test mutex");
reset_test_fs();
ensure_test_home();
let home = ensure_test_home();
// 创建 Codex 配置目录和文件
let codex_dir = home.join(".codex");
fs::create_dir_all(&codex_dir).expect("create codex dir");
fs::write(codex_dir.join("auth.json"), r#"{"OPENAI_API_KEY":"test-key"}"#)
.expect("create auth.json");
fs::write(codex_dir.join("config.toml"), "")
.expect("create empty config.toml");
let mut config = MultiAppConfig::default();
config.ensure_app(&AppType::Codex);
config.mcp.codex.servers.insert(
// v3.7.0: 使用统一结构
config.mcp.servers = Some(HashMap::new());
config.mcp.servers.as_mut().unwrap().insert(
"codex-server".into(),
json!({
"id": "codex-server",
"name": "Codex Server",
"server": {
McpServer {
id: "codex-server".to_string(),
name: "Codex Server".to_string(),
server: json!({
"type": "stdio",
"command": "echo"
}),
apps: McpApps {
claude: false,
codex: false, // 初始未启用
gemini: false,
},
"enabled": false
}),
description: None,
homepage: None,
docs: None,
tags: Vec::new(),
},
);
let state = AppState {
config: RwLock::new(config),
};
McpService::set_enabled(&state, AppType::Codex, "codex-server", true)
.expect("set enabled should succeed");
// v3.7.0: 使用 toggle_app 替代 set_enabled
McpService::toggle_app(&state, "codex-server", AppType::Codex, true)
.expect("toggle_app should succeed");
let guard = state.config.read().expect("lock config");
let entry = guard
.mcp
.codex
.servers
.as_ref()
.unwrap()
.get("codex-server")
.expect("codex server exists");
assert!(
entry
.get("enabled")
.and_then(|v| v.as_bool())
.unwrap_or(false),
"server should be marked enabled after command"
entry.apps.codex,
"server should have Codex app enabled after toggle"
);
drop(guard);