Claude Code Agent SDK 把 MCP 工具的名字统一拼接为 mcp__{server_name}__{tool_name},双下划线是固定分隔符,非法字符会被替换为下划线。该规范直接决定了 SDK 能否在多服务器场景下精准定位工具、并把白名单/黑名单规则命中到正确的工具实例上——名字一旦错位,权限控制就会形同虚设。
一、命名规范本身
1.1 格式定义
官方文档给出明确的拼装规则:服务器名与工具名之间使用双下划线 __ 作为分隔符,工具名遵循 mcp__{server_name}__{tool_name} 的三段式结构。例如 weather 服务器里名为 get_temperature 的工具,暴露给 Claude 时就变成 mcp__weather__get_temperature。多个服务器同时挂载时,这一前缀保证 get_temperature、search、query 这类常见名字不会互相覆盖。
1.2 字符归一化
服务器名与工具名在拼装前会做归一化处理,只保留 a-z、A-Z、0-9、_、- 几类字符,其他符号(包括空格、点号、斜杠)都会被替换为下划线。这样做的好处是命名在配置文件、权限规则、日志里都能被直接复制粘贴,不需要转义;代价是同名相近的服务器可能被压成同一个名字,所以命名阶段就要避免重名。
1.3 与其它 SDK 的差异
同样是 MCP 生态,Claude Agent SDK 用 __ 双下划线,而社区一些实现(如 Spring AI 的 Claude Agent SDK Java 教程)使用单下划线 {server}_{tool}。二者各有好处:双下划线在文本里更易一眼看出边界,单下划线更紧凑。Claude Code 自身的插件、内置工具、SDK 一律遵循 mcp__xxx__yyy 形式,自定义 MCP 服务器若改了前缀,反而会让 allowedTools、disallowedTools 规则无法命中。
二、命名规范如何决定权限控制
2.1 三层控制机制
Claude Code 的工具访问分两层:可用性(availability)与权限(permission)。前者决定工具是否出现在 Claude 上下文里,后者决定 Claude 调用时是否被放行。MCP 工具的名字正是这两层规则的命中键。
| 控制层 | 配置项 | 对 MCP 工具的影响 | 行为 |
|---|---|---|---|
| 可用性 | tools: ["Read","Grep"] |
不影响 MCP 工具 | 只裁剪内置工具 |
| 可用性 | tools: [] |
不影响 MCP 工具 | 移除全部内置工具,仅留 MCP |
| 可用性 | disallowedTools: ["Bash"] |
裸名匹配,按前缀同样有效 | 把 MCP 工具从上下文删除 |
| 权限 | allowedTools: ["mcp__weather__get_temperature"] |
作用域匹配 | 调用时跳过权限提示 |
| 权限 | disallowedTools: ["mcp__weather__*"] |
作用域匹配 | 命中调用直接拒绝,但工具仍可见 |
可以看到,权限规则全部基于工具全名生效。一旦 mcp__<server>__<tool> 在不同服务器间出现重名,规则会同时命中多台服务器,可能造成”想放开 A,结果放开了 B”的越权。
2.2 一个真实的踩坑
团队在引入两个 MCP 服务器 github 与 gitlab 时,都各自带了一个叫 search 的工具。Claude Code 会自动拼成 mcp__github__search 与 mcp__gitlab__search,互不冲突。但如果其中一台服务器名里带点(比如 git.hub),归一化后变成 mcp__git_hub__search,与另一个叫 git_hub 的服务器撞名,权限规则就会同时影响两台服务器。命名阶段在 server_name 里就避开点、空格,是规避这类问题的最稳妥手段。
三、配合 disallowedTools 的最小规则集
3.1 按命名空间批量拒绝
利用前缀 * 通配,可以一次禁掉某台服务器的全部工具或某种类型。常见做法是先把所有 MCP 工具加进上下文,再用 disallowedTools: ["mcp__*__delete_*"] 之类规则守住危险操作。
import { query, ClaudeAgentOptions } from "@anthropic-ai/claude-agent-sdk";
const options: ClaudeAgentOptions = {
mcpServers: {
filesystem: { command: "npx", args: ["@modelcontextprotocol/server-filesystem"] },
weather: { command: "npx", args: ["@modelcontextprotocol/server-weather"] },
},
allowedTools: [
"mcp__filesystem__read_file",
"mcp__filesystem__list_directory",
"mcp__weather__get_temperature",
],
disallowedTools: [
// 作用域规则:匹配所有 mcp 工具里名字含 delete 的调用
"mcp__*__delete_*",
],
};
for await (const message of query({ prompt: "列出当前目录", options })) {
// ...
}
3.2 规则生效顺序
Claude Code 在评估一次工具调用时,先看 disallowedTools 中是否有作用域规则命中——命中即拒绝;再看 allowedTools 是否有精准匹配——命中即放行;都没有则进入默认的权限提示流程。命名规范稳定的前提下,这条链路是可预期的;一旦名字拼错,整条链路都会偏移到不期望的工具上。
四、为什么这套规范对权限控制至关重要
权限规则本质上是字符串匹配。匹配的目标字符串一旦由”服务器名 + 工具名”决定,命名规范就成了整个权限模型的”坐标轴”:
- 全局唯一性:双下划线 + 归一化规则让工具全名在全局内互不重复,避免”放开 A 误放 B”。
- 可读性:日志、配置文件、用户审查界面里,
mcp__weather__get_temperature一眼能定位到来源。 - 可表达性:
*通配配合命名空间,能用一行规则禁掉一类操作(如所有delete_*、所有*__admin_*),比逐个列举省心。 - 可审计性:当规则出现”明明没放行却调通了”的情况,第一反应就是核对服务器名是否被归一化掉、名字是否撞车。
常见问题(FAQ)
Q1:自定义 MCP 服务器必须使用 mcp__ 前缀吗?
不是。前缀是 Claude Code 在注册阶段自动加上的,工具开发者只在 server_name 与工具本地名字上保持简洁清晰即可,避免特殊字符。
Q2:allowedTools 写错了会怎么样?
写错名字(拼写、归一化后不一致)不会报错,只会被视作”没有匹配项”,调用走默认权限流程,体感是”规则没生效”。
Q3:能用一个规则同时禁掉多台服务器的同种工具吗?
可以。disallowedTools: ["mcp__*__delete_*"] 这种通配写法会命中所有服务器里以 delete_ 开头的工具。