PullMD Part 7: Spec-Compliance Isn't Enough – Simply Explained
By this point in the story of my little Markdown service, PullMD, I’d built it, opened it up to other people, and given each user their own login. This part is about the moment when all of that was finished — and still didn’t work. Not because of a mistake on my side, but because of a setting on someone else’s.
What I had built
After the multi-user version was out, the next step was obvious: let people connect the tool to claude.ai through a proper login flow. This kind of login — where you click “Allow” on a page and the app gets permission to act on your behalf — has a standard name, OAuth.
Over a few days, Claude Code and I built exactly that, following every rule in the official handbook for how such logins should work. The technical handbook is called a “spec” — short for specification — and following it precisely is called “spec-compliance”.
There are eight separate documents that describe how the pieces should fit together. We implemented all eight. Then I deployed it to my server.
The first attempt
In claude.ai, there is a button: “Add custom connector”. You enter the address of your own server and the login page is supposed to open.
I clicked it. What came back was:
“Couldn’t reach the MCP server”
No further information. No error code. No stack trace. Just that one sentence.
I tried it a few more times. Maybe a network hiccup. Maybe a short outage on my end. Maybe something I had set up wrong.
The most telling clue: silence
I looked at my server logs. Logs are the diary every server keeps: every request that arrives, every response that goes out.
Nothing was in there. Not a single line for those attempts. Not even the first handshake that two computers normally exchange before they say hello.
My server was sitting there, listening, but nobody was knocking on the door.
When I tried the same addresses from my phone, from a completely different network, from a server in another country — everything worked. The server answered every time. So the problem wasn’t with my server. The problem was that the request from claude.ai never even left their side.
The homework, double-checked
Before drawing any conclusions, I tested all eight rules from the handbook by hand, from a third network. Every endpoint returned exactly what it was supposed to return. Every check came back green.
At that point I stopped looking for bugs in my own code. The problem had to be somewhere else.
Someone else had been here first
I searched GitHub, where developers track problems publicly, and found an issue from a week earlier. A user called Philippstf had written it up.
He had built something for his company. A platform for marketing professionals, with a launch date already promised and customers already waiting. The whole product was built around exactly this feature: users connecting their own claude.ai account to his server. Same symptom as mine. Server logs empty. All checks green.
He had even managed to capture 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 very precise: the rejection was happening on Anthropic’s side, before any request was sent out. The signal never crossed the wire.
Someone else had already spent a week fighting the same problem, with much more pressure on them than I had.
I left a note
I added a comment to the issue, brief and factual. Same symptom, different server, the eight checks all verified. Plus a reference number that claude.ai had given me — a little code Anthropic could use to look up my failed attempt in their own logs, if anyone there ever did.
A second server as proof
The next morning, Claude Code and I built a second, independent version of the same service. Different programming language, different database, different tools — but the same handbook, followed just as carefully.
I’d been meaning to build this second version anyway, and it doubled as the strongest argument in the discussion: if two completely independent servers, built on different foundations, fail in exactly the same way, the fault can’t be in my code.
The second server failed in exactly the same way. Two completely different codebases, same symptom. That ruled out “it’s something specific to your code”.
I left a second comment on the issue. Same finding.
At that point, the question “what did I do wrong?” had its honest answer: nothing.
The confirmation, a few days later
On the morning I had planned to write this article, the issue had a new comment from someone at Anthropic. The issue was closed:
“#256 captured an
x-deny-reason: host_not_allowedheader 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.”
To translate: an “allowlist” is a guest list. The bouncer at the door only lets in people whose names are on the list. My server’s name was simply 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 that said so. Not a guess anymore. Direct evidence.
That’s the missing piece. Not just “no request comes through” but specifically “because your server isn’t on our guest list”.
So now I wait
The OAuth version of my tool sits finished on its branch — I just can’t release it yet. When the guest list opens for everyone isn’t something I can decide.
I added a short note to the original Reddit post from a few weeks earlier, so anyone heading down the same road finds it quickly.
Three kinds of not being in control
Looking at this trilogy together, I notice three different versions of “things didn’t go as I planned”:
Part 5 — what I didn’t expect. Other people showed up. Comments, downloads, feature ideas. Surprising but good.
Part 6 — what I didn’t plan. The order of things changed underneath me. A single comment on the issue tracker reordered my whole roadmap. I could have planned that better.
Part 7 — what I can’t influence at all. Someone else’s switch. With the first two, I could imagine doing things differently next time. With this one, no. The work is correct. The waiting just happens.
Building on top of someone else’s platform means accepting that walls like this exist. That’s not a complaint. It’s just how things are when someone else runs the infrastructure underneath you. I chose to document instead of getting frustrated: what I built, which checks were green, which reference numbers I got, what the public issue showed. Others walking down the same path will find that when they search.
Part 7 of 9 in the PullMD series.
← PullMD Part 6: The Multi-User Pivot | PullMD Part 8: An Old Rule →