给 MCP 服务锁版本,避免工具悄然变更
文章抽样的 346 个公开仓库中,46% 至少有一个本地 MCP 服务未固定版本,可能随启动引入不兼容更新。作者建议固定包版本或容器摘要、保存工具列表,并通过脚本在 CI 中拦截未锁定配置。
AI 深度解读
MCP 配置里的一行 npx -y @scope/server,可能让你在没有修改代码的情况下,启动一个刚发布的新版本。原文的主张很明确:把本地 MCP server 固定到精确版本,保存工具定义,并用 CI 阻止未固定的配置进入仓库。
它值得程序员花几分钟读,因为问题不只在供应链安全。客户端和服务端升级不同步,也可能让工具突然消失。作者给出了实际版本对比和故障案例,同时提醒:固定版本只是起点,依赖、权限和漏洞更新还得分别管理。
配置没改,为什么工具突然变了
原文引用的调查从 2,999 个包含 agent skills 或 MCP 配置的公开 GitHub 仓库中,找出 346 个配置了本地 MCP server 的仓库。其中 159 个,也就是 46.0%,至少有一个服务没有固定版本;63 个,即 18.2%,明确使用了 latest。
这不是全量普查。样本受 GitHub 代码搜索影响,人工抽查的 22 条发现里有 19 条正确。即使只看至少 100 stars 的仓库,未固定比例也达到 57.8%,但这个分组只有 45 个仓库,不能据此推断整个生态。
未固定配置把版本选择交给了启动时的解析过程。例如 npx -y some-package、uvx some-package,以及未指定标签的容器镜像,都没有表达「团队验证过哪个版本」。@latest、@next、@^2.1.0、@~0.9、@* 同样不是精确固定。
作者在 10 月 21 日查询 npm registry,发现 @modelcontextprotocol/server-filesystem 的 1.0.0 于 10 月 11 日发布,2.0.0 在 10 月 19 日接替它成为 latest。只隔八天,配置就可能跨了一个主版本。
新版依赖从 @modelcontextprotocol/sdk 1.x 换成了 @modelcontextprotocol/server 2.x,Node 要求也提高到 Node 24 或以上。
作者实际运行了两个版本:14 个工具的名称、描述和输入 schema 一致,变化只是新版移除了每个工具的 execution 字段。这次没有发现工具契约上的实质变化,但运行环境已经变了。
固定版本的价值,是让升级成为一次可以检查的变更。
工具归零,可能是协议升级没对上
原文最具体的兼容性案例来自 github/github-mcp-server#3453。
报告者使用没有标签的 ghcr.io/github/github-mcp-server,当时取得的是 v2.0.1。服务从 v2.0.0 开始协商 MCP 协议 2026-07-28,却没有在 tools/list 中提供该修订要求的 resultType 字段。
结果是 Claude Code 能连接,却加载不到工具;协商较早协议的 Codex CLI 则能正常使用。报告者通过固定到 ghcr.io/github/github-mcp-server:v1.14.0 绕过了问题。
客户端也在变化。Claude Code v2.1.299 于 10 月 14 日调整了 cloud-session 和 Remote Control MCP connectors 的默认协议协商,提供 MCP_PROTOCOL_NEGOTIATION=legacy 作为退出选项。10 月 16 日的 v2.1.302 又修复了协商 2026-07-28 时,缺少 resultType、ttlMs 或 cacheScope 导致工具不显示或调用失败的问题。
这里不能把某类 connector 的默认行为,扩大成所有连接方式的行为。但案例足够说明:排查「零工具」时,服务进程是否启动成功只是第一步,还要看双方协商了什么协议。
固定服务端并不能消除客户端升级的影响,却能减少一个同时变化的变量。
除了版本号,还要保存模型看到的工具定义
包版本回答的是「运行了哪份软件」,tools/list 回答的是「模型看到了哪些工具,以及怎样使用它们」。
工具描述会被模型读取。升级时只看包名和版本号,可能漏掉描述、参数约束或工具名称的变化。作者建议在添加服务和调整版本时,把工具列表保存到 mcp-contracts/,纳入代码审查。
下面是作者导出 filesystem 工具定义时使用的命令:
npx --yes @modelcontextprotocol/inspector@2.11.0 --cli \
npx -y @modelcontextprotocol/server-filesystem@2.0.0 /path/to/project \
-- --method tools/list --format json > mcp-contracts/filesystem-2.0.0.json
要看第一行:用于检查服务的 Inspector 也固定到了 2.11.0,检查工具自身同样需要可复现。
比较时优先关注工具名称、描述和输入 schema。作者的两个 filesystem 版本中,14 个工具的原始 JSON 都有差异,原因却只是删除了 execution。逐字节比较容易制造审查噪声。
配置检查则负责另一件事:阻止新的浮动版本进入仓库。原文介绍了一个 96 行的 mcp-pin-check.py,要求 Python 3.11 或以上,通过 tomllib 读取 TOML,不需要额外依赖。
它覆盖 Claude Code、Cursor、Codex、OpenCode 等配置,也读取 .vscode/mcp.json 的 servers,以及容易漏掉的 ~/.claude.json 项目级本地配置。发现未指定版本、latest、范围或浮动标签时输出 FAIL,并以退出码 1 结束;容器版本标签输出 WARN,因为标签仍可能被重新指向。
作者测试过 JSONC、npx --package=…、uvx --from pkg==…、pnpm dlx、bunx、Windows 的 cmd /c npx 和 Podman 等情况。但它是启发式检查,遇到不认识的启动器会输出 INFO。本次提供的原文没有附出完整脚本,不能直接把介绍当成可运行交付。
作者还建议把完成条件写进 AGENTS.md:
## MCP servers
- Pin every local MCP server to an exact version (image digest for containers).
- Save its tools/list to mcp-contracts/ when you add or bump one.
- `python3 scripts/mcp-pin-check.py` must exit 0 before you call the change done.
最后一行要由 CI 真正执行。文字规则帮助 agent 理解要求,退出码才负责拦截。
固定版本之后,哪些风险还在
顶层包固定,不等于整个运行环境固定。
npx、uvx 固定顶层包后,传递依赖仍可能在安装时重新解析。原文引用的一位评论者称,同一份 mongodb-mcp-server@2.1.2,在不同日期安装时,因为解析到不同的 zod 次版本,出现了参数类型差异。这是社区报告,不是作者复现的实验。
锁文件或镜像 digest 可以进一步约束依赖。容器应固定到 digest,仅使用版本标签仍有内容被替换的可能。
权限也需要单独处理。原文引用的研究收录了 21,097 个 MCP server,对其中 4,806 个进行初筛,发现 787 个,即 16.4%,具有与其分类不符的文件系统能力。这是粗筛信号,不是漏洞数量。后续动态测试确认了 150 个存在漏洞的实例,协调披露产生 13 份公告,包括 Anthropic Git MCP server 的两个 CVE。
因此,服务应只挂载所需目录,尽可能只读;不需要联网时使用 --network=none。每个服务通过自己的 env 配置取得最小权限 token,也比依赖 shell profile 中广泛导出的凭据更便于控制。
固定版本还会把漏洞一起固定。原文列出的 GHSA-84m7-p3x7-pcfv,即 CVE-2026-59951,影响 MCP Python SDK 默认有状态模式下的 Streamable HTTP 服务:会话状态没有回收,客户端可通过普通短请求持续消耗内存。
受影响版本为 mcp 1.8.0–1.29.1 和 2.0.0–2.1.1;修复版本为 1.30.0、2.2.0,新增默认 1,800 秒空闲超时和 10,000 会话上限。不需要会话状态且暂时无法升级时,公告建议使用 stateless_http=True。这个场景针对可被不完全可信客户端访问的 Python MCP 服务,不能直接套到所有本地 stdio 服务上。
我的判断
我赞成把精确版本、工具契约和 CI 检查放在一起。它们分别解决版本漂移、模型可见接口变化和团队执行问题,比单独写一句「谨慎安装 MCP」更容易落实。
但原文没有提供团队实施前后的故障率、维护成本或对照数据,因此这是一套有案例支撑的工程建议,还不能宣称效果经过系统验证。
也要修正其中过于绝对的启动描述。npx 会受缓存和解析行为影响,Docker 启动也受本地镜像及拉取策略影响,不能一概理解为每次启动都必然下载最新内容。准确的问题是:浮动配置没有保证下一次仍运行同一版本。
如果团队已经通过锁文件安装服务,再调用固定的本地入口,这也是可行路径。具体选锁文件还是容器 digest,取决于运行方式;关键是把依赖解析结果纳入管理,并给升级安排明确的负责人。
能拿来做什么
- 在自己的电脑和项目仓库各检查一次 MCP 配置,删除不用的服务,并将
latest、版本范围和无版本启动参数改成经过检查的精确版本。使用 filesystem 服务时,同时确认 Node 是否满足要求。 - 给新增或升级的服务保存一份
tools/list,审查工具名称、描述和输入 schema 的变化,再把配置检查接入 CI。CI 通常只能检查提交到仓库的配置,个人目录仍需本机检查。 - Claude Code 出现零工具时,记录客户端版本、服务端版本和协商协议。针对原文的缺字段案例,检查是否已经升级到
v2.1.302或后续版本,MCP_PROTOCOL_NEGOTIATION=legacy只作为临时绕行。 - 如果维护对外可访问的 Python MCP 服务,对照上述受影响范围检查 SDK,并升级到修复版本;同时给已固定的依赖安排公告跟踪,避免版本长期无人维护。