你在允许列表里写下 mcp__a,因为你信任服务器 a。但在 Claude Code 2.1.299 之前,这条规则还会让 Claude Code 调用另一个名为 a__b 的服务器,全程不问你一句。
![]()
2.1.299 于 2026年10月14日发布,带 131 项缺陷修复。其中一条改动写着:MCP 允许规则只能触达它点名的服务器,mcp__a 不再放行名为 a__b 或 a_ 的服务器;要覆盖某个服务器,得写 mcp__<服务器完整规范化名称>__*。另一行把 agent 工具的 tools: 列表做了同样处理,缺陷修复里还提到:服务器名含双下划线时,针对它的 deny 和 ask 规则现在才会生效。
这三行描述的是同一个 bug,根源在于分隔符可以出现在哪里。
Claude Code 给每个 MCP 工具一个扁平名字:mcp__,接着服务器名,再接着 __,最后是工具名。权限文档给出三种规则形态:mcp__puppeteer 匹配该服务器的任意工具,mcp__puppeteer__* 用通配符匹配同一集合,mcp__puppeteer__puppeteer_navigate 匹配单个工具。
服务器名就是你在 claude mcp add 里输入的内容,或 .mcp.json 文件里的键。MCP 文档说这些名字只能包含字母、数字、连字符和下划线。连续两个下划线仍然是下划线,所以 a__b 和 a_ 都是合法服务器名。工具名有同样的自由,MCP 规范允许字母、数字、下划线、连字符和点,服务器完全可以给工具起名 b__whoami。
现在为三个服务器上的同一个工具 whoami 拼出名字:
- 服务器 a → mcp__a__whoami
- 服务器 a__b → mcp__a__b__whoami
- 服务器 a_ → mcp__a___whoami
分隔符是 __,而服务器名本身允许含 __。从左边读第二行,在前缀之后停在第一个 __:你得到服务器 a、工具 b__whoami。第三行同理,得到服务器 a、工具 _whoami。两者看起来都像服务器 a 的工具,而一条裸的 mcp__a 规则意味着"服务器 a 的任意工具"。
两个不同的服务器,同一个前缀:光看工具名,说不清服务器名在哪里结束。
更新日志没有公布旧的匹配器,所以上面这段只能当作一种重建。它是我能找到的最简读法,能同时产出更新日志给出的两个例子(a__b 和 a_),也解释了同一版本里 deny 侧的修复。一条为拦截服务器 a__b 而写的 deny 规则 mcp__a__b,在同样的切分下读作"服务器 a、工具 b",而 a__b 没有任何工具真的叫 b。发布说明称,当"服务器的规范化名称含双下划线"时,针对某个 MCP 服务器的 deny 和 ask 规则不会生效。同样的切分,反方向。
服务器 a 有个工具叫 b__whoami,服务器 a__b 有个工具叫 whoami,两者产出完全相同的字符串 mcp__a__b__whoami。任何解析器都无法只凭名字区分这两者。匹配器必须知道配置了哪些服务器,这正是更新日志里"服务器的完整规范化名称"所指的方向。MCP 规范从客户端一侧发出过警告:从多个服务器聚合来的工具"可能遇到命名冲突",客户端"应当实现消歧策略,例如给工具名加上服务器标识前缀"。前缀只有在标识符不能包含分隔符时才有用。
允许规则会跳过权限提示。一条触达第二个服务器的规则,等于为你从未审查过的服务器跳过了提示。
谁来挑服务器名?你,通过 claude mcp add。你打开的任何一个附带 .mcp.json 的仓库也可以。按权限文档,在交互式会话中,Claude Code 会在连接这些项目服务器前询问你。而在 claude -p 或 SDK 下、在一个你从未信任过的文件夹里,同一份文档说它们"连接时不询问,无论是否获批"——如果你的 CI 对检出的仓库跑 claude -p,这一点就很重要。而服务器连上,和让它每次工具调用都不经提示地运行,是两个不同的决定。设想一条用户级的 mcp__github 规则,加上一个名为 github__mirror 的项目服务器:在旧的匹配逻辑下,那个服务器一旦连上,这条规则很可能也覆盖了它的工具。这只是假设,我没有见过已报告的事件。
Anthropic 的托管 MCP 文档在一条警告里讲了这个普遍问题:serverName 条目"不是安全控制",因为"用户可以调用任何服务器 github"。对于管理员允许列表,他们指向 serverCommand 或 serverUrl。权限规则没有这样的选项,它们以名字为键,所以值得搞清楚一个名字能伸多远。
为一道门写的允许规则,也打开了旁边那道门。
要复现,需要 Node 和 Claude Code。下面的服务器使用 v2 MCP TypeScript SDK,包名 @modelcontextprotocol/server,暴露一个工具,说出是哪个服务器应答的。把同一个文件用两个名字注册到临时文件夹里:
- claude mcp add a -- node "$PWD/dummy.mjs" a
- claude mcp add a__b -- node "$PWD/dummy.mjs" a__b
claude mcp add 默认使用 local 作用域,只在这个文件夹里生效。接着在文件夹的本地设置里只允许服务器 a。修复方式更新日志写得很清楚:mcp__<服务器完整规范化名称>__*。对这两个假服务器就是 mcp__a__* 和 mcp__a__b__*。
对于插件里捆绑的服务器,完整规范化名称要包含插件。MCP 文档给出的模式是 mcp__plugin_<插件名>_<服务器名>__<工具名>,其中字母、数字、下划线和连字符之外的任何字符都变成下划线。文档还警告,针对裸服务器键写的 hook 匹配器对插件服务器永远不会触发。权限规则匹配的是同一批工具名,所以要针对完整的 plugin_… 名称来写。
我个人的偏好更进一步:干脆别让服务器名里出现 __。连字符是允许的,a-b 得到 mcp__a-b__whoami,无法被两种方式切分。删掉服务器,用新名字重新添加,再更新指向它的规则。按权限文档,规则按"deny、然后 ask、然后 allow"的顺序检查,第一个匹配者胜出,所以一条宽泛的 deny 仍然压过你写的任何 allow。
这个 bug 活在一条边界上:一个扁平字符串承载两个标识符,而分隔符是两者都被允许包含的字符。
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.