CIMD: claude.ai bot protection 403s server-side fetches of /oauth/claude-code-client-metadata, breaking MCP OAuth for servers on cloud egress IPs
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.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗