Blog • Claude Code / MCP

After upgrading to Claude Code 2.1.318, when managed settings deniedMcpServers includes URL rules, how should JSON logs record stops for Anthropic-hosted MCP and cloud connectors?

On November 4, 2026 Anthropic shipped Claude Code 2.1.318. When managed settings deniedMcpServers lists serverUrl patterns, Anthropic-hosted MCP is also denied by upstream URL glob match; connectors in cloud sessions and self-hosted runners match the connector URL first, otherwise deny by serverName rules.

Session server metadata JSON should record serverKind, upstreamUrl, connectorUrl, serverName, and sessionSurface; denied MCP tool calls should log decision denied, reason mcp_server_denied, plus matchedRule, matchedPattern, and upstreamUrl. Mirror mcp_url_deny_gate branches schema, deny_by_url, deny_by_connector_url, deny_by_name, and allow instead of mixing serverCommand env-only rules.

serverCommand deny patterns are in MCP env allowlist and serverCommand; MCP security baseline is in MCP security; non-interactive elicitation timeout stops are in MCP elicitation timeout JSON. This article covers only the 2.1.318 deniedMcpServers URL extension, managed settings JSON, session server metadata, an MCP tool call sample, mcp_server_denied audit fields, and the five mcp_url_deny_gate branches.

Keep this in mind: 2.1.318 extends deniedMcpServers URL matching for hosted MCP and cloud connectors plus mcp_server_denied audit fields; it does not rewrite serverCommand env-only deny tables. Below: changelog, managed settings JSON, session metadata, MCP call sample, audit log table, mcp_url_deny_gate source, and JSONNote validation steps.

How does 2.1.318 extend deniedMcpServers URL to hosted MCP and cloud connectors?

Release notes are in the Claude Code changelog, tag page at v2.1.318 release. Versus earlier builds that mainly blocked user-configured MCP, 2.1.318 applies serverUrl rules to upstreamUrl on Anthropic-hosted MCP; connectors in cloud sessions and self-hosted runners compare connectorUrl to rules first, otherwise deny by serverName.

Denial happens before the MCP tool call runs. Audit JSON should keep decision denied, reason mcp_server_denied, matchedRule, matchedPattern, and upstreamUrl so SIEM attributes to the URL rule instead of a generic connector failure.

How do you write serverUrl and serverName in managed settings deniedMcpServers?

In organizational managed settings, deniedMcpServers entries may use a serverUrl glob such as https://mcp.example.com/v1/* or a serverName such as slack-connector. From 2.1.318 serverUrl matches upstreamUrl on Anthropic-hosted MCP and connectorUrl on cloud connectors (connector flows compare connectorUrl first).

Managed settings: one serverUrl glob and one serverName entry.
{
  "deniedMcpServers": [
    {
      "serverUrl": "https://mcp.example.com/v1/*"
    },
    {
      "serverName": "slack-connector"
    }
  ]
}

If serverUrl is not a string or deniedMcpServers is not an array, local schema validation should fail with schema before mcp_url_deny_gate deny branches. Do not treat shape errors as allow.

How do upstreamUrl and sessionSurface appear in session server metadata JSON?

While resolving MCP servers the client writes metadata JSON: serverKind separates anthropic_hosted from user_configured; upstreamUrl is the hosted MCP upstream; connectorUrl is the cloud connector URL; serverName is the registry name; sessionSurface marks cloud, local, or self_hosted_runner.

Session metadata: 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 and connectorUrl must be non-empty strings; sessionSurface must stay inside the enum; missing serverKind or extra fields make mcp_url_deny_gate return schema first. Valid metadata with no rule hit returns allow.

Session metadata JSON Schema: shape before 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"] }
  }
}

How should MCP tool call JSON compare to server metadata that misses rules?

Sample: when upstreamUrl matches a serverUrl rule, the tool call JSON includes mcp__analytics-mcp__search and arguments; the client skips the MCP tool and audit JSON records decision denied and reason mcp_server_denied. Contrast CALL_OUTSIDE where upstreamUrl misses rules and mcp_url_deny_gate should return allow.

Rule hit: MCP tool call denied.
{
  "tool": "mcp__analytics-mcp__search",
  "arguments": {
    "query": "quarterly revenue"
  }
}
No rule hit: server metadata yields allow.
{
  "serverKind": "anthropic_hosted",
  "upstreamUrl": "https://other.example.com/v1/tools",
  "connectorUrl": "https://connect.example.com/other",
  "serverName": "other-mcp",
  "sessionSurface": "cloud"
}

How should decision denied and mcp_server_denied appear in audit logs?

When upstreamUrl or connectorUrl matches a deniedMcpServers rule, MCP tool audit JSON should set decision denied, reason mcp_server_denied, and keep matchedRule, matchedPattern, and upstreamUrl; connector flows may also keep connectorUrl for cloud attribution. Do not mix serverCommand env-only deny with URL deny in one 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"
}
Session or URL signalmcp_url_deny_gate returnsOperator action
upstreamUrl matches a serverUrl glob ruledeny_by_urlAlign audit with decision denied, matchedRule serverUrl, matchedPattern, and upstreamUrl.
connectorUrl matches serverUrl while upstream does notdeny_by_connector_urlLog matchedRule serverUrl and keep connectorUrl for cloud attribution.
serverName matches a deniedMcpServers name ruledeny_by_nameWrite reason mcp_server_denied and matchedRule serverName.
Bad metadata JSON shape or missing deny listschemaFix managed settings or ctx before deny branches.

How does mcp_url_deny_gate choose among schema and five deny branches?

Order: validate serverKind, upstreamUrl, connectorUrl, serverName, and sessionSurface with CTX_SCHEMA; walk deniedMcpServers; upstreamUrl matching serverUrl returns deny_by_url; connectorUrl matching serverUrl returns deny_by_connector_url; serverName matching a name rule returns deny_by_name; otherwise allow.

Do not collapse deny_by_url and deny_by_connector_url into one mcp_denied bit. Count deny_by_connector_url separately for cloud connectors, deny_by_url for hosted MCP, deny_by_name for name fallbacks.

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"

How do you validate managed settings, session metadata, and audit logs in JSONNote?

  1. 1
    Pin deny list shape with Schema

    Open JSON Schema. Paste deniedMcpServers schema in the first field and serverUrl plus serverName samples in the second. required or type errors should flag first; mcp_url_deny_gate must not return deny_by_url or allow on a bad shape.

  2. 2
    Use Format to inspect MCP tool calls

    Open JSON Format, paste MCP tool call JSON. Confirm tool and arguments keys exist before checking whether mcp_url_deny_gate should return deny_by_url or allow.

  3. 3
    Diff allow versus denied audit logs

    Open JSON Diff. Put the CALL_OUTSIDE allow sample on the left and a decision denied plus reason mcp_server_denied sample on the right. The highlighted matchedPattern is the line your SIEM rule should key on.

Common questions

How do serverUrl rules differ from legacy user-configured MCP only?

Legacy deniedMcpServers serverUrl mainly matched user-configured HTTP MCP in settings. From 2.1.318 the same rules also match upstreamUrl on Anthropic-hosted MCP and compare connectorUrl then serverName on cloud and self-hosted runners.

How do logs distinguish deny_by_url from deny_by_connector_url?

For deny_by_url matchedRule is usually serverUrl with upstreamUrl aligned to matchedPattern; for deny_by_connector_url upstream may miss while connectorUrl hits the same serverUrl rule, and logs should keep connectorUrl plus sessionSurface cloud or self_hosted_runner.

Which runs first, serverName or serverUrl rules?

mcp_url_deny_gate walks serverUrl first: upstreamUrl hit returns deny_by_url, else connectorUrl hit returns deny_by_connector_url, then serverName entries return deny_by_name. Do not store URL globs in serverName.

How does URL deny relate to serverCommand env-only deny?

serverCommand rules match stdio launch commands and env placeholders; they do not replace serverUrl blocking of hosted MCP upstream. Keep both JSON sources separate; mcp_url_deny_gate mirrors URL and serverName rules only, it does not parse serverCommand arrays.

Does this article provide attack or bypass tutorials?

No. This article only explains 2.1.318 deniedMcpServers URL extension JSON shapes, mcp_server_denied audit fields, and the five mcp_url_deny_gate branches for operator and compliance validation and SIEM attribution. It does not provide steps to bypass controls via URL or connector configuration.

Summary

Claude Code 2.1.318 extends deniedMcpServers URL matching for hosted MCP upstream and cloud connectors and writes decision denied, reason mcp_server_denied, matchedRule, matchedPattern, and upstreamUrl in audit JSON. Drive mcp_url_deny_gate with managed settings, session metadata, MCP calls, and denied audit samples instead of watching serverCommand env-only lists alone.

Decision: metadata JSON failing schema → schema; upstreamUrl matches serverUrl → deny_by_url; connectorUrl matches serverUrl → deny_by_connector_url; serverName matches name rule → deny_by_name; no match → allow; deny audits align with decision denied, reason mcp_server_denied, matchedRule, matchedPattern, and upstreamUrl.

Next: pin deniedMcpServers and metadata with Schema, use Format to inspect MCP calls, Diff allow versus denied audit logs, and report deny_by_url, deny_by_connector_url, deny_by_name, and allow separately.

Back to the blog