We run a hosted MCP gateway, and we recently took a real customer deployment — 84 GraphQL tools — through the production clients of Claude, Cursor and ChatGPT. The spec said we were compliant. The clients disagreed, three times in one day. Here's exactly what broke, how each failure looked from the outside, and the fixes — so you don't spend the same day we did.
Bug 1 — the 401 that causes an infinite OAuth loop
Symptom: the client completes OAuth successfully… then immediately re-registers and runs OAuth again. Five rounds later it gives up with a generic connection error. Server logs show nothing but token issuance.
Cause: after connecting, Streamable HTTP clients send a GET to the MCP endpoint to probe for a server-initiated SSE stream. Our gateway answered every GET with 401 + WWW-Authenticate — the discovery response. But to a client holding a valid token, 401 means "your token is dead": it discards the credential and starts over. Forever.
Fix: authenticated GET → 405 Method Not Allowed (we don't offer server-initiated streams); unauthenticated GET keeps the 401 + discovery metadata. One status code, whole loop gone.
Bug 2 — the CORS header nobody lists
Symptom: OAuth completes, and then… silence. Zero requests in the server log. The client reports "Couldn't connect to the server. Check that the URL points to a valid MCP server."
Cause: browser-based and Electron MCP clients preflight every request. The 2025-06-18 MCP revision requires clients to send an MCP-Protocol-Version header — and our Access-Control-Allow-Headers didn't include it. The browser blocked the real request client-side; the server never got a chance to log anything.
Fix: allow mcp-protocol-version, last-event-id and the DELETE method in CORS. While you're there, expose mcp-session-id and www-authenticate in Access-Control-Expose-Headers.
Bug 3 — your own CDN is blocking your AI customers
Symptom: unauthenticated discovery works, OAuth works, and the authenticated tool calls vanish — not rejected, vanished. No log line anywhere.
Cause: Cloudflare's AI-bot policies. Claude's tool-calling traffic identifies itself honestly (user-agent Claude-User, published egress IPs) — which is exactly what AI-crawler blocking matches. The edge returned 403 before our worker ever saw the request. The cruel detail: Anthropic's OAuth service uses a different signature that passed, which made it look like our auth was broken.
Fix: allow AI-agent user agents for your MCP paths (Cloudflare: AI Crawl Control → allow Claude-User; review the legacy "Block AI bots" managed rule). If your customers serve MCPs on their own domains, warn them — they will hit this too.
The meta-lesson
Spec compliance is table stakes; client compliance is the product
We fixed all three at the gateway level, so every MCP hosted on Command+K inherits the fixes — including the AnnounceKit deployment where we found them.
FAQ
- Why does Claude keep re-authenticating against my MCP server?
- The most common cause: your server answers an authenticated GET (Claude probing for a server-initiated SSE stream) with 401 instead of 405. Spec-compliant clients treat that 401 as 'token revoked', discard the token, and loop through OAuth forever. If you don't support server-initiated streams, return 405 Method Not Allowed for authenticated GETs.
- Why do my MCP requests never reach the server from claude.ai?
- Check your CORS preflight. MCP clients send the MCP-Protocol-Version header on every request; if your Access-Control-Allow-Headers list doesn't include it (plus Last-Event-ID and the DELETE method for session termination), the browser blocks the request client-side — your server sees nothing and the client reports 'couldn't connect'.
- Claude says 'couldn't connect' but my OAuth flow completes — why?
- Look at your CDN/WAF, not your code. Cloudflare's AI-bot policies (and the legacy 'Block AI bots' rule) match AI-client signatures like Claude-User and silently block the post-auth calls at the edge — OAuth endpoints pass while tool calls vanish. Allow Anthropic/OpenAI agent user-agents for your MCP paths.