블로그 • 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도 upstreamUrl로 거부되고 cloud·self-hosted runner connector는 connectorUrl을 먼저 맞춘 뒤 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 timeout 정지는 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에도 적용된다.
- 세션 메타데이터 JSON에 serverKind, upstreamUrl, connectorUrl, serverName, sessionSurface가 필요하다. 필드 누락·enum 오류면 mcp_url_deny_gate는 schema를 먼저 반환한다.
- 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. 무일치면 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를 남겨 URL 규칙으로 SIEM 귀속한다.
managed settings deniedMcpServers에 serverUrl·serverName은 어떻게 쓰나
조직 managed settings deniedMcpServers에 serverUrl 글로브와 serverName을 쓴다. 2.1.318부터 serverUrl은 Anthropic 托管 MCP upstreamUrl과 cloud connector connectorUrl에도 적용된다.
{
"deniedMcpServers": [
{
"serverUrl": "https://mcp.example.com/v1/*"
},
{
"serverName": "slack-connector"
}
]
}
serverUrl이 문자열이 아니거나 deniedMcpServers가 배열이 아니면 schema가 먼저 실패한다. 형태 오류를 allow로 보지 않는다.
세션 server 메타데이터 JSON의 upstreamUrl·sessionSurface
MCP 서버 해석 시 메타데이터 JSON을 쓴다. serverKind로 anthropic_hosted·user_configured, 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"] }
}
}
MCP tool 호출 JSON과 규칙 미일치 server 메타데이터 대조
upstreamUrl이 serverUrl 규칙에 맞으면 tool JSON에 mcp__analytics-mcp__search·arguments. MCP tool 미실행·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 작성법
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이 serverUrl 글로브 규칙과 일치 | deny_by_url | decision denied, matchedRule serverUrl, matchedPattern, upstreamUrl와 맞춘다. |
| connectorUrl만 serverUrl 규칙 일치 | 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 하나로 접지 않는다. 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, 세션 메타데이터, 감사 로그를 어떻게 검증하나
- 1Schema로 deny 목록 형태 고정
열기 JSON Schema. 1칸에 deniedMcpServers schema, 2칸에 serverUrl·serverName 샘플. required·타입 오류를 먼저 표시하고 형태 깨지면 deny_by_url·allow 반환 금지.
- 2포맷으로 MCP tool 호출 확인
열기 JSON 포맷, MCP tool 호출 JSON을 붙인다. tool·arguments 키를 확인한 뒤 mcp_url_deny_gate가 deny_by_url·allow 중 무엇인지 본다.
- 3Diff로 allow·denied 감사 로그 비교
열기 JSON Diff. 왼쪽 CALL_OUTSIDE allow 샘플, 오른쪽 decision denied·reason mcp_server_denied 샘플. Diff로 강조된 matchedPattern이 SIEM 규칙 핵심 줄.
자주 묻는 질문
serverUrl 규칙과 구버전 자체 MCP만 거부와 차이는?
구버전 serverUrl은 주로 사용자 설정 HTTP MCP. 2.1.318부터 Anthropic 托管 MCP upstreamUrl에도 적용되고 cloud·self-hosted runner에서 connectorUrl을 먼저 본다.
deny_by_url과 deny_by_connector_url을 로그에서 어떻게 구분하나?
deny_by_url은 matchedRule serverUrl·upstreamUrl. deny_by_connector_url은 upstream 미일치여도 connectorUrl 일치, connectorUrl·sessionSurface 보존.
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 실행·env placeholder용이며 serverUrl upstream 거부를 대체하지 않는다. JSON 분리, mcp_url_deny_gate는 URL·serverName만.
이 글이 공격·우회 튜토리얼인가?
아니다. 2.1.318 deniedMcpServers URL 확장 JSON 형태, mcp_server_denied 감사, mcp_url_deny_gate 다섯 분기만 설명한다. URL·connector로 통제 우회 절차는 제공하지 않는다.
정리
Claude Code 2.1.318은 deniedMcpServers URL 매칭을 확장하고 감사 JSON에 decision denied, reason mcp_server_denied, matchedRule, matchedPattern, upstreamUrl를 쓴다. managed settings, 세션 메타데이터, MCP 호출, denied 감사 네 샘플로 mcp_url_deny_gate를 돌린다.
결론: 메타데이터 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 호출 포맷 확인, allow·denied 감사 로그 Diff, deny_by_url·deny_by_connector_url·deny_by_name·allow 분리 집계.