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

PullMD Part 12: Spec-Compliance Isn't Enough – Simply Explained

8/8 SPEC CHECKS · HOST NOT ALLOWED · ZERO REQUESTSAI-generated image
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'.

The multi-user version shipped on May 2. That gave each user their own account, and accounts are the prerequisite for a proper login flow. The standard name for that kind of login is OAuth (Open Authorization): you click “Allow” on a page and the app gets permission to act on your behalf. This was the next step. Claude Code and I built it.

What we built

The login flow follows an official handbook. In the tech world, that handbook is called a “spec,” short for specification. Following it precisely is called “spec-compliance.” There are several documents describing how the different pieces fit together, published as RFCs (Requests for Comments, which is a modest name for documents that are basically internet law). We went through all of them.

The result: a complete login flow. A user visits a consent page, clicks “Allow,” gets a code, and the code is exchanged for a permission token. Plus all the safety mechanisms the spec asks for: challenges to prove you are who you say you are, the ability to revoke access, and proper error responses when something is missing. Tests passed. The endpoints responded correctly. Then I deployed it to my Ubuntu development server.

“Couldn’t reach the MCP server”

In claude.ai there is a settings page with a button: “Add custom connector.” You type in the address of your own server, and the login page is supposed to open. I typed it in. What came back was:

“Couldn’t reach the MCP server”

(MCP, short for Model Context Protocol, is the standard way Claude connects to outside tools.)

No further information. No error code. Just that one sentence.

The most telling clue: silence

I checked my server logs. Logs are the diary a server keeps: every request that arrives, every response that goes out. Nothing was in there. Not a single line during any of my attempts. Not even the first handshake that two computers normally exchange before they start talking. The traffic analytics told the same story: zero requests on the relevant paths.

My server was sitting there, door open, listening. Nobody knocked.

When I tried the same address from my phone, from a different network, from a server in another country, everything worked. The server answered every time. So the problem was not on my side. The connection from claude.ai simply never left their infrastructure.

The homework, verified eight times

Before drawing conclusions, we tested all the spec requirements by hand from a third network. Eight separate checks, each matching one of the rules in the handbook. Every single one came back green.

At that point I stopped looking for bugs in my own code. The problem was somewhere else.

Someone else had hit the same wall

A GitHub search turned up Issue #237 in Anthropic’s public issue tracker. Filed one day before my own Reddit post, by a developer named Philippstf. He described a company called Helferlain: a platform for marketing professionals whose entire product depended on letting users connect their own Claude account through exactly this kind of connector. A startup with a launch deadline and paying beta customers already waiting.

Same symptom as mine. Server logs empty. All checks green. He had even captured a live trace during the failed attempts. The numbers were the most striking part: zero registrations, zero codes issued, zero tokens issued. Not a single request from claude.ai had ever reached his server.

His diagnosis was precise: the rejection was happening inside Anthropic’s infrastructure, before any HTTP request was sent out.

Someone else had already spent a week fighting the same wall, with much more pressure on them than I had.

Adding data points

On May 3, I left a comment on the issue. Brief, 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)

I included a reference ID that claude.ai had given me for the failed attempt: a code Anthropic could use to look it up in their own logs.

The next morning, Claude Code and I built a second, independent server. Different programming language, different database, different stack entirely, but the same handbook followed just as carefully. I had been meaning to build this second version anyway, and it served as the strongest argument: if two completely independent servers, built on different foundations, fail in exactly the same way, the fault cannot be in any single codebase.

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)

That answered “what did I do wrong?” with a clear: nothing.

The confirmation

On the morning I had planned to start writing this article, the issue had a new comment from someone at Anthropic. 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)

An “allowlist” is a guest list. The bouncer at the door only lets in servers whose names are on the list. My server’s name simply was not on it. The rejection happened before anyone even knocked.

A separate report from another developer had captured the exact line in Anthropic’s response. Not a guess anymore: direct evidence from their own proxy saying “host not allowed.” The affected cases are now consolidated in Issue #214, which remains open and continues collecting hosts and reference IDs.

Finished, waiting

The OAuth version of my tool sits complete on its branch. I just cannot release it yet. When the guest list opens is not in my hands. I added a short note to the original Reddit post, so anyone heading down the same road finds it quickly.

Three kinds of not being in control

Across the last few parts, three things went differently than I could steer.

In Part 9 it was the response on Reddit. I had not counted on that: dozens of comments, hundreds of people cloning the project, feature requests I had not planned for. Surprising, but good.

In Part 11 it was the multi-user pivot I had not planned. The dependencies between the issues set the order, not me. I could have planned that differently.

And here, in Part 12, it is something I cannot influence at all: a host allowlist on Anthropic’s side. The code is finished and spec-compliant. The rest is up to someone else.

Building on another company’s infrastructure means accepting walls like this. That is not a complaint. It is just how things work when someone else runs the platform underneath you. So I documented instead of getting frustrated: what got built, which checks came back green, which reference IDs I got, what the public issue turned up. Anyone with the same problem will find it when they search.


Part 12 of 15 in the PullMD series.

← PullMD Part 11: The Multi-User Pivot | PullMD Part 13: An Old Rule →

Comments

Commenting takes an email confirmation or a GitHub login. Comments run on my own server, not on an ad provider's — details in the privacy policy.