一个面板管多个应用的 MCP:各自配置文件路径与格式差异

2026-08-10

用过多个 AI 编码 CLI 的人都遇到过同一件麻烦事:同一个 MCP 服务器,要在 Claude 那边写一遍,在 Codex 那边再写一遍,格式还不一样。CC Switch 的统一 MCP 面板做的就是这件事的收口——你在一处维护条目,它往各个应用的本地配置文件里投影。

但”投影”这个词容易让人产生一个错觉:以为落盘的就是同一份 JSON,只是换了个地方放。实际读一遍 src-tauri/src/mcp/ 下的代码就会发现,路径不同只是最浅的一层差异,真正的差异在字段名和类型值上——同一条服务器写到不同应用,键名会改、类型枚举会换、数组会被合并、还会被塞进几个你没填过的字段。

本文只讲这一层:目标应用有哪几个、各自落到哪个文件的哪个键、格式要怎么改。至于双向同步的时机、校验规则拦住了哪些写法,我们另有篇目专门讲,这里不重复。

先确认面板管的到底是哪几个应用

我们采集时(2026-08-10)读到的快照是 cc-switch 的 c39c903src-tauri/src/mcp/ 下共 8 个文件、合计 2431 行(wc -l src-tauri/src/mcp/*.rs)。

入口是 src-tauri/src/mcp/mod.rs。它的 mod 声明列了 6 个应用子模块:claude、codex、gemini、grokbuild、hermes、opencode(mcp/mod.rs:14-19)。同一个文件的模块头注释的模块清单里只有 validation 加 claude、codex、gemini、opencode、hermes 五个应用,没有把 grokbuild 写进注释,但 mod grokbuild; 这一行确实在(mcp/mod.rs:7-19)。

要交叉验证这个”6”,还有两处:McpApps 结构体的字段是 claude / codex / gemini / grokbuild / opencode / hermes 六个布尔(src-tauri/src/mcp/claude.rs:90-97);import_from_all_apps 里的数组长度直接硬编码成 6,元素也是这六个(src-tauri/src/services/mcp.rs:513-520)。

反过来,有两个应用是被明确排除在外的:

  • project_servers_to_appAppType::OpenClaw | AppType::ClaudeDesktop 直接 return Ok(()),即这两个应用不参与 MCP 投影(src-tauri/src/services/mcp.rs:530-534 区段)
  • McpService::sync_server_to_app_no_configAppType::ClaudeDesktop 只打一条日志就跳过,注释原文是 “Claude Desktop 3P profiles do not use CC Switch MCP sync, skipping”
  • 深链那侧也一致:parse_mcp_apps 碰到 "openclaw" 只写 debug 日志,注释是 “OpenClaw doesn’t support MCP, ignore silently”(src-tauri/src/deeplink/mcp.rs:171-174

这里有一处文档与代码对不上,值得先记下来:中文用户手册 docs/user-manual/zh/3-extensions/3.1-mcp.md 的开关表只有 5 行(Claude / Codex / Gemini / OpenCode / Hermes),正文也写”MCP 功能支持 Claude、Codex、Gemini、OpenCode 和 Hermes”(3.1-mcp.md:101-109);而代码里 McpApps 有 grokbuild 字段、有 mcp/grokbuild.rs 这个文件、import_from_all_apps 的数组里也含 grokbuild。两处不一致,以我们实读的仓库状态为准。差异说到这里为止,我们不推断原因。

六个落点:文件、键、格式

应用配置文件承载键格式源码锚点
Claude~/.claude.json根对象 mcpServersJSONclaude_mcp.rs:343-345config.rs:176-184
Codex~/.codex/config.toml顶层 [mcp_servers]TOMLmcp/codex.rs:278-286
Gemini~/.gemini/settings.jsonmcpServersJSONgemini_mcp.rs:9-1273-75
GrokBuild~/.grok/config.toml顶层 [mcp_servers]TOMLgrok_config.rs:30-33mcp/grokbuild.rs:1-4
OpenCode~/.config/opencode/opencode.jsonmcpJSONopencode_config.rs:48-60222-240
Hermes~/.hermes/config.yamlmcp_serversYAMLhermes_config.rs:100-102mcp/hermes.rs:172

这张表值得逐列读一遍。三种序列化格式(JSON / TOML / YAML)、三个不同的键名(mcpServers / mcp_servers / mcp),其中 TOML 侧写成表头 [mcp_servers]——键名连大小写风格都不统一。GrokBuild 那一行的模块注释写得很直白:“Grok Build uses the same top-level [mcp_servers] TOML layout as Codex”(mcp/grokbuild.rs:1-4),所以它和 Codex 是同一套 TOML 布局,只是文件位置不同。

另外 Claude 那一行有个细节:写入时”仅覆盖 mcpServers,其他字段保持不变”(claude_mcp.rs:343-345),路径由 get_claude_mcp_path() 决定,默认 ~/.claude.json,但可以被 claude_override_dir 改掉(src-tauri/src/config.rs:176-184)。也就是说,如果你排查”为什么写进去了但 Claude 没读到”,除了看文件本身,还得先确认 override 目录有没有被设过。

反直觉的那一处:type 字段往返不守恒

如果只看上面那张表,你会以为差异止于”换个文件换个键”。真正会咬人的是 OpenCode 这一支。

src-tauri/src/mcp/opencode.rs 的文件头把转换规则原样列成了一张表(opencode.rs:7-13):

  • type: "stdio"type: "local"
  • command + argscommand: [cmd, ...args](两个字段被合并成一个数组)
  • envenvironment
  • type: "sse" / type: "http"type: "remote"
  • urlurl

注意第四条:ssehttp 两种类型在写出时都会塌缩成同一个 remote。而反向转换那一侧,remote 分支写的是 result.insert("type".into(), json!("sse")),注释是 “Convert to “sse” type (default remote protocol)“(opencode.rs:152-154)。

把这两步接起来看:一个原本 type: "http" 的服务器,写进 OpenCode 配置变成 remote,再从 OpenCode 导回来就成了 sse。转一圈之后,类型值和你最初填的不一样了。这不是隐藏 bug,是源码注释里写明的行为——remote 这个枚举值本身不携带”原来是 http 还是 sse”的信息,回程只能取一个默认。

除此之外,OpenCode 写出时还会额外加一个 enabled: true,stdio 与 remote 两条分支都加(opencode.rs:7696)。所以你去 opencode.json 里看到一个自己没填过的 enabled 字段,来源在这。

同类的字段改名在 Codex 那边也有一处:统一结构里鉴权头叫 headers,写进 Codex 的 TOML 时字段名是 http_headersmcp/codex.rs:87-89687-695)。你手写 Codex 配置时如果照着 Claude 的 JSON 抄 headers,名字就对不上了。

Gemini:没有 type,靠字段名认传输方式

Gemini 是另一种思路。src-tauri/src/gemini_mcp.rs 的注释说明,Gemini CLI 不使用 type 字段,而是按字段名推断传输类型:HTTP 用 httpUrl,SSE 用 urlgemini_mcp.rs:32-37104-112)。

因为不认 type,写入前会把一批字段直接删掉。被移除的是 typeenabledsourceidnamedescriptiontagshomepagedocs 共 9 个(gemini_mcp.rs:117-126)——后面这几个是 CC Switch 自己的 UI 辅助字段,落到 Gemini 的配置里没有意义。

超时那一段更需要留意。Gemini 侧只有一个 timeout,而统一结构里可能存在 startup_timeout_sec / startup_timeout_mstool_timeout_sec / tool_timeout_ms 两组四个字段。代码的做法是:先把秒/毫秒归一成毫秒,两组各取一个值(未设置时 startup 默认 10000ms、tool 默认 60000ms),然后取两者的 max 写成 Gemini 的 timeoutgemini_mcp.rs:128-152)。

换句话说,统一结构里如果同时带了启动超时与工具超时两组字段,到 Gemini 那边会被压成一个 timeout,取的是较大的那个。这两个默认值是源码里的默认配置,不是对实际运行结果的保证——具体该设多少取决于你挂的是什么服务器,项目没有给通用值。

Hermes:字段保不住的问题,它用 merge-on-write 解

Hermes 的推断方式和 Gemini 类似:无显式 type 字段,靠有 command 还是有 url 来判断(mcp/hermes.rs:12-15)。它自己还有一批额外字段,常量 HERMES_EXTRA_FIELDS 里列了 7 项:enabledtimeoutconnect_timeouttoolssamplingrootsauthmcp/hermes.rs:32-45)。

这批字段的处理方式是 merge-on-write:核心字段取新的 spec,Hermes 专有字段从已存在的条目里保留下来,注释写的是 “prevents CC Switch from overwriting user customizations”(mcp/hermes.rs:204-209)。这就是上一节 OpenCode 那个”往返不守恒”问题的另一种解法——OpenCode 那边丢的是类型信息、只能取默认,Hermes 这边则是把外部字段原样端回去、不参与转换。

Codex 的 TOML:白名单与三个不支持

TOML 不是 JSON 的平替,转换器有明确边界。Codex 的扩展字段白名单一共 21 个:timeouttimeout_msstartup_timeout_msstartup_timeout_secconnection_timeoutread_timeoutdebuglog_leveldisabledshellencodingworking_dirrestart_on_exitmax_restart_countretry_countmax_retry_attemptsretry_delaycache_tools_listverify_sslinsecureproxymcp/codex.rs:625-649)。白名单之外的字段不在这条通道里。

JSON→TOML 的通用转换器另外明确标了三种不支持:null、深度嵌套对象、混合类型数组(mcp/codex.rs:511-514)。如果你的服务器配置里有这三类结构,别指望它原样过去。

还有一个兼容性设计:Codex 的导入侧同时认两种格式——正确的 [mcp_servers.*],以及”错误格式 [mcp.servers.*](容错读取,用于迁移错误写入的配置)“(mcp/codex.rs:45-51);写入侧则会”自动清理错误格式 [mcp.servers]”(mcp/codex.rs:278-286)。

Windows 侧:cmd /c 包装与 WSL 路径的已知盲区

本站读者以 Windows 居多,这一段不能省。

src-tauri/src/claude_mcp.rs 的头部注释写明,Windows 上 npx 一类命令会被包成 cmd /c,动机是解决 Claude Code /doctor 报的 “Windows requires ‘cmd /c’ wrapper to execute npx” 警告(claude_mcp.rs:15-16)。

例外情况也写在代码里:WSL 网络路径(\\wsl$\... / \\wsl.localhost\...)不做包装,因为那边跑的是 Linux。但注释同时注明了这个检测的边界——“仅检测直接 UNC 路径,映射磁盘符(如 Z:)无法检测”(claude_mcp.rs:68-70)。也就是说,如果你把 WSL 路径映射成了盘符,这个判断认不出来。这是源码里自己标出的已知盲区,照实记下即可。

同样的跨平台处理在前端预设里也有一份。我们采集的 c39c903 快照里,src/config/mcpPresets.ts 共 104 行,mcpPresets 数组是 5 条(mcpPresets.ts:31-90),5 条的 server.type 全部是 "stdio";其中的 createNpxCommand 在 Windows 下生成 command: "cmd"args: ["/c","npx", ...extraArgs, packageName],非 Windows 直接用 command: "npx"mcpPresets.ts:9-24)。源码注释对这批预设的定位写得很克制:“仅包含最常用、可快速落地的 stdio 模式示例”(mcpPresets.ts:26-30)。

你可以自己核的三件事

上面每一条都能落到具体文件的具体行,核查动作很直接:

  1. 数一遍目标应用grep -n "^mod " src-tauri/src/mcp/mod.rs 会返回 7 行,其中 validation 不是应用模块,去掉它剩 6 个应用子模块(即 mcp/mod.rs:14-19 那六行);再打开 src-tauri/src/mcp/claude.rs:90-97McpApps 的布尔字段,同样是 6 个,两处对得上。
  2. 对照手册与代码:打开 docs/user-manual/zh/3-extensions/3.1-mcp.md:101-109 的开关表数行数,与上一步的字段名逐个对,差在哪一项一眼可见。
  3. 验证往返不守恒mcp/opencode.rs:7-13 的注释表看出向 remote 的塌缩,再看 opencode.rs:152-154 的回程默认值,两处连起来读。

以上核查动作按仓库中的文件位置组合而成,我们没有运行过它们,以官方文档与仓库当前内容为准。

最后提醒一句边界:这个面板读写的是 ~/.claude.json~/.codex/config.toml 这类真实 CLI 配置文件,属于你本机的敏感数据,条目里往往还带鉴权头。改动之前先留一份自己的副本,比事后从任何自动机制里找回来都可靠(手动留副本是通用运维做法,不是该项目文档里的步骤)。


本文依据 CC Switch 官方仓库(github.com/farion1231/cc-switch)的 README、docs/ 下的用户手册与发布说明、 src/config/ 的预设定义与 src-tauri/src/ 的后端源码整理,核对日 2026-08-10,对应仓库快照 c39c903。 本文内容为仓库源码与文档口径,我们没有安装或运行过这个桌面应用, 因此不涉及界面外观、操作手感与切换速度的任何描述。 文中出现的阈值与默认值均为源码中的默认配置,不构成对实际运行结果的保证。 该项目仍在快速迭代,版本与默认值随时可能变动,请以仓库最新内容为准。 安全相关做法请结合自身环境评估,本文不构成安全方案建议。

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。