← Back to Blog
pullmdoauthmcpanthropicecosystemMay 13, 2026 · 7 min read

PullMD Part 7: Spec-Compliance Isn't Enough

8/8 SPEC CHECKS · HOST NOT ALLOWED · ZERO REQUESTS
Editorial diagram: a client panel with code on the left feeds a SPEC CHECKS column where eight RFC items all show green check marks, then arrives at a metal HOST NOT ALLOWED gate with a padlock; on the right the MCP SERVER stays idle with 'LOG · ZERO REQUESTS'.Editorial diagram: a client panel with code on the left feeds a SPEC CHECKS column where eight RFC items all show green check marks, then arrives at a metal HOST NOT ALLOWED gate with a padlock; on the right the MCP SERVER stays idle with 'LOG · ZERO REQUESTS'.

v2.0 shipped on May 2. It finally gave me user accounts — and those are the prerequisite for OAuth. That’s exactly why the multi-user rebuild had to come first. Now I could take on Issue #6: OAuth for the claude.ai web connector.

Claude Code and I built an OAuth 2.1 implementation against the MCP spec on feat/oauth, tested it locally, and deployed. Then I tried to add the server in claude.ai web.

What Was Built on feat/oauth

The implementation follows the current MCP spec in full:

  • RFC 9728 — Protected Resource Metadata (/.well-known/oauth-protected-resource)
  • RFC 8414 — Authorization Server Metadata (/.well-known/oauth-authorization-server)
  • RFC 7591 — Dynamic Client Registration (POST /oauth/register)
  • RFC 7636 — PKCE-S256 (code challenge on the authorization endpoint)
  • RFC 6750 — Bearer auth + WWW-Authenticate challenge on /mcp without token
  • RFC 7009 — Token revocation (POST /oauth/revoke)

Plus the complete authorization code flow: GET /oauth/authorize → consent page → POST /oauth/consent → code exchange → JWT issue → refresh token rotation with reuse detection. Tests passed, the endpoints responded correctly. The implementation was done.

The last commit before deployment: fix(oauth): CORS on token/register/revoke/discovery + /mcp for browser MCP clients. Then docker compose pull && docker compose up -d on my Ubuntu development server.

Add Custom Connector → “Couldn’t reach the MCP server”

claude.ai web, settings, “Add custom connector”. Hostname entered. Expected: OAuth flow opens, browser navigates to the consent page, I authorize, done.

What actually came back: “Couldn’t reach the MCP server” — no further information, no stack trace, no HTTP status code.

I tried this a few more times and thought: maybe a CORS issue, maybe a timeout, maybe a brief downtime on my end.

Server Logs Empty

The most striking finding wasn’t the error message — it was what didn’t happen.

Traefik access log: empty. Not a single request during the attempts. Cloudflare analytics: zero traffic on the relevant paths. No TCP handshake, no TLS handshake, no HTTP request — the connection attempts never reached my server.

Direct curl requests from the same network, from mobile, from a VPS: all reached the server without issue and got the expected responses back. The problem wasn’t on my side of the cable.

8 Spec Checks Live-Verified

Before drawing any conclusions, I manually checked all endpoints from a third network:

Check Result
GET /.well-known/oauth-protected-resource (RFC 9728) 200 ✅
GET /.well-known/oauth-protected-resource/mcp (sub-path) 200 ✅
GET /.well-known/oauth-authorization-server (RFC 8414) 200 ✅
POST /mcp without token → 401 + RFC 6750 WWW-Authenticate bearer challenge
HEAD /mcp → 405 with Allow: POST, OPTIONS
POST /oauth/register (RFC 7591 DCR) — accepts claude.ai redirect URI 201 ✅
OPTIONS /mcp from Origin: https://claude.ai (CORS preflight) 204 ✅
GET /authorize → 302 → same-origin /oauth/authorize

All eight checks green. From that point I stopped looking for bugs in my own code and started looking elsewhere.

anthropics/claude-ai-mcp#237

One GitHub search later: Issue #237 in the anthropics/claude-ai-mcp repo. Opened on April 27 — one day before my Reddit post — by a user named Philippstf.

The issue describes a company called Helferlain: a SaaS performance-marketing platform whose entire differentiator is built on letting users connect their own Claude Pro account via a custom MCP connector. YC-track startup, launch deadline within 30 days, several paying beta customers waiting on the integration. The symptom: exactly the same as mine — “Couldn’t reach the MCP server”, server logs empty, spec compliance verified.

Philippstf had even run wrangler tail live during the connection attempts: clientsRegistered=0, codesIssued=0, tokensIssued=0. Not a single request. The diagnosis was precise: Anthropic’s claudeai-proxy never initiates the OAuth flow — the rejection happens internally, before an HTTP request reaches the origin server.

Someone else had already spent a week fighting the same problem, with significantly more pressure on them.

May 3 — Adding a Data Point

I left a comment on #237, brief and factual:

“Same symptom on a different server — adding a data point. Setup: Custom MCP server, OAuth 2.1 spec-compliant. […] Server-side evidence: Traefik access log shows ZERO requests from claudeai-proxy during repeated ‘Add custom connector’ attempts.” — syswave-dev (issue comment)

My reference ID: ofid_24917fdc13bc12fd. That was what claude.ai had returned to me — an identifier on Anthropic’s side that corresponds to a failed connection attempt. I included it in case anyone wants to search Anthropic’s logs for it.

May 4 — The Second Server

The next morning I built a second, independent OAuth MCP server with Claude Code.

Different stack: TypeScript instead of JavaScript, PostgreSQL instead of SQLite, but the same RFC coverage. I’d been meaning to build this second server anyway — and it doubled as the strongest argument for the issue: two independent implementations on different stacks failing identically all but rules out a bug in any single codebase. That’s exactly what I could point to in the thread.

Second comment on #237:

“Built a second independent OAuth-MCP server (different codebase, different stack — TypeScript/PostgreSQL instead of JavaScript/SQLite). Same exhaustive spec compliance, same symptom on ‘Add custom connector’ in claude.ai web UI. […] Two independent codebases failing identically rules out implementation-specific bugs on the user side.” — syswave-dev (issue comment)

New reference ID: ofid_a20d3248e15a0e7b. Same finding. That answered the question “what did I do wrong?”: nothing.

May 6, Early Morning — Confirmation

On the morning I had actually intended to start writing this article, #237 had a new comment from localden (Anthropic Collaborator). The issue was closed:

“#256 captured an x-deny-reason: host_not_allowed header from Anthropic’s egress proxy, which is a strong signal that the underlying cause is a host allowlist on our side rather than anything in your server.” — @localden (Anthropic, issue close)

Issue #256 was a separate report: someone had reproduced the same error with a Meta Ads MCP server on Cloudflare Workers and had captured the egress header x-deny-reason: host_not_allowed — directly from Anthropic’s proxy response, not from their own server. That was the missing evidence: not just “no request comes through”, but specifically “because your host isn’t on the allowlist”.

Consolidated in Issue #214, which remains open and continues collecting affected hosts and reference IDs.

v2.1.0 sits finished on the shelf — I just can’t release it yet. The feat/oauth branch is complete; when the allowlist opens is not in my hands. In the Reddit thread from April 28 I captured the status briefly as an EDIT2, for anyone heading down the same path.

What I Couldn’t Control

Across the three parts, three things ran differently than I could steer.

In Part 5 it was the response on Reddit, which I hadn’t counted on: 58 comments, 320 unique cloners in two weeks, plus feature requests I hadn’t planned for.

In Part 6 it was the multi-user pivot I hadn’t planned. The dependencies between the issues set the order, not me.

And here, in Part 7, it’s something I can’t influence at all: a host allowlist on Anthropic’s side. With the first two I could have planned or prioritized differently. Not here — the code is finished and spec-compliant, the rest is up to someone else.

Building on another company’s infrastructure means living with waits like this. It’s not a complaint, just how it is. So I documented instead of getting annoyed: what got built, which checks were green, which reference IDs I got, what the issue thread turned up. Anyone with the same problem will find it when they search.


Part 7 of 9 in the PullMD series.

PullMD Part 6: The Multi-User Pivot | PullMD Part 8: An Old Rule