博客 • MCP
升级到 Claude Code 2.1.305 之后,managed-mcp.json 里只在 env 写了变量的 stdio 服务器又加载了,deniedMcpServers 要怎么写?
Claude Code 2.1.305 的更新说明只有一句,但升级之后会看见服务器自己出现:allowedMcpServers 不再过滤 managed-mcp.json 里那些只在 env 使用 ${VAR} 的 stdio 服务器。允许名单原来没写它,升级后它会加载。要继续停住,改的是 deniedMcpServers,不是把允许名单再抄一遍。
谁负责 MCP 的权限,写在MCP 安全那篇。OAuth 凭证交给错误授权服务器时日志停在哪一步,写在上一篇 OAuth 文。本篇只写 2.1.305 这一处:允许名单不再挡住只在 env 使用变量的 stdio 服务器,拒绝名单的 JSON 要和命令完全一致。
- 对象是 managed-mcp.json 里的 stdio 服务器,而且 ${VAR} 只出现在 env。
- 2.1.305 起,允许名单不再把它滤掉。允许名单里没有它,升级后会加载。
- 要继续停住,deniedMcpServers 里的 serverCommand 必须是命令加参数的数组,顺序也要一致。
- 空的拒绝名单形状合法,但什么也不挡。serverName 不是安全控制。
直接结论:只在 env 使用 ${VAR} 的 stdio 服务器,2.1.305 之后不再靠允许名单停住。拒绝名单里写下与 command 和 args 完全相同的 serverCommand。
2.1.305 改了允许名单的哪一处?
更新说明写的是:allowedMcpServers 不再过滤 managed-mcp.json 里那些只在 env 使用 ${VAR} 的 stdio 服务器。允许名单原来没写上的那一台,升级后会加载。要继续停住,用 deniedMcpServers。原文在Claude Code 更新说明。
组织级 MCP 文档仍写着更早的规则:managed-mcp.json 里的服务器本来跳过允许名单,但定义里只要有 ${VAR},就还要过允许名单。2.1.259 之前,只要设了允许名单,每台托管服务器都要过。2.1.305 把其中一窄条拿掉了:stdio,而且变量只在 env。更新说明没有说命令、参数、URL 或 headers 里的 ${VAR} 也免检。文档在组织级 MCP 文档。
怎样看出变量只写在 env?
文档里的 stdio 例子有 type、command、args 和 env。密钥不要写进 env 的值里。每人自己的凭证用 ${VAR} 从该用户的环境读取。下面这份把变量只放在 env,命令和参数都是字面量。这就是 2.1.305 说的那一种。命令路径是占位符,不是要安装的包。
{
"mcpServers": {
"notes": {
"type": "stdio",
"command": "/usr/local/bin/notes-server",
"args": ["--stdio"],
"env": {
"NOTES_TOKEN": "${NOTES_TOKEN}"
}
}
}
}
如果 ${VAR} 写在 command、args、URL 或 headers 里,更新说明没有把它们算进这次放行。那种服务器仍按文档里的允许名单检查来对待,直到更新说明自己改口。本篇的拒绝名单例子只覆盖 env 这一种。
deniedMcpServers 要和哪一条命令一致?
文档里,serverCommand 匹配的是启动 stdio 服务器的命令和参数,而且要完全一致。上面的 command 加上 args,就是这一条数组。少一个参数、多一个参数、或把顺序换掉,都对不上。serverName 只是标签。文档写明,允许名单或拒绝名单里的 serverName 不是安全控制,因为用户可以给任何服务器起同一个名字。
{
"deniedMcpServers": [
{
"serverCommand": ["/usr/local/bin/notes-server", "--stdio"]
}
]
}
拒绝名单对托管服务器同样生效,用户自己的 deniedMcpServers 也会并进来,所以用户可以只为自己挡住一台托管服务器。允许名单是空数组时,文档说除了跳过允许名单检查的服务器以外,都不允许加载。2.1.305 之后,只在 env 使用变量的这台 stdio 服务器正是跳过的那一种,空的允许名单留不住它。
这份拒绝名单用什么 Schema 卡住形状?
用 JSON Schema 卡住类型。deniedMcpServers 必须是数组。每一项的 serverCommand 必须是字符串数组。空数组形状仍然合法。它不证明这台服务器会被停住。停住它的是应用规则:数组要和 command 加 args 完全相同。
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"required": ["deniedMcpServers"],
"properties": {
"deniedMcpServers": {
"type": "array",
"items": {
"type": "object",
"required": ["serverCommand"],
"properties": {
"serverCommand": {
"type": "array",
"items": { "type": "string" }
}
}
}
}
}
}
| 看到的记录 | Schema | 要停住这台服务器时 |
|---|---|---|
| serverCommand 写成一整段字符串 | 失败。这里要字符串数组。 | 先拆成命令和参数两个字符串。 |
| 参数写成数字 | 失败。数组里的每一项都是字符串。 | 改成和 args 里相同的字符串。 |
| deniedMcpServers 是空数组,形状正确 | 通过。空数组仍是数组。 | 补上与 command 和 args 相同的 serverCommand。空名单什么也不挡。 |
加载前的三步,哪一步真正停住它?
文档把加载前的检查写成三步,并且在用户于 /mcp 里重新连接或重新打开时再跑一遍。进程内的 sdk 类型跳过这三步。拒绝名单命中之后,没有东西能推翻。
| 步骤 | 这台 env-only 服务器 |
|---|---|
| 合并各层名单 | 各设置层的允许名单和拒绝名单合成一份。allowManagedMcpServersOnly 为 true 时,只留托管来源的允许名单。拒绝名单始终从每一层合并。 |
| 先查拒绝名单 | serverCommand 完全一致就停住。这一步之后,允许名单不能把它放回来。 |
| 再查允许名单 | 2.1.305 之后,这台只在 env 使用变量的 stdio 服务器跳过这一步。允许名单里没有它,也不再因此停住。 |
在 JSONNote 里怎样核对这份拒绝名单?
- 1选对操作
打开 JSON Schema。操作选「用 Schema 校验」。第一栏贴拒绝名单,第二栏贴上面的 Schema。serverCommand 必须是字符串数组。
- 2撞四条坏数据
serverCommand 写成一整段字符串,或删掉 deniedMcpServers,或把参数写成数字,或把 deniedMcpServers 写成对象。这四条都会失败。上面那份拒绝名单会通过。通过只说明形状对。
- 3对比空名单和那条命令
打开 JSON 对比 Diff。第一栏贴上面的拒绝名单。第二栏把 deniedMcpServers 改成空数组。两份都能通过 Schema。挡住这台服务器的是那条 serverCommand,不是空数组。
常见问题
允许名单里补上同一条命令,能不能代替拒绝名单?
不能。2.1.305 之后,这台服务器跳过允许名单。允许名单里有没有这条命令,都不再决定它加载不加载。要停住,必须有一条命中的拒绝名单。拒绝名单命中之后,允许名单不能把它放回来。
用 serverName 写成 notes,够不够?
不够。文档写明,serverName 是用户起的标签,通配符也不展开。用户可以把任意服务器叫成同一个名字。要停住这台 stdio 服务器,用 serverCommand,并且和 command、args 完全一致。
命令里也有 ${VAR},这次还会加载吗?
更新说明没有把这种情况算进放行。它只点名 stdio、而且变量只在 env。命令、参数、URL 或 headers 里还有 ${VAR} 时,仍按组织级文档里的允许名单检查来对待。那种服务器如果允许名单对不上,不要假设 2.1.305 会让它加载。
用户自己的设置能不能挡住组织下发的这一台?
可以。文档写明,deniedMcpServers 对 managed-mcp.json 里的服务器也生效,用户自己的拒绝名单会并进来。所以用户可以只为自己挡住一台。允许名单不能代替这一步。
env 里要写下 token 的值吗?
不要。文档写明,这台机器上的任何人都能读 managed-mcp.json,所以不要把 API key 或其他凭证写进 env。每人自己的凭证用 ${VAR},从该用户的环境读取。拒绝名单里也不要出现 token,只保留命令和参数。
总结
2.1.305 只放行一种:managed-mcp.json 里的 stdio 服务器,${VAR} 只在 env。允许名单不再把它滤掉。命令、参数、URL 或 headers 里的变量,更新说明没有一起放行。
直接结论:要继续停住,就在 deniedMcpServers 里写与 command 和 args 完全相同的 serverCommand;空的允许名单留不住它;serverName 不够;env 里不写 token 的值。
下一步:用 Schema 卡住拒绝名单的数组,再用 Diff 看出空数组和那条 serverCommand 的差别。