博客 • 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 要和命令完全一致。

直接结论:只在 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 说的那一种。命令路径是占位符,不是要安装的包。

变量只在 env。2.1.305 之后,允许名单不再把这台 stdio 服务器滤掉。
{
  "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 不是安全控制,因为用户可以给任何服务器起同一个名字。

把这台服务器停住的拒绝名单。数组和上面的 command、args 相同。
{
  "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. 1
    选对操作

    打开 JSON Schema。操作选「用 Schema 校验」。第一栏贴拒绝名单,第二栏贴上面的 Schema。serverCommand 必须是字符串数组。

  2. 2
    撞四条坏数据

    serverCommand 写成一整段字符串,或删掉 deniedMcpServers,或把参数写成数字,或把 deniedMcpServers 写成对象。这四条都会失败。上面那份拒绝名单会通过。通过只说明形状对。

  3. 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 的差别。

返回博客