Блог • 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.

Сначала это: 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.

Managed settings: один glob serverUrl и serverName.
{
  "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, 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.

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"] }
  }
}

Как JSON вызова MCP tool сопоставляется с метаданными без правила

При совпадении upstreamUrl с serverUrl JSON tool mcp__analytics-mcp__search; без выполнения, decision denied. CALL_OUTSIDE → allow.

Правило сработало: вызов MCP tool denied.
{
  "tool": "mcp__analytics-mcp__search",
  "arguments": {
    "query": "quarterly revenue"
  }
}
Нет правила: метаданные → 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 в audit logs

При совпадении upstreamUrl или connectorUrl: decision denied, reason mcp_server_denied, matchedRule, matchedPattern, upstreamUrl; connectorUrl для cloud. Не смешивать serverCommand env-only и URL deny в одной reason enum.

Audit log: 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"
}
Сигнал сессии или URLmcp_url_deny_gate возвращаетДействие оператора
upstreamUrl совпадает с glob serverUrldeny_by_urlСогласовать с decision denied, matchedRule serverUrl, matchedPattern, upstreamUrl.
connectorUrl совпадает с serverUrl, upstream нетdeny_by_connector_urlЛог matchedRule serverUrl и connectorUrl для cloud.
serverName совпадает с правилом имени deniedMcpServersdeny_by_nameПисать reason mcp_server_denied и matchedRule serverName.
Плохая форма JSON метаданных или нет denyschemaИсправить 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 для имён.

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"

Как проверять managed settings, метаданные сессии и audit logs в JSONNote

  1. 1
    Зафиксировать форму deny через Schema

    Откройте JSON Schema. Schema deniedMcpServers в первое поле, serverUrl и serverName во второе. Ошибки required или type сначала; при плохой форме не deny_by_url или allow.

  2. 2
    Format для вызовов MCP tool

    Откройте JSON Формат, вставьте JSON вызова MCP tool. Проверьте tool и arguments, затем deny_by_url или allow у mcp_url_deny_gate.

  3. 3
    Diff 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.

Назад в блог