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