Blog • Claude Code / MCP

Après Claude Code 2.1.318 avec deniedMcpServers URL dans managed settings : logs JSON pour MCP hébergé Anthropic et connectors cloud ?

Le 4 novembre 2026, Anthropic a livré Claude Code 2.1.318. serverUrl dans deniedMcpServers refuse aussi le MCP hébergé Anthropic via upstreamUrl ; les connectors cloud et self-hosted runner comparent connectorUrl d abord, sinon serverName.

Métadonnées serveur session : serverKind, upstreamUrl, connectorUrl, serverName, sessionSurface. Appels tool MCP refusés : decision denied, reason mcp_server_denied, matchedRule, matchedPattern, upstreamUrl. mcp_url_deny_gate reflète schema, deny_by_url, deny_by_connector_url, deny_by_name, allow sans mélanger serverCommand env-only.

Le deny serverCommand est dans allowlist env MCP et serverCommand; la baseline sécurité MCP dans sécurité MCP; les arrêts timeout elicitation non interactifs dans JSON timeout elicitation MCP. Cet article ne couvre que l extension deniedMcpServers URL 2.1.318, JSON settings gérés, métadonnées serveur de session, appel tool MCP, champs audit mcp_server_denied et cinq branches mcp_url_deny_gate.

Gardez ceci : 2.1.318 étend le matching deniedMcpServers URL pour MCP hébergé et connectors cloud plus champs mcp_server_denied ; ne réécrit pas la table deny serverCommand env-only. Ensuite : changelog, JSON gérés, métadonnées, appel MCP, table audit, mcp_url_deny_gate, JSONNote.

Comment 2.1.318 étend deniedMcpServers URL au MCP hébergé et aux connectors cloud

Les notes de version sont dans changelog Claude Code, page tag version v2.1.318. Avant surtout MCP configuré par l utilisateur ; 2.1.318 applique serverUrl à upstreamUrl du MCP hébergé Anthropic ; connectors cloud et self-hosted runner comparent connectorUrl d abord, sinon serverName.

Refus avant l appel tool MCP. Audit : decision denied, reason mcp_server_denied, matchedRule, matchedPattern, upstreamUrl pour attribution SIEM à la règle URL.

Comment écrire serverUrl et serverName dans deniedMcpServers géré

Dans settings gérés, deniedMcpServers accepte globs serverUrl et serverName. Dès 2.1.318 serverUrl correspond à upstreamUrl MCP hébergé Anthropic et connectorUrl cloud.

Settings gérés : un glob serverUrl et un serverName.
{
  "deniedMcpServers": [
    {
      "serverUrl": "https://mcp.example.com/v1/*"
    },
    {
      "serverName": "slack-connector"
    }
  ]
}

serverUrl non string ou deniedMcpServers pas tableau → schema d abord, pas allow si forme mauvaise.

Comment upstreamUrl et sessionSurface apparaissent dans le JSON métadonnées serveur

Lors de la résolution MCP, le client écrit métadonnées : serverKind, upstreamUrl, connectorUrl, serverName, sessionSurface.

Métadonnées session : 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 et connectorUrl non vides ; sessionSurface dans enum ; serverKind manquant → schema ; pas de règle → allow.

JSON Schema métadonnées session : forme avant 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"] }
  }
}

Comment le JSON appel tool MCP se compare aux métadonnées sans règle

Si upstreamUrl correspond à serverUrl, JSON tool mcp__analytics-mcp__search ; pas d exécution, decision denied. CALL_OUTSIDE → allow.

Règle touchée : appel tool MCP refusé.
{
  "tool": "mcp__analytics-mcp__search",
  "arguments": {
    "query": "quarterly revenue"
  }
}
Pas de règle : métadonnées → allow.
{
  "serverKind": "anthropic_hosted",
  "upstreamUrl": "https://other.example.com/v1/tools",
  "connectorUrl": "https://connect.example.com/other",
  "serverName": "other-mcp",
  "sessionSurface": "cloud"
}

Comment decision denied et mcp_server_denied apparaissent dans les logs d audit

Si upstreamUrl ou connectorUrl correspond : decision denied, reason mcp_server_denied, matchedRule, matchedPattern, upstreamUrl ; connectorUrl pour cloud. Ne pas mélanger deny serverCommand env-only et URL dans une enum reason.

Log d audit : 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"
}
Signal session ou URLmcp_url_deny_gate renvoieAction opérateur
upstreamUrl correspond à une règle glob serverUrldeny_by_urlAligner sur decision denied, matchedRule serverUrl, matchedPattern, upstreamUrl.
connectorUrl correspond à serverUrl, pas upstreamdeny_by_connector_urlLogger matchedRule serverUrl et garder connectorUrl pour attribution cloud.
serverName correspond à une règle de nom deniedMcpServersdeny_by_nameÉcrire reason mcp_server_denied et matchedRule serverName.
Mauvaise forme JSON métadonnées ou liste deny absenteschemaCorriger settings gérés ou ctx avant branches deny.

Comment mcp_url_deny_gate choisit entre schema et cinq branches deny

Ordre : CTX_SCHEMA pour serverKind, upstreamUrl, connectorUrl, serverName, sessionSurface ; parcourir deniedMcpServers ; upstreamUrl correspond à serverUrl → deny_by_url ; connectorUrl correspond → deny_by_connector_url ; serverName correspond → deny_by_name ; sinon allow.

Ne fusionnez pas deny_by_url et deny_by_connector_url en un mcp_denied. deny_by_connector_url pour connectors cloud, deny_by_url pour MCP hébergé, deny_by_name pour noms.

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"

Comment valider settings gérés, métadonnées session et logs d audit dans JSONNote

  1. 1
    Fixer la forme deny avec Schema

    Ouvrez JSON Schema. Schema deniedMcpServers au premier champ, serverUrl et serverName au second. Erreurs required ou type d abord ; pas deny_by_url ou allow si forme mauvaise.

  2. 2
    Format pour les appels tool MCP

    Ouvrez JSON Format, collez JSON appel tool MCP. Vérifiez tool et arguments avant deny_by_url ou allow pour mcp_url_deny_gate.

  3. 3
    Diff logs allow et denied

    Ouvrez JSON Diff. À gauche échantillon allow CALL_OUTSIDE, à droite decision denied et reason mcp_server_denied. matchedPattern surligné est la clé SIEM.

Questions fréquentes

En quoi les règles serverUrl diffèrent du refus MCP configuré seul ?

Avant serverUrl visait surtout le MCP HTTP configuré. Dès 2.1.318 aussi upstreamUrl MCP hébergé Anthropic et connectorUrl puis serverName.

Comment les logs distinguent deny_by_url de deny_by_connector_url ?

deny_by_url : matchedRule serverUrl et upstreamUrl. deny_by_connector_url : connectorUrl correspond, upstream peut manquer, garder connectorUrl et sessionSurface.

Quelle règle passe en premier, serverName ou serverUrl ?

mcp_url_deny_gate parcourt serverUrl d abord : upstreamUrl→deny_by_url, connectorUrl→deny_by_connector_url, puis serverName→deny_by_name. Pas de glob URL dans serverName.

Quel lien entre deny URL et deny serverCommand env-only ?

serverCommand pour stdio et env ne remplace pas le deny upstream serverUrl. JSON séparés ; mcp_url_deny_gate reflète URL et serverName seulement.

Cet article est-il un tutoriel d attaque ou de contournement ?

Non. L article explique seulement les formes JSON de l extension deniedMcpServers URL 2.1.318, champs mcp_server_denied et cinq branches mcp_url_deny_gate. Pas d étapes de contournement via URL ou connector.

Conclusion

Claude Code 2.1.318 étend le matching deniedMcpServers URL et écrit decision denied, reason mcp_server_denied, matchedRule, matchedPattern, upstreamUrl dans l audit. Pilotez mcp_url_deny_gate avec settings gérés, métadonnées, appels MCP et audit denied.

Décision : JSON métadonnées échoue schema → schema ; upstreamUrl correspond serverUrl → deny_by_url ; connectorUrl correspond serverUrl → deny_by_connector_url ; serverName → deny_by_name ; sinon allow ; audit deny aligné sur decision denied, reason mcp_server_denied, matchedRule, matchedPattern, upstreamUrl.

Ensuite : Schema pour deniedMcpServers et métadonnées, Format pour MCP, Diff logs allow et denied, compter séparément deny_by_url, deny_by_connector_url, deny_by_name et allow.

Retour au blog