博客 • 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。

返回博客