博客 • 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 五條分支。
- managed settings 的 deniedMcpServers 可同時寫 serverUrl 通配與 serverName;2.1.318 起 serverUrl 也會匹配 Anthropic 託管 MCP 的 upstreamUrl,不要只當成用戶自配 HTTP MCP。
- 會話元數據 JSON 必須帶 serverKind、upstreamUrl、connectorUrl、serverName、sessionSurface;缺字段或 enum 錯誤時 mcp_url_deny_gate 先返回 schema,再進入 URL 或名稱匹配。
- 審計日誌在 deny 分支應寫 decision denied、reason mcp_server_denied,並保留 matchedRule、matchedPattern 與 upstreamUrl;SIEM 應按 matchedRule 聚合而不是混用 serverCommand deny。
- cloud 與 self-hosted runner 上 connector 先按 connectorUrl 對 serverUrl 規則,對不上再用 serverName 規則走 deny_by_name;無任何規則命中時 mcp_url_deny_gate 返回 allow。
先記住這一點: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)。
{
"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": "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。
{
"$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。
{
"tool": "mcp__analytics-mcp__search",
"arguments": {
"query": "quarterly revenue"
}
}
{
"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": "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。
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用 Schema 固定 deny 列表形狀
打開 JSON Schema。第一欄貼 deniedMcpServers schema,第二欄貼 serverUrl 與 serverName 樣本。required 或類型錯誤應先標紅,mcp_url_deny_gate 不應在形狀錯誤時返回 deny_by_url 或 allow。
- 2用格式化覈對 MCP tool 調用
打開 JSON 格式化,貼 MCP tool 調用 JSON。確認 tool 與 arguments 鍵存在,再對照 mcp_url_deny_gate 是否應走 deny_by_url 或 allow。
- 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。