一个面板管多个应用的 MCP:各自配置文件路径与格式差异
用过多个 AI 编码 CLI 的人都遇到过同一件麻烦事:同一个 MCP 服务器,要在 Claude 那边写一遍,在 Codex 那边再写一遍,格式还不一样。CC Switch 的统一 MCP 面板做的就是这件事的收口——你在一处维护条目,它往各个应用的本地配置文件里投影。
但”投影”这个词容易让人产生一个错觉:以为落盘的就是同一份 JSON,只是换了个地方放。实际读一遍 src-tauri/src/mcp/ 下的代码就会发现,路径不同只是最浅的一层差异,真正的差异在字段名和类型值上——同一条服务器写到不同应用,键名会改、类型枚举会换、数组会被合并、还会被塞进几个你没填过的字段。
本文只讲这一层:目标应用有哪几个、各自落到哪个文件的哪个键、格式要怎么改。至于双向同步的时机、校验规则拦住了哪些写法,我们另有篇目专门讲,这里不重复。
先确认面板管的到底是哪几个应用
我们采集时(2026-08-10)读到的快照是 cc-switch 的 c39c903,src-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_app对AppType::OpenClaw | AppType::ClaudeDesktop直接return Ok(()),即这两个应用不参与 MCP 投影(src-tauri/src/services/mcp.rs:530-534区段)McpService::sync_server_to_app_no_config对AppType::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 | 根对象 mcpServers | JSON | claude_mcp.rs:343-345、config.rs:176-184 |
| Codex | ~/.codex/config.toml | 顶层 [mcp_servers] | TOML | mcp/codex.rs:278-286 |
| Gemini | ~/.gemini/settings.json | mcpServers | JSON | gemini_mcp.rs:9-12、73-75 |
| GrokBuild | ~/.grok/config.toml | 顶层 [mcp_servers] | TOML | grok_config.rs:30-33、mcp/grokbuild.rs:1-4 |
| OpenCode | ~/.config/opencode/opencode.json | mcp | JSON | opencode_config.rs:48-60、222-240 |
| Hermes | ~/.hermes/config.yaml | mcp_servers | YAML | hermes_config.rs:100-102、mcp/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+args→command: [cmd, ...args](两个字段被合并成一个数组)env→environmenttype: "sse"/type: "http"→type: "remote"url→url
注意第四条:sse 和 http 两种类型在写出时都会塌缩成同一个 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:76、96)。所以你去 opencode.json 里看到一个自己没填过的 enabled 字段,来源在这。
同类的字段改名在 Codex 那边也有一处:统一结构里鉴权头叫 headers,写进 Codex 的 TOML 时字段名是 http_headers(mcp/codex.rs:87-89、687-695)。你手写 Codex 配置时如果照着 Claude 的 JSON 抄 headers,名字就对不上了。
Gemini:没有 type,靠字段名认传输方式
Gemini 是另一种思路。src-tauri/src/gemini_mcp.rs 的注释说明,Gemini CLI 不使用 type 字段,而是按字段名推断传输类型:HTTP 用 httpUrl,SSE 用 url(gemini_mcp.rs:32-37、104-112)。
因为不认 type,写入前会把一批字段直接删掉。被移除的是 type、enabled、source、id、name、description、tags、homepage、docs 共 9 个(gemini_mcp.rs:117-126)——后面这几个是 CC Switch 自己的 UI 辅助字段,落到 Gemini 的配置里没有意义。
超时那一段更需要留意。Gemini 侧只有一个 timeout,而统一结构里可能存在 startup_timeout_sec / startup_timeout_ms 与 tool_timeout_sec / tool_timeout_ms 两组四个字段。代码的做法是:先把秒/毫秒归一成毫秒,两组各取一个值(未设置时 startup 默认 10000ms、tool 默认 60000ms),然后取两者的 max 写成 Gemini 的 timeout(gemini_mcp.rs:128-152)。
换句话说,统一结构里如果同时带了启动超时与工具超时两组字段,到 Gemini 那边会被压成一个 timeout,取的是较大的那个。这两个默认值是源码里的默认配置,不是对实际运行结果的保证——具体该设多少取决于你挂的是什么服务器,项目没有给通用值。
Hermes:字段保不住的问题,它用 merge-on-write 解
Hermes 的推断方式和 Gemini 类似:无显式 type 字段,靠有 command 还是有 url 来判断(mcp/hermes.rs:12-15)。它自己还有一批额外字段,常量 HERMES_EXTRA_FIELDS 里列了 7 项:enabled、timeout、connect_timeout、tools、sampling、roots、auth(mcp/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 个:timeout、timeout_ms、startup_timeout_ms、startup_timeout_sec、connection_timeout、read_timeout、debug、log_level、disabled、shell、encoding、working_dir、restart_on_exit、max_restart_count、retry_count、max_retry_attempts、retry_delay、cache_tools_list、verify_ssl、insecure、proxy(mcp/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)。
你可以自己核的三件事
上面每一条都能落到具体文件的具体行,核查动作很直接:
- 数一遍目标应用:
grep -n "^mod " src-tauri/src/mcp/mod.rs会返回 7 行,其中validation不是应用模块,去掉它剩 6 个应用子模块(即mcp/mod.rs:14-19那六行);再打开src-tauri/src/mcp/claude.rs:90-97数McpApps的布尔字段,同样是 6 个,两处对得上。 - 对照手册与代码:打开
docs/user-manual/zh/3-extensions/3.1-mcp.md:101-109的开关表数行数,与上一步的字段名逐个对,差在哪一项一眼可见。 - 验证往返不守恒:
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。
本文内容为仓库源码与文档口径,我们没有安装或运行过这个桌面应用,
因此不涉及界面外观、操作手感与切换速度的任何描述。
文中出现的阈值与默认值均为源码中的默认配置,不构成对实际运行结果的保证。
该项目仍在快速迭代,版本与默认值随时可能变动,请以仓库最新内容为准。
安全相关做法请结合自身环境评估,本文不构成安全方案建议。