CIMD: claude.ai bot protection 403s server-side fetches of /oauth/claude-code-client-metadata, breaking MCP OAuth for servers on cloud egress IPs

Status Open
Maintainer reply None cached
Activity 1 comment · opened Aug 5, 2026

Summary

Newer Claude Code builds authenticate to MCP servers using the URL-shaped client id https://claude.ai/oauth/claude-code-client-metadata (Client ID Metadata Documents). Per the scheme, the MCP server's OAuth provider must fetch that document server-side on first resolution. claude.ai's edge (Cloudflare) returns 403 for that fetch from some cloud-provider egress IPs, which makes spec-compliant authorization servers reject the client with invalid client_id — bricking Claude Code MCP auth for every user of that server.

Evidence (2026-08-05, our production OAuth server on Fly.io)

Identical request (GET https://claude.ai/oauth/claude-code-client-metadata, Accept: application/json, httpx):

| Vantage | Result |
|---|---|
| Residential IP (any UA incl. python-httpx) | 200 |
| Fly.io machine A (shared egress IP) | 200 |
| Fly.io machine B (shared egress IP 152.233.48.147) | 403, cf-ray: a267cdde199b1272-IAD, server: cloudflare |

So the block is per-source-IP reputation, not user-agent. Our authorization server logged document fetch returned HTTP 403 and (correctly, per the scheme) rejected the unknown client — users saw {"detail":"Invalid client_id"} on the authorize page.

Impact

Any MCP OAuth provider hosted on cloud ranges with imperfect IP reputation (Fly shared egress, and presumably some AWS/GCP NAT ranges) will intermittently or persistently fail to mint the Claude Code CIMD client. The failure is confusing to diagnose: it presents as the server rejecting Claude Code.

Suggested fix

Exempt /oauth/claude-code-client-metadata (or the /oauth/*-client-metadata path family) from bot-protection/IP-reputation challenges on claude.ai — the document is ~300 bytes of public JSON that is designed to be fetched by servers, not browsers.

Workarounds we applied (for other server operators hitting this)

  • Allocate dedicated egress IPs (fresh reputation) — fixed it immediately in our case.
  • Cache-and-serve-stale: once a client row is minted, serve it even when refresh fetches fail.
  • Optionally proxy the fetch through an egress hop with stable reputation.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗