mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-25 13:45:03 +08:00
a8fd1f0dd2
Add guard functions to check if Claude/Codex/Gemini CLI has been initialized before attempting to sync MCP configurations. This prevents creating unwanted config files in directories that don't exist. - Claude: check ~/.claude dir OR ~/.claude.json file exists - Codex: check ~/.codex dir exists - Gemini: check ~/.gemini dir exists When the target app is not installed, sync operations now silently succeed without writing any files, allowing users to manage MCP servers for apps they actually use without side effects on others.
147 lines
4.7 KiB
Rust
147 lines
4.7 KiB
Rust
//! Claude MCP 同步和导入模块
|
||
|
||
use serde_json::Value;
|
||
use std::collections::HashMap;
|
||
|
||
use crate::app_config::{McpApps, McpConfig, McpServer, MultiAppConfig};
|
||
use crate::error::AppError;
|
||
|
||
use super::validation::{extract_server_spec, validate_server_spec};
|
||
|
||
fn should_sync_claude_mcp() -> bool {
|
||
// Claude 未安装/未初始化时:通常 ~/.claude 目录与 ~/.claude.json 都不存在。
|
||
// 按用户偏好:此时跳过写入/删除,不创建任何文件或目录。
|
||
crate::config::get_claude_config_dir().exists() || crate::config::get_claude_mcp_path().exists()
|
||
}
|
||
|
||
/// 返回已启用的 MCP 服务器(过滤 enabled==true)
|
||
fn collect_enabled_servers(cfg: &McpConfig) -> HashMap<String, Value> {
|
||
let mut out = HashMap::new();
|
||
for (id, entry) in cfg.servers.iter() {
|
||
let enabled = entry
|
||
.get("enabled")
|
||
.and_then(|v| v.as_bool())
|
||
.unwrap_or(false);
|
||
if !enabled {
|
||
continue;
|
||
}
|
||
match extract_server_spec(entry) {
|
||
Ok(spec) => {
|
||
out.insert(id.clone(), spec);
|
||
}
|
||
Err(err) => {
|
||
log::warn!("跳过无效的 MCP 条目 '{id}': {err}");
|
||
}
|
||
}
|
||
}
|
||
out
|
||
}
|
||
|
||
/// 将 config.json 中 enabled==true 的项投影写入 ~/.claude.json
|
||
pub fn sync_enabled_to_claude(config: &MultiAppConfig) -> Result<(), AppError> {
|
||
if !should_sync_claude_mcp() {
|
||
return Ok(());
|
||
}
|
||
let enabled = collect_enabled_servers(&config.mcp.claude);
|
||
crate::claude_mcp::set_mcp_servers_map(&enabled)
|
||
}
|
||
|
||
/// 从 ~/.claude.json 导入 mcpServers 到统一结构(v3.7.0+)
|
||
/// 已存在的服务器将启用 Claude 应用,不覆盖其他字段和应用状态
|
||
pub fn import_from_claude(config: &mut MultiAppConfig) -> Result<usize, AppError> {
|
||
let text_opt = crate::claude_mcp::read_mcp_json()?;
|
||
let Some(text) = text_opt else { return Ok(0) };
|
||
|
||
let v: Value = serde_json::from_str(&text)
|
||
.map_err(|e| AppError::McpValidation(format!("解析 ~/.claude.json 失败: {e}")))?;
|
||
let Some(map) = v.get("mcpServers").and_then(|x| x.as_object()) else {
|
||
return Ok(0);
|
||
};
|
||
|
||
// 确保新结构存在
|
||
let servers = config.mcp.servers.get_or_insert_with(HashMap::new);
|
||
|
||
let mut changed = 0;
|
||
let mut errors = Vec::new();
|
||
|
||
for (id, spec) in map.iter() {
|
||
// 校验:单项失败不中止,收集错误继续处理
|
||
if let Err(e) = validate_server_spec(spec) {
|
||
log::warn!("跳过无效 MCP 服务器 '{id}': {e}");
|
||
errors.push(format!("{id}: {e}"));
|
||
continue;
|
||
}
|
||
|
||
if let Some(existing) = servers.get_mut(id) {
|
||
// 已存在:仅启用 Claude 应用
|
||
if !existing.apps.claude {
|
||
existing.apps.claude = true;
|
||
changed += 1;
|
||
log::info!("MCP 服务器 '{id}' 已启用 Claude 应用");
|
||
}
|
||
} else {
|
||
// 新建服务器:默认仅启用 Claude
|
||
servers.insert(
|
||
id.clone(),
|
||
McpServer {
|
||
id: id.clone(),
|
||
name: id.clone(),
|
||
server: spec.clone(),
|
||
apps: McpApps {
|
||
claude: true,
|
||
codex: false,
|
||
gemini: false,
|
||
},
|
||
description: None,
|
||
homepage: None,
|
||
docs: None,
|
||
tags: Vec::new(),
|
||
},
|
||
);
|
||
changed += 1;
|
||
log::info!("导入新 MCP 服务器 '{id}'");
|
||
}
|
||
}
|
||
|
||
if !errors.is_empty() {
|
||
log::warn!("导入完成,但有 {} 项失败: {:?}", errors.len(), errors);
|
||
}
|
||
|
||
Ok(changed)
|
||
}
|
||
|
||
/// 将单个 MCP 服务器同步到 Claude live 配置
|
||
pub fn sync_single_server_to_claude(
|
||
_config: &MultiAppConfig,
|
||
id: &str,
|
||
server_spec: &Value,
|
||
) -> Result<(), AppError> {
|
||
if !should_sync_claude_mcp() {
|
||
return Ok(());
|
||
}
|
||
// 读取现有的 MCP 配置
|
||
let current = crate::claude_mcp::read_mcp_servers_map()?;
|
||
|
||
// 创建新的 HashMap,包含现有的所有服务器 + 当前要同步的服务器
|
||
let mut updated = current;
|
||
updated.insert(id.to_string(), server_spec.clone());
|
||
|
||
// 写回
|
||
crate::claude_mcp::set_mcp_servers_map(&updated)
|
||
}
|
||
|
||
/// 从 Claude live 配置中移除单个 MCP 服务器
|
||
pub fn remove_server_from_claude(id: &str) -> Result<(), AppError> {
|
||
if !should_sync_claude_mcp() {
|
||
return Ok(());
|
||
}
|
||
// 读取现有的 MCP 配置
|
||
let mut current = crate::claude_mcp::read_mcp_servers_map()?;
|
||
|
||
// 移除指定服务器
|
||
current.remove(id);
|
||
|
||
// 写回
|
||
crate::claude_mcp::set_mcp_servers_map(¤t)
|
||
}
|