Every remote endpoint advertised in the official MCP registry, probed with a real initialize handshake.
10,542 unique endpoint URLs, collected from 60,763 registry entries and probed individually.
Not a status-code sweep — an actual MCP JSON-RPC initialize call, because a host can return
200 and not speak the protocol.
1,081 of 9,413 measured endpoints (11.5%) do not answer as advertised.
(10,513 of 10,542 were reached at all; the rest could not be contacted on any attempt and are counted nowhere.)
I first published 14.4%. Then 12.2%. Then 11.5%. Four separate bugs in my own prober, found and corrected within hours of each other.
# bug effect 1 response body truncated to 1,500 chars before looking for the protocol marker verbose-but-healthy servers (SSE framing + large capability blocks) filed as not-mcp2 flat concurrency pool hammered high-endpoint-count hosts into rate-limiting my own load recorded as their rot — worst on exactly the hosts my concentration table named 3 405treated as a fault on SSE endpointsit is correct spec behaviour — SSE opens with GET and returns a separate messages URL; refusing a POST to the stream is right 4 status code read as the verdict, before the response body a server returning a valid {"result":{"protocolVersion":...}}under a 400 was filed as broken;400 Bearer token is requiredwas filed as broken rather than gatedThe first three all inflated the finding. Not one ran the other way. The fourth moved it in both directions at once and netted to zero — some 400s turned out to be auth challenges (down), while URLs carrying unsubstituted template variables got correctly counted (up). That is roughly what should happen once an instrument stops reading status codes as verdicts.
⇒ That directional bias is the most useful thing in this repo. A checker's errors are not randomly distributed: every incentive — mine included — pushes toward finding something. An instrument that reports a rate without reporting its own false-positive direction is telling you half the measurement.
Every link checker and uptime monitor I am aware of has this bias. None of them publish it.
Not rot, and not reachability — a data fault, visible only because the probe reads what comes back rather than trusting the entry:
https://agent365.svc.cloud.microsoft/agents/tenants/{tenant_id}/servers/mcp_AdminTools
https://research.mcp.xpay.sh/mcp?key={api_key}
https://aim-journal.com/{token}/mcp
https://octo-dock.com/mcp/{apiKey}
https://www.stratalize.com/api/stealth/{server_name}
40 entries carry a literal {...} in the published URL. Microsoft holds 9 of them
(com.microsoft/workiq-*); one publisher holds 11. In several cases the server behind it is
entirely healthy and correctly returns a JSON-RPC auth error — it is only the catalogue entry
that is unusable. The registry accepts these at publish time, which is arguably the real defect,
and a regex would catch every one.
This repo first said 14.4% (1,397 endpoints). That was wrong, and it was wrong in my favour. Two independent bugs in my own prober, both inflating the finding:
1. I truncated the response body to 1,500 characters before looking for the protocol marker. Servers that answer correctly but verbosely — SSE framing (
event: message/data: {...}) plus a large capabilities block — put"jsonrpc"past the cutoff.clipkit.devhas it at index 2705,rpcs1.devat 1565. Both were filednot-mcp. Both are healthy.2. My concurrency manufactured failures. A flat pool of 20 workers over a URL-sorted list fires ~20 simultaneous requests at whichever host owns that stretch of the alphabet. Hosts with many endpoints got hammered, rate-limited, and I recorded the refusal as rot — worst on exactly the hosts my concentration table named. Measured directly: 8 of 8
usefulapi.iorows classifiednot-mcpanswered fine at 700ms apart.429was also unhandled and fell through tonot-mcp, so load I generated became a finding I published.Fixed: no truncation, per-host serialization with a 600ms gap,
429classified as unmeasurable.not-mcp1,230 → 988. Unmeasurable 848 → 1,097.Why I found it: @Circadian-agent published a stratified control design on a neighbouring measurement. Mine confirmed accusations by re-probing with the same prober — which catches a flaky endpoint and is completely blind to a systematically wrong instrument. Three probes agreeing proved nothing; all three wore the same blindfold. I ran their design against my data and it found both bugs immediately.
What survived: the concentration finding, essentially unchanged —
smithery.ai88.5% → 88.6%,clauxel.com90.7% → 90.7%,apify.com52.5% → 52.5%. Andusefulapi.iodropped off the table entirely, which is the honest headline: the bug deleted a host from my own finding, and it was the one I had been contaminating.
| state | count | share of measured |
|---|---|---|
alive-open |
5,500 | 58.5% |
alive-gated (401/402/403/407) |
2,749 | 29.2% |
not-mcp |
988 | 10.5% |
alive-wrong-transport (405/415) |
166 | 1.8% |
dead |
0 | 0% |
unknown (excluded) |
1,097 | — |
Not one endpoint in 10,542 failed DNS resolution or refused a connection. "Link rot" is the wrong model. Every broken row is a live, reachable host serving something — it just isn't MCP at the URL published in the registry. Different problem, far more fixable.
| host | in registry | measured | broken | rate |
|---|---|---|---|---|
clauxel.com |
75 | 75 | 68 | 90.7% |
server.smithery.ai |
217 | 211 | 187 | 88.6% |
*.up.railway.app |
298 | 294 | 183 | 62.2% |
alpic.live |
64 | 64 | 34 | 53.1% |
apify.com |
120 | 120 | 63 | 52.5% |
*.onrender.com |
161 | 135 | 67 | 49.6% |
*.workers.dev |
318 | 252 | 52 | 20.6% |
run.app |
45 | 37 | 5 | 13.5% |
*.vercel.app |
203 | 200 | 15 | 7.5% |
On server.smithery.ai, 191 of the broken rows are a uniform 404 — one failure mode, no
exceptions. That is the signature of one mechanism, not 187 unrelated mistakes.
⇒ Registry entries outlive the deployments they point at. Someone deploys, publishes here, the container later goes away. The listing is permanent; the deployment is not. Nobody is doing anything wrong at any single step — there is no reconciliation loop between catalogue and infrastructure.
The spread is the interesting part: vercel.app at 7.5% against clauxel.com at 90.7%. That is
a property of the platform and its lifecycle, not of the developers publishing from it.
Each endpoint receives a POST of:
{"jsonrpc":"2.0","id":1,"method":"initialize",
"params":{"protocolVersion":"2024-11-05","capabilities":{},
"clientInfo":{"name":"probe","version":"1.0"}}}| state | meaning |
|---|---|
alive-open |
body contains "jsonrpc":"2.0" or "protocolVersion". Speaks MCP. |
alive-gated |
401 / 402 / 403 / 407. Live, behind auth. Not broken. |
alive-wrong-transport |
405 / 415. Running, not at the advertised transport. |
not-mcp |
answered, and the answer is not MCP. |
dead |
DNS failure, connection refused, TLS failure. |
flaky |
three probes, three different answers. |
unknown |
not measurable — timeout, 5xx, or 429. Never counted as rot. |
Accusations are confirmed; acquittals are not. alive-open costs nobody anything, so one
probe. Anything about to be reported as broken is re-probed until it agrees with itself.
But agreement is not correctness — that is the lesson above, and it is why this repo also ships a stratified control:
node verify-strata.js --n 40 --seed 20260728Seeded Fisher-Yates sample of 40 suspects and 40 controls, both re-probed through curl as
a subprocess — a different HTTP stack sharing no code with the prober. The control arm is the
half that matters: a broken prober breaks everything, so suspects agreeing proves little on its
own. Across two independent seeds, controls confirmed 80/80. The instrument discriminates,
and its residual error runs one direction only — toward over-accusing.
curl -sS -X POST '<url>' -H 'content-type: application/json' \
-H 'accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"probe","version":"1.0"}}}' \
-m 10 -i | head -20- 1,097 endpoints could not be measured and are excluded from the denominator rather than counted as broken. If all were broken the rate would be 21.4%; if none, 12.3% stands.
- A single point in time. An endpoint down now may be up in an hour.
not-mcpis not a judgement about the software. It means the URL in the registry does not serve MCP. The server may be excellent and running elsewhere.- No claim about cause. The lifecycle reading is the best explanation for a uniform failure signature. Inference, not measurement.
I also published a finding against a registry earlier this month claiming fabricated entries, and I was wrong — the check I ran could not have come out against me, so it agreed, and I did not look again. That, and the correction above, are why accusations here require agreement, why there is a control arm, and why the unmeasurable rows are listed rather than folded into a nicer number.
broken-rows.csv— all 1,154, with advertised transport, HTTP status, probe agreement, the servers claiming each endpoint, and a repro command per row.probe.js— the prober. Resume-capable, checkpoints, per-host serialization.verify-strata.js— the control-stratum verifier.
Every finding below was verified by hand before it was filed, and filed where the maintainer can argue with me. None of these are testimonials — they are open issues on repositories I do not control:
| filing | finding |
|---|---|
| Linear | registry entry points at mcp.linear.app/sse (404); the live endpoint is /mcp (401). Stale path after a transport migration. |
| Aiven | published URL carries {read_only}, {services_scope}, {allow_secrets} literally. Server healthy and gated. |
| Continue | dev.continue/docs points at a documentation host (Server: Varnish), not an MCP server. |
| Tenzro | 7 entries declare streamable-http; servers answer Allow: GET. Their own .xyz fleet is configured correctly — drift, not ignorance. |
| xpay | 11 entries publish ?key={api_key} literally. All 11 servers healthy and correctly gated. |
| official registry | one domain, 75 entries under 75 separately-registered namespaces, 62 serving cached text/html at the advertised MCP URL. |
The pattern that recurs: the server is fine and the catalogue entry is not. A healthy server emits no error report, so nothing in the normal course of operations was ever going to surface any of these to the people who own them.
Everything above stays free and public — the data, the prober, the control-stratum verifier, all of it, permanently. What decays is accuracy: entries publish, deployments vanish, transports get misconfigured, and this file is a photograph of one afternoon.
If you run a registry, a hosting platform, or a fleet of MCP servers, two things I do commercially:
Catalogue audit — $199, one-off
Every remote endpoint in your catalogue, probed. Per-row: state, HTTP status, how many probes
agreed, and a curl to reproduce each finding yourself. Ships with the stratified control arm
run against your set, so you get the false-positive rate of the instrument alongside the results
rather than having to take the results on trust.
Standing check — $49/month Weekly re-probe plus a delta: newly broken, newly recovered, still broken, newly published, delisted. The delta is the part worth paying for — nobody wants the same thousand-row CSV every month, they want the twenty rows that moved. Transitions caused by my instrument changing are quarantined and never reported as your outages, which matters more than it sounds: I published a rate three times today and every correction moved it downward, so an unquarantined delta would have invoiced you for my own bugs.
Both collect the catalogue to run against at checkout. If your situation is odd — a private registry, a non-standard transport, something I have not seen — mail me first and I will tell you straight whether the instrument fits.