博客 • Claude Code / MCP

升级到 Claude Code 2.1.318 之后,managed settings 里 deniedMcpServers 写了 URL 规则时,Anthropic 托管 MCP 与 cloud connector 要在 JSON 日志里怎样记下停点?

2026 年 11 月 4 日,Anthropic 发布 Claude Code 2.1.318。managed settings 里的 deniedMcpServers 若写了 serverUrl,Anthropic 托管 MCP 也会按 upstream URL 做通配匹配并拒绝;cloud 会话与 self-hosted runner 上的 connector 先比对 connector 自身 URL,对不上再按 serverName 规则拒绝。

会话 server 元数据 JSON 应写出 serverKind、upstreamUrl、connectorUrl、serverName 与 sessionSurface;被拒绝的 MCP tool 调用应在审计 JSON 写 decision denied、reason mcp_server_denied,并保留 matchedRule、matchedPattern 与 upstreamUrl。运维可用 mcp_url_deny_gate 对照 schema、deny_by_url、deny_by_connector_url、deny_by_name、allow,而不是与 serverCommand env-only 规则混报。

serverCommand 拒绝写法见 MCP env allowlist 与 serverCommand;MCP 安全基线见 MCP 安全;非交互 elicitation 超时停点见 MCP elicitation timeout JSON。本篇只写 2.1.318 的 deniedMcpServers URL 扩展、managed settings JSON、会话 server 元数据、MCP tool 调用样本、mcp_server_denied 审计字段,以及 mcp_url_deny_gate 五条分支。

先记住这一点:2.1.318 扩展的是 deniedMcpServers URL 对托管 MCP 与 cloud connector 的匹配与 mcp_server_denied 审计字段,不是重写 serverCommand env-only 拒绝表。下面按 changelog、managed settings JSON、会话元数据、MCP 调用样本、审计日志对照表、mcp_url_deny_gate 源码,以及 JSONNote 校验步骤。

2.1.318 怎样扩展 deniedMcpServers URL 到托管 MCP 与 cloud connector?

变更说明见 Claude Code changelog,标签页见 v2.1.318 release。相对更早只拦用户自配 MCP 的版本,2.1.318 让 serverUrl 规则同样作用于 Anthropic 托管 MCP 的 upstreamUrl;cloud 会话与 self-hosted runner 上的 connector 会先拿 connectorUrl 对规则,否则再按 serverName 拒绝。

拒绝发生在 MCP tool 调用之前。审计 JSON 应保留 decision denied、reason mcp_server_denied、matchedRule、matchedPattern 与 upstreamUrl,方便 SIEM 按 URL 规则归因,而不是只记一条泛化的 connector 失败。

managed settings 里 deniedMcpServers 怎样写 serverUrl 与 serverName?

在组织 managed settings 的 deniedMcpServers 数组里,一条可写 serverUrl 通配(例如 https://mcp.example.com/v1/*),另一条可写 serverName(例如 slack-connector)。2.1.318 起 serverUrl 会同时匹配 Anthropic 托管 MCP 的 upstreamUrl 与 cloud connector 的 connectorUrl(connector 场景先比 connectorUrl)。

managed settings:serverUrl 通配与 serverName 各一条。
{
  "deniedMcpServers": [
    {
      "serverUrl": "https://mcp.example.com/v1/*"
    },
    {
      "serverName": "slack-connector"
    }
  ]
}

若把 serverUrl 写成非字符串或 deniedMcpServers 不是数组,本地 schema 校验应先失败并返回 schema,再进入 mcp_url_deny_gate 的 deny 分支。不要把形状错误当成 allow。

会话 server 元数据 JSON 里 upstreamUrl 与 sessionSurface 怎样写?

客户端在解析 MCP 服务器时写入元数据 JSON:serverKind 区分 anthropic_hosted 与用户配置;upstreamUrl 是托管 MCP 的上游地址;connectorUrl 是 cloud connector 对外 URL;serverName 是注册名;sessionSurface 标记 cloud、local 或 self_hosted_runner。

会话元数据:serverKind、upstreamUrl、connectorUrl、serverName、sessionSurface。
{
  "serverKind": "anthropic_hosted",
  "upstreamUrl": "https://mcp.example.com/v1/tools",
  "connectorUrl": "https://connect.example.com/slack",
  "serverName": "analytics-mcp",
  "sessionSurface": "cloud"
}

upstreamUrl 与 connectorUrl 必须是非空字符串;sessionSurface 必须在 enum 内;缺 serverKind 或多余字段时 mcp_url_deny_gate 先返回 schema。元数据合法且规则未命中时才返回 allow。

会话元数据 JSON Schema:先过形状再进 mcp_url_deny_gate。
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "additionalProperties": false,
  "required": ["serverKind", "upstreamUrl", "connectorUrl", "serverName", "sessionSurface"],
  "properties": {
    "serverKind": { "type": "string", "enum": ["anthropic_hosted", "user_configured", "connector"] },
    "upstreamUrl": { "type": "string", "minLength": 1 },
    "connectorUrl": { "type": "string", "minLength": 1 },
    "serverName": { "type": "string", "minLength": 1 },
    "sessionSurface": { "type": "string", "enum": ["cloud", "local", "self_hosted_runner"] }
  }
}

MCP tool 调用 JSON 与未命中规则的 server 元数据怎样对照?

样本:upstreamUrl 命中 serverUrl 规则时,tool 调用 JSON 含 mcp__analytics-mcp__search 与 arguments;客户端不执行该 MCP tool,审计 JSON 写 decision denied 与 reason mcp_server_denied。对照样本 CALL_OUTSIDE 的 upstreamUrl 不在规则内,mcp_url_deny_gate 应返回 allow。

规则命中:MCP tool 调用被 deny。
{
  "tool": "mcp__analytics-mcp__search",
  "arguments": {
    "query": "quarterly revenue"
  }
}
规则未命中:server 元数据允许 allow。
{
  "serverKind": "anthropic_hosted",
  "upstreamUrl": "https://other.example.com/v1/tools",
  "connectorUrl": "https://connect.example.com/other",
  "serverName": "other-mcp",
  "sessionSurface": "cloud"
}

审计日志里 decision denied 与 mcp_server_denied 应怎样写?

当 upstreamUrl 或 connectorUrl 命中 deniedMcpServers 规则时,MCP tool 审计 JSON 应写 decision denied、reason mcp_server_denied,并保留 matchedRule、matchedPattern 与 upstreamUrl;connector 场景可同时保留 connectorUrl 便于 cloud 归因。不要把 serverCommand env-only deny 与 URL deny 混在同一 reason 枚举里。

审计日志:decision denied、reason mcp_server_denied、matchedRule、matchedPattern、upstreamUrl。
{
  "decision": "denied",
  "reason": "mcp_server_denied",
  "matchedRule": "serverUrl",
  "matchedPattern": "https://mcp.example.com/v1/*",
  "upstreamUrl": "https://mcp.example.com/v1/tools"
}
会话或 URL 信号mcp_url_deny_gate 返回运维动作
upstreamUrl 命中 serverUrl 通配规则deny_by_url对齐 decision denied、matchedRule serverUrl、matchedPattern 与 upstreamUrl。
connectorUrl 命中 serverUrl 规则且 upstream 未命中deny_by_connector_url日志写 matchedRule serverUrl 并保留 connectorUrl 便于 cloud 归因。
serverName 命中 deniedMcpServers 名称规则deny_by_name写 reason mcp_server_denied 与 matchedRule serverName。
元数据 JSON 形状错误或 deny 列表缺失schema先修 managed settings 或 ctx,再跑 deny 分支。

mcp_url_deny_gate 怎样在 schema 与五条 deny 分支间选择?

实现顺序:先用 CTX_SCHEMA 校验 serverKind、upstreamUrl、connectorUrl、serverName、sessionSurface;再遍历 deniedMcpServers,upstreamUrl 命中 serverUrl 返回 deny_by_url,connectorUrl 命中 serverUrl 返回 deny_by_connector_url,serverName 命中名称规则返回 deny_by_name;都不命中返回 allow。

不要把 deny_by_url 与 deny_by_connector_url 合成单一 mcp_denied 位。cloud connector 报表应单独计数 deny_by_connector_url,托管 MCP 用 deny_by_url,名称兜底用 deny_by_name。

mcp_url_deny_gate:schema、deny_by_url、deny_by_connector_url、deny_by_name、allow。
def actual_type(data: object) -> str:
    if data is None:
        return "null"
    if isinstance(data, bool):
        return "boolean"
    if isinstance(data, list):
        return "array"
    if isinstance(data, int):
        return "integer"
    if isinstance(data, float):
        return "number"
    if isinstance(data, str):
        return "string"
    if isinstance(data, dict):
        return "object"
    raise TypeError(type(data))

def validate(data: object, schema: dict) -> list[str]:
    errors: list[str] = []
    if not isinstance(schema, dict):
        return errors
    types = schema.get("type")
    if isinstance(types, str):
        types = [types]
    actual = actual_type(data)
    if types and not any(t == actual or (t == "number" and actual == "integer") for t in types):
        errors.append("type")
    if "enum" in schema and not any(value == data for value in schema["enum"]):
        errors.append("enum")
    if isinstance(data, str) and "minLength" in schema and len(data) < schema["minLength"]:
        errors.append("minLength")
    if isinstance(data, dict) and schema.get("properties"):
        for key in schema.get("required") or []:
            if key not in data:
                errors.append(f"required:{key}")
        if schema.get("additionalProperties") is False:
            for key in data:
                if key not in schema["properties"]:
                    errors.append(f"additional:{key}")
        for key, sub in schema["properties"].items():
            if key in data and data[key] is not None:
                errors.extend(validate(data[key], sub))
    return errors

def url_match(url: str, pattern: str) -> bool:
    return fnmatch(url, pattern)

def mcp_url_deny_gate(ctx: dict, deny_doc: dict, schema: dict) -> str:
    if validate(ctx, schema):
        return "schema"
    entries = deny_doc.get("deniedMcpServers")
    if not isinstance(entries, list):
        return "schema"
    upstream = ctx.get("upstreamUrl", "")
    connector = ctx.get("connectorUrl", "")
    name = ctx.get("serverName", "")
    for entry in entries:
        if not isinstance(entry, dict):
            continue
        pat = entry.get("serverUrl")
        if isinstance(pat, str):
            if upstream and url_match(upstream, pat):
                return "deny_by_url"
            if connector and url_match(connector, pat):
                return "deny_by_connector_url"
        rule_name = entry.get("serverName")
        if isinstance(rule_name, str) and rule_name and rule_name == name:
            return "deny_by_name"
    return "allow"

在 JSONNote 里怎样校验 managed settings、会话元数据与审计日志?

  1. 1
    用 Schema 固定 deny 列表形状

    打开 JSON Schema。第一栏贴 deniedMcpServers schema,第二栏贴 serverUrl 与 serverName 样本。required 或类型错误应先标红,mcp_url_deny_gate 不应在形状错误时返回 deny_by_url 或 allow。

  2. 2
    用格式化核对 MCP tool 调用

    打开 JSON 格式化,贴 MCP tool 调用 JSON。确认 tool 与 arguments 键存在,再对照 mcp_url_deny_gate 是否应走 deny_by_url 或 allow。

  3. 3
    用 Diff 对比 allow 与 denied 审计日志

    打开 JSON Diff。左边放 CALL_OUTSIDE 对应的 allow 样本,右边放 decision denied 与 reason mcp_server_denied 样本。Diff 高亮的 matchedPattern 就是 SIEM 规则最该锁定的一行。

常见问题

serverUrl 规则与旧版只拦自配 MCP 有何不同?

旧版 deniedMcpServers 的 serverUrl 主要匹配用户在 settings 里自配的 HTTP MCP。2.1.318 起同样规则也会匹配 Anthropic 托管 MCP 的 upstreamUrl,并在 cloud 与 self-hosted runner 上先对 connectorUrl 再对 serverName。

deny_by_url 与 deny_by_connector_url 在日志里怎样区分?

deny_by_url 时 matchedRule 多为 serverUrl,upstreamUrl 与 matchedPattern 对齐 settings 里的通配;deny_by_connector_url 时 upstream 可能未命中,但 connectorUrl 命中同一 serverUrl 规则,日志应保留 connectorUrl 与 sessionSurface cloud 或 self_hosted_runner。

serverName 规则与 serverUrl 规则谁先匹配?

mcp_url_deny_gate 先遍历 serverUrl:upstreamUrl 命中返回 deny_by_url,否则 connectorUrl 命中返回 deny_by_connector_url,最后才用 serverName 条目返回 deny_by_name。不要把 serverName 写成 URL 通配。

URL deny 与 serverCommand env-only deny 是什么关系?

serverCommand 规则匹配 stdio 启动命令与环境变量占位,不替代 serverUrl 对托管 MCP upstream 的拦截。两套 JSON 分开保存,mcp_url_deny_gate 只镜像 URL 与 serverName 规则,不解析 serverCommand 数组。

本篇是否提供攻击或绕过教程?

不是。本文只解释 2.1.318 deniedMcpServers URL 扩展的 JSON 字段形状、mcp_server_denied 审计写法与 mcp_url_deny_gate 五条分支,帮助运维与合规做校验和 SIEM 归因。不提供如何利用 URL 或 connector 配置绕过管控的步骤。

总结

Claude Code 2.1.318 扩展 deniedMcpServers URL 对托管 MCP upstream 与 cloud connector 的匹配,并在审计 JSON 写 decision denied、reason mcp_server_denied、matchedRule、matchedPattern 与 upstreamUrl。运维应用 managed settings、会话元数据、MCP 调用与 denied 审计四套样本驱动 mcp_url_deny_gate,而不是只盯 serverCommand env-only 列表。

直接结论:元数据 JSON 不过 schema 返回 schema;upstreamUrl 命中 serverUrl 返回 deny_by_url;connectorUrl 命中 serverUrl 返回 deny_by_connector_url;serverName 命中名称规则返回 deny_by_name;都不命中返回 allow;deny 审计对齐 decision denied、reason mcp_server_denied、matchedRule、matchedPattern 与 upstreamUrl。

下一步:用 Schema 固定 deniedMcpServers 与元数据,用格式化看清 MCP 调用,用 Diff 对比 allow 与 denied 审计日志,并在报表里分开统计 deny_by_url、deny_by_connector_url、deny_by_name 与 allow。

返回博客