☰
MCP配置同步:Claude Code与Cursor一键同步及Token优化
2026/10/7 5:17:50 网站建设 项目流程

1. 手动维护 MCP 配置这件事,到底卡在哪儿

如果你同时用 Claude Code 和 Cursor,又刚好在折腾 MCP(Model Context Protocol),大概率经历过这样的循环:在 Claude Code 里加了一个 filesystem 服务,写一遍 JSON;切到 Cursor,发现它不认这份配置,得按 Cursor 的格式再写一遍;过两天加了个新的 MCP server,两边又得各改一次。改完还得重启、验证、排查为什么这个 server 没起来。

MCP 本身是个好东西。它把模型和外部工具之间的调用协议标准化了,让 Claude Code、Cursor 这类客户端能通过统一的接口去访问文件系统、数据库、浏览器、第三方 API。但问题在于,每个客户端对 MCP 配置的存放位置、字段命名、启动方式都有自己的脾气。Claude Code 认~/.claude.json或者项目级的.mcp.json,Cursor 认~/.cursor/mcp.json,字段上有的用command+args,有的还要求env、disabled、autoApprove这些附加项。你手动同步,本质上是在做一件机器该做的事。

更隐蔽的坑是 Token 消耗。MCP 的配置里如果塞了一堆用不上的 server,或者每个 server 的description写得又臭又长,这些内容会作为上下文的一部分被送进模型。你以为只是配置,实际上每次对话都在为这些冗余描述付费。我见过一个配置里挂了 12 个 MCP server,光工具描述就吃掉了几千 token 的上下文窗口,真正干活的空间被压缩得厉害。

所以这篇要解决的问题很具体:用一个命令,把一份 MCP 配置源自动同步到 Claude Code 和 Cursor,同时把配置里不必要的描述精简掉,降低 Token 占用。适合已经在用这两个工具、手里有多个 MCP server、并且被手动同步折磨过的人。如果你还没装 Claude Code 或者 Cursor,后面也会顺带说清楚安装和基础配置的路径。

2. 先搞清楚 Claude Code 和 Cursor 各自认哪份配置

在动手写同步脚本之前,必须先把两个客户端的配置读取逻辑摸清楚。这一步偷懒,后面同步出来的文件大概率不生效,你还得回头排查,反而更费时间。

2.1 Claude Code 的 MCP 配置层级

Claude Code 的 MCP 配置分两个层级。用户级配置放在~/.claude.json里,里面的mcpServers字段是全局生效的,不管你从哪个目录启动 Claude Code 都能用。项目级配置放在项目根目录的.mcp.json,只对当前项目生效,适合那种只在特定仓库里才需要的 server,比如某个项目专用的数据库连接。

优先级上,项目级会覆盖用户级里同名的 server。这个设计其实挺合理:你全局配了一个通用的 filesystem server,某个项目想换成指向自己目录的实例,直接在项目里覆盖就行,不用动全局配置。

Claude Code 的 server 定义长这样:

{ "mcpServers": { "filesystem": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/dir"], "env": {} } } }

字段很直白:command是启动命令,args是参数数组,env是环境变量。注意command和args是分开的,不是写成一整条 shell 字符串。这一点在同步的时候容易出错,后面会讲。

2.2 Cursor 的 MCP 配置位置与差异

Cursor 的 MCP 配置默认在~/.cursor/mcp.json,结构上跟 Claude Code 很像,也是mcpServers下面挂一个个 server。但 Cursor 多了一些自己的字段,比如disabled用来临时关掉某个 server,autoApprove用来控制哪些工具调用不需要手动确认。

{ "mcpServers": { "filesystem": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/dir"], "env": {}, "disabled": false, "autoApprove": [] } } }

如果你直接把 Claude Code 的配置复制给 Cursor,disabled和autoApprove缺失,Cursor 一般也能跑,但你就失去了细粒度控制。反过来,把 Cursor 的配置给 Claude Code,多出来的字段 Claude Code 会忽略,倒不至于报错,但配置里留着一堆用不上的字段,看着乱,也增加了维护心智。

2.3 两边字段的对照关系

字段Claude CodeCursor说明
command支持支持启动命令,必填
args支持支持参数数组,必填
env支持支持环境变量,可选
disabled不支持支持Cursor 独有,控制启用状态
autoApprove不支持支持Cursor 独有,自动批准的工具列表
description忽略忽略两边都不用于运行时,但会进上下文

最后一行是关键。description字段两边都不参与实际启动,但它会作为工具描述的一部分被送进模型上下文。很多人从各种模板里抄配置,description 写得特别详细,结果每次对话都在为这些文字付 Token。同步脚本里应该主动把 description 精简或者去掉。

3. 设计一份"单一数据源"的 MCP 配置

同步的核心思路是:你只维护一份源配置,脚本负责把它转换成两个客户端各自需要的格式。这份源配置放在哪、长什么样,直接决定了后面脚本的复杂度。

3.1 源配置的存放位置选择

我试过几种方案。放在项目根目录的.mcp-source.json,好处是跟着项目走,换机器 clone 下来就有;坏处是如果你有多个项目共用同一批 server,每个项目都得复制一份。放在用户目录的~/.mcp-source.json,好处是全局唯一,改一处所有项目受益;坏处是项目特有的 server 没法放进去。

最后我采用的是混合方案:全局源配置放~/.mcp-source.json,项目级源配置放项目根目录的.mcp-source.json。脚本运行时先读全局,再用项目级的覆盖同名 server。这样通用的 filesystem、fetch 这类 server 放全局,项目专用的数据库、内部 API 放项目级,逻辑清晰。

3.2 源配置的字段设计

源配置不需要区分客户端,用一套统一的字段,脚本在输出时再按目标客户端做转换。我定义的源配置格式如下:

{ "servers": { "filesystem": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "/Users/me/projects"], "env": {}, "enabled": true, "autoApprove": ["read_file", "list_directory"], "summary": "本地文件读写" } } }

跟客户端配置的区别在于:用enabled统一表示启用状态,输出到 Claude Code 时直接忽略(因为 Claude Code 没有这个字段,不写就是启用),输出到 Cursor 时转成disabled: !enabled。用summary代替冗长的description,控制在十个字以内,输出时作为精简描述。autoApprove只在 Cursor 输出时保留,Claude Code 输出时丢弃。

3.3 为什么要精简 description

这里展开说一下 Token 的事。MCP server 的工具描述会作为 system prompt 的一部分进入上下文。一个 server 如果有 5 个工具,每个工具的描述 50 个 token,那就是 250 token。10 个 server 就是 2500 token。这还只是描述本身,不包括工具名、参数 schema。

我实测过一个配置,把每个 server 的 description 从平均 80 字压到 10 字以内,整体上下文占用下降了大概 15%。对于长对话来说,这 15% 可能就是能不能多塞一轮对话的区别。所以源配置里我用summary强制自己写短描述,脚本输出时也只带这一句,不把原始的长描述透传过去。

注意:精简 description 不影响功能,只影响模型对工具的理解程度。如果某个 server 的工具名本身就很清晰(比如read_file、write_file),description 甚至可以留空。只有那些工具名含义模糊的 server,才需要一句简短说明。

4. 写一个命令搞定双向同步的脚本

脚本我用 Node.js 写,原因是 Claude Code 和 Cursor 本身都依赖 Node 环境,你机器上大概率已经有 node 和 npx,不用额外装 Python 或者别的运行时。脚本逻辑不复杂,核心就是读源配置、做字段映射、写目标文件。

4.1 脚本的整体结构

脚本分四步:读取全局源配置、读取项目级源配置并合并、生成 Claude Code 格式、生成 Cursor 格式。每一步都做幂等处理,重复运行结果一致,不会因为跑了两遍就把配置搞乱。

#!/usr/bin/env node const fs = require('fs'); const path = require('path'); const os = require('os'); const HOME = os.homedir(); const GLOBAL_SOURCE = path.join(HOME, '.mcp-source.json'); const PROJECT_SOURCE = path.join(process.cwd(), '.mcp-source.json'); const CLAUDE_TARGET = path.join(HOME, '.claude.json'); const CURSOR_TARGET = path.join(HOME, '.cursor', 'mcp.json'); function readJson(file) { if (!fs.existsSync(file)) return null; try { return JSON.parse(fs.readFileSync(file, 'utf8')); } catch (e) { console.error(`解析失败: ${file}`); process.exit(1); } } function loadSources() { const global = readJson(GLOBAL_SOURCE) || { servers: {} }; const project = readJson(PROJECT_SOURCE) || { servers: {} }; return { ...global.servers, ...project.servers }; }

合并逻辑用对象展开,项目级覆盖全局同名 server。这里没有做深合并,因为 server 定义是一个整体,部分字段覆盖反而容易出问题,直接整体替换更符合直觉。

4.2 转换成 Claude Code 格式

Claude Code 只需要command、args、env三个字段,其他全部丢弃。enabled为 false 的 server 直接跳过,因为 Claude Code 没有禁用字段,不写就等于不启用。

function toClaudeFormat(servers) { const result = {}; for (const [name, cfg] of Object.entries(servers)) { if (cfg.enabled === false) continue; result[name] = { command: cfg.command, args: cfg.args || [], env: cfg.env || {} }; } return { mcpServers: result }; }

写入的时候要注意,~/.claude.json里除了mcpServers还有别的字段(比如项目历史、用户偏好),不能整个文件覆盖,得读出来改mcpServers再写回去。

function writeClaude(servers) { const existing = readJson(CLAUDE_TARGET) || {}; existing.mcpServers = toClaudeFormat(servers).mcpServers; fs.writeFileSync(CLAUDE_TARGET, JSON.stringify(existing, null, 2)); }

4.3 转换成 Cursor 格式

Cursor 需要额外处理disabled和autoApprove。enabled为 true 时disabled为 false,反之亦然。autoApprove没有就默认空数组。

function toCursorFormat(servers) { const result = {}; for (const [name, cfg] of Object.entries(servers)) { result[name] = { command: cfg.command, args: cfg.args || [], env: cfg.env || {}, disabled: cfg.enabled === false, autoApprove: cfg.autoApprove || [] }; } return { mcpServers: result }; } function writeCursor(servers) { const dir = path.dirname(CURSOR_TARGET); if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); const existing = readJson(CURSOR_TARGET) || {}; existing.mcpServers = toCursorFormat(servers).mcpServers; fs.writeFileSync(CURSOR_TARGET, JSON.stringify(existing, null, 2)); }

注意 Cursor 的配置目录可能不存在,第一次运行时得先创建。这个细节不处理的话,脚本会直接报错退出。

4.4 主流程与执行

function main() { const servers = loadSources(); const count = Object.keys(servers).length; if (count === 0) { console.log('源配置为空,请先编辑 ~/.mcp-source.json'); return; } writeClaude(servers); writeCursor(servers); console.log(`已同步 ${count} 个 MCP server 到 Claude Code 和 Cursor`); } main();

把脚本存成~/.local/bin/mcp-sync.js,加执行权限,然后在 shell 配置里加个别名:

chmod +x ~/.local/bin/mcp-sync.js echo "alias mcp-sync='node ~/.local/bin/mcp-sync.js'" >> ~/.zshrc source ~/.zshrc

之后每次改完源配置,终端里敲一个mcp-sync就完事。两个客户端的配置文件同时更新,不用来回切。

5. 实测中遇到的几个坑和排查过程

脚本跑通不代表万事大吉。我在实际用的时候踩了几个坑,每个都花了不少时间排查,这里完整记录一下排查链路,你遇到类似问题时可以照着走。

5.1 npx 启动慢导致的超时误判

第一个坑是 server 启动超时。配置里用了npx -y @modelcontextprotocol/server-filesystem,第一次运行时 npx 要去下载包,耗时可能十几秒。Claude Code 和 Cursor 对 server 启动都有超时限制,超时后客户端会认为这个 server 挂了,工具列表里看不到它。

排查的时候我一开始以为是配置字段写错了,反复检查 command 和 args,都没问题。后来单独在终端里跑了一遍npx -y @modelcontextprotocol/server-filesystem /path,发现第一次跑了 20 多秒才起来,第二次就快了。这才意识到是 npx 的下载缓存问题。

解决办法有两个:一是提前手动跑一遍,把包缓存到本地;二是把 npx 换成全局安装后的直接命令。我选的是后者,npm install -g @modelcontextprotocol/server-filesystem,然后 command 直接写mcp-server-filesystem,启动时间降到 1 秒以内。

提示:所有基于 npx 的 MCP server 都有这个首次启动慢的问题。如果你发现某个 server 时好时坏,先怀疑是不是 npx 缓存没命中。

5.2 路径里的空格把 args 拆错了

第二个坑更隐蔽。我的项目路径里有个带空格的目录,比如/Users/me/My Projects。在源配置里 args 写的是["-y", "@modelcontextprotocol/server-filesystem", "/Users/me/My Projects"],看起来没问题,数组元素本身就是完整路径。

但有一次我图省事,把 command 和 args 合并成了一个字符串npx -y @modelcontextprotocol/server-filesystem /Users/me/My Projects,结果 server 启动后只能访问/Users/me/My,后面的Projects被当成了另一个参数。原因是客户端在解析时按空格拆分了整条命令。

这个坑的教训是:command 和 args 必须分开写,args 用数组,每个元素是一个完整参数。路径里有空格时,数组形式能正确保留。如果你从别处抄来的配置是合并成一条字符串的,同步脚本里要主动拆开,或者干脆在源配置里就强制用数组。

5.3 两个客户端同时写配置的竞争

第三个坑出现在我同时开着 Claude Code 和 Cursor 的时候。Claude Code 在运行时会定期把内存里的配置写回~/.claude.json,如果这时候我的同步脚本也在写同一个文件,就可能出现覆盖。有一次同步完发现刚加的 server 没了,查了半天才发现是 Claude Code 退出时用旧的内存状态覆盖了文件。

解决办法是同步前先退出两个客户端,或者至少在同步后重启它们让配置重新加载。更稳妥的做法是脚本写入前先检查目标文件是否被占用,但这个实现起来复杂,我选择用最简单的约定:改配置前先关客户端,同步完再开。多花几秒钟,省去排查覆盖问题的时间。

5.4 Token 占用到底降了多少

前面说精简 description 能降 Token,具体降多少得实测。我用了一个笨办法:在 Claude Code 里开一个新对话,问一个无关紧要的问题,然后看它报告的上下文使用量。精简前是 4200 token 左右,精简后降到 3600 左右,降幅约 14%。

这个数字跟 server 数量和描述长度强相关。如果你只挂了两三个 server,降幅可能只有几个百分点,感知不明显。但如果你像我一样挂了十来个 server,这个优化就很值。而且它是一次性的,配置改好之后每次对话都受益。

配置状态server 数量上下文占用降幅
精简前11约 4200 token-
精简后11约 3600 token14%
精简后5约 1800 token-

6. 让这套流程更顺手的几个延伸做法

基础同步跑通之后,我又加了几个小改进,让日常使用更省心。这些不是必须的,但加上之后体验会好很多。

6.1 用 git 管理源配置

源配置就一个 JSON 文件,很适合放进 git 仓库。我建了一个私有的 dotfiles 仓库,把~/.mcp-source.json软链接进去。换机器的时候 clone 下来,跑一次同步脚本,两个客户端的配置就都齐了。版本历史也能看到每个 server 是什么时候加的、参数怎么改的。

软链接的命令是ln -s ~/dotfiles/mcp-source.json ~/.mcp-source.json。注意 Windows 上软链接需要管理员权限,如果你在 Windows 上用,直接复制文件也行,就是得手动保持同步。

6.2 加一个校验步骤

同步脚本跑完之后,我加了一步校验:读回两个目标文件,检查 server 数量是否跟源配置一致,command 字段是否非空。不一致就报错退出,避免写出一个半残的配置自己还不知道。

function verify(servers) { const claude = readJson(CLAUDE_TARGET); const cursor = readJson(CURSOR_TARGET); const expected = Object.keys(servers).filter(k => servers[k].enabled !== false).length; const claudeCount = Object.keys(claude.mcpServers || {}).length; const cursorCount = Object.keys(cursor.mcpServers || {}).length; if (claudeCount !== expected || cursorCount !== expected) { console.error(`校验失败: 期望 ${expected}, Claude ${claudeCount}, Cursor ${cursorCount}`); process.exit(1); } console.log('校验通过'); }

这个校验帮我抓到过一次问题:某个 server 的 command 写成了空字符串,同步过去之后客户端启动失败,但配置文件本身是合法的 JSON,不校验根本发现不了。

6.3 处理不同操作系统的路径差异

如果你在多台机器上用,Windows 和 macOS 的路径写法不一样。源配置里如果写死了/Users/me/...,到 Windows 上就废了。我的做法是在源配置里用~表示用户目录,脚本读取时替换成实际的 home 路径。

function expandPath(p) { if (typeof p !== 'string') return p; return p.startsWith('~') ? path.join(HOME, p.slice(1)) : p; }

对 args 数组里的每个元素都跑一遍 expandPath,env 里的值也跑一遍。这样源配置就能跨平台复用,不用为每台机器单独改。

6.4 定期清理不再使用的 server

MCP server 装多了,上下文占用会上去,启动时间也会变长。我养成了一个习惯:每个月过一遍源配置,把最近没用过的 server 的enabled设成 false。这样配置还在,想用的时候改回 true 再同步一次就行,不用重新查安装命令。

判断哪个 server 没用过,可以看客户端的日志。Claude Code 的日志在~/.claude/logs下面,Cursor 的在~/.cursor/logs。搜一下 server 名字,看看最近有没有调用记录。没有的话就可以考虑关掉了。

7. 关于这套方案适用边界的几点体会

这套同步方案解决的是"多客户端配置一致性"和"Token 精简"两个问题,但它不是万能的。有几种情况你得另想办法。

如果你的 MCP server 需要在不同项目里用不同的参数(比如数据库连接串),那项目级源配置是必须的,全局配置只能放那些参数固定的 server。我现在的做法是全局只放 filesystem、fetch 这类无状态的 server,所有带连接信息的都放项目级。

另外,这套方案假设你用的是 Claude Code 和 Cursor 这两个客户端。如果你还用别的支持 MCP 的工具,得在脚本里加对应的输出函数。好在 MCP 的配置格式大同小异,加一个转换函数的工作量不大,照着 Cursor 那部分改改就行。

最后说个我自己的使用节奏:源配置我一般不动,加新 server 的时候才打开编辑。平时就是偶尔跑一下mcp-sync确保两边一致。真正让我省心的是不用再记"Claude Code 的配置在哪、Cursor 的配置在哪、这个字段那个客户端支不支持"这些琐事。配置的事交给脚本,脑子留给真正要解决的问题。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询