Блог • Claude Code / MCP
После Claude Code 2.1.318 с URL в deniedMcpServers: как писать JSON-остановки для Anthropic-hosted MCP и cloud connector?
4 ноября 2026 Anthropic выпустил Claude Code 2.1.318. serverUrl в deniedMcpServers блокирует Anthropic-hosted MCP по upstreamUrl; connector в cloud и self-hosted runner сначала сверяют connectorUrl, иначе serverName.
Метаданные server сессии: serverKind, upstreamUrl, connectorUrl, serverName, sessionSurface. Отказ MCP tool: 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.
Deny serverCommand — в MCP env allowlist и serverCommand; базовая линия MCP security — в безопасность MCP; остановки elicitation timeout — в JSON elicitation timeout MCP. Эта статья только о расширении deniedMcpServers URL 2.1.318, managed settings JSON, метаданных server сессии, вызове tool MCP, полях mcp_server_denied и пяти ветках mcp_url_deny_gate.
- В managed settings deniedMcpServers может содержать serverUrl и serverName; с 2.1.318 serverUrl также матчит upstreamUrl Anthropic-hosted MCP.
- JSON метаданных сессии: serverKind, upstreamUrl, connectorUrl, serverName, sessionSurface; пропуск или enum → schema до match.
- Audit logs в deny: decision denied, reason mcp_server_denied, matchedRule, matchedPattern, upstreamUrl; SIEM по matchedRule, не смешивать serverCommand deny.
- В cloud и self-hosted runner сначала connectorUrl к serverUrl, иначе serverName для deny_by_name; нет совпадений → allow.
Сначала это: 2.1.318 расширяет matching deniedMcpServers URL для hosted MCP и cloud connector и поля mcp_server_denied; не переписывает таблицу deny serverCommand env-only. Далее: changelog, managed settings JSON, метаданные, вызов MCP, таблица audit, mcp_url_deny_gate, JSONNote.
Как 2.1.318 расширяет deniedMcpServers URL на hosted MCP и cloud connector
Изменения — в changelog Claude Code, страница тега релиз v2.1.318. Раньше в основном user-configured MCP; 2.1.318 serverUrl также для upstreamUrl Anthropic-hosted MCP; connector в cloud и self-hosted runner сначала connectorUrl, иначе serverName.
Отказ до вызова MCP tool. Audit JSON: decision denied, reason mcp_server_denied, matchedRule, matchedPattern, upstreamUrl для SIEM по URL-правилу.
Как задать serverUrl и serverName в deniedMcpServers managed settings
В managed settings deniedMcpServers: serverUrl glob и serverName. С 2.1.318 serverUrl матчит upstreamUrl Anthropic-hosted MCP и connectorUrl cloud connector.
{
"deniedMcpServers": [
{
"serverUrl": "https://mcp.example.com/v1/*"
},
{
"serverName": "slack-connector"
}
]
}
serverUrl не string или deniedMcpServers не array → schema сначала, не allow при ошибке формы.
Как upstreamUrl и sessionSurface выглядят в JSON метаданных server сессии
При разборе MCP клиент пишет метаданные: 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 → 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"] }
}
}
Как JSON вызова MCP tool сопоставляется с метаданными без правила
При совпадении upstreamUrl с serverUrl JSON tool mcp__analytics-mcp__search; без выполнения, decision denied. CALL_OUTSIDE → 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 в audit logs
При совпадении upstreamUrl или connectorUrl: decision denied, reason mcp_server_denied, matchedRule, matchedPattern, upstreamUrl; connectorUrl для cloud. Не смешивать serverCommand env-only и URL deny в одной reason enum.
{
"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 совпадает с glob 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 → deny_by_connector_url; serverName → deny_by_name; иначе allow.
Не схлопывайте deny_by_url и deny_by_connector_url в один mcp_denied. deny_by_connector_url для cloud connector, deny_by_url для hosted MCP, 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"
Как проверять managed settings, метаданные сессии и audit logs в JSONNote
- 1Зафиксировать форму deny через Schema
Откройте JSON Schema. Schema deniedMcpServers в первое поле, serverUrl и serverName во второе. Ошибки required или type сначала; при плохой форме не deny_by_url или allow.
- 2Format для вызовов MCP tool
Откройте JSON Формат, вставьте JSON вызова MCP tool. Проверьте tool и arguments, затем deny_by_url или allow у mcp_url_deny_gate.
- 3Diff audit logs allow и denied
Откройте JSON Diff. Слева образец allow CALL_OUTSIDE, справа decision denied и reason mcp_server_denied. Подсвеченный matchedPattern — ключ SIEM.
Вопросы
Чем serverUrl отличается от старого deny только user-configured?
Раньше serverUrl в основном для user-configured HTTP MCP. С 2.1.318 также upstreamUrl Anthropic-hosted MCP и connectorUrl затем serverName.
Как в логах отличить deny_by_url от deny_by_connector_url?
deny_by_url: matchedRule serverUrl, upstreamUrl. deny_by_connector_url: connectorUrl совпал, upstream может не совпасть, сохраните connectorUrl и sessionSurface.
Что проверяется раньше, serverName или serverUrl?
mcp_url_deny_gate сначала serverUrl: upstreamUrl→deny_by_url, connectorUrl→deny_by_connector_url, затем serverName→deny_by_name. Не пишите URL glob в serverName.
Как URL deny связан с serverCommand env-only deny?
serverCommand для stdio и env не заменяет deny upstream по serverUrl. JSON раздельно; mcp_url_deny_gate только URL и serverName.
Есть ли в статье атака или bypass?
Нет. Статья только о формах JSON расширения deniedMcpServers URL 2.1.318, полях mcp_server_denied и пяти ветках mcp_url_deny_gate. Без шагов обхода через URL или connector.
Итог
Claude Code 2.1.318 расширяет matching deniedMcpServers URL и пишет в audit JSON decision denied, reason mcp_server_denied, matchedRule, matchedPattern, upstreamUrl. Кормите mcp_url_deny_gate образцами managed settings, метаданных, MCP и denied audit.
Решение: метаданные JSON не проходит schema → schema; upstreamUrl совпал serverUrl → deny_by_url; connectorUrl совпал serverUrl → deny_by_connector_url; serverName → deny_by_name; иначе allow; deny audit с decision denied, reason mcp_server_denied, matchedRule, matchedPattern, upstreamUrl.
Дальше: Schema для deniedMcpServers и метаданных, Format для MCP, Diff audit allow и denied, отдельно deny_by_url, deny_by_connector_url, deny_by_name и allow.