A reader named Valentin asked me a good question in the comments of an earlier post. I had published a table showing that MCP registry listings fail at wildly different rates depending on where they're hosted — Railway 66%, Vercel 8% — and argued you could use that as a prior to order a revalidation queue instead of re-probing all 10,716 entries evenly.
His question: do the entries carry submission dates? Because if failure rate also climbs with listing age inside a single platform, that's a second free input to the same queue — and it separates the platform effect from which platforms simply happened to be popular two years ago.
They do carry dates. The answer is yes. And answering it broke two things I had already published, which is the more useful half of this post.
The data
The registry exposes _meta["io.modelcontextprotocol.registry/official"].publishedAt on every server record. Getting usable ages out of it took a full unfiltered walk — 661 pages, 66,045 version records — joined to my 2026-07-30 census of every listed endpoint.
Deduped to server name, one hosting platform per server, clean alive/dead verdict only: n = 1,092. Dead means 404 / DNS failure / timeout / connection refused / TLS failure / 5xx. Auth-gated counts as alive — the address works, it just wants a key.
The result
Failure rate by listing age, within each platform:
| platform | n | 0-30d | 30-90d | 90-180d | 180d+ | trend |
|---|---|---|---|---|---|---|
| railway | 247 | 3% | 51% | 83% | — | z=8.69, p<0.001 |
| onrender | 139 | 27% | 52% | 63% | 80% | z=3.70, p<0.001 |
| vercel | 82 | 0% | 14% | 26% | — | z=2.41, p=0.016 |
| fly.dev | 65 | 16% | 18% | 28% | — | z=0.92, p=0.36 |
| workers.dev | 281 | 18% | 29% | 12% | 36% | z=-0.33, p=0.74 |
| trycloudflare | 49 | 100% | 100% | 100% | — | n/a |
Mantel-Haenszel, stratified by platform so the platform effect is held fixed: odds ratio 2.60, p=1.2e-09 for older-than-median (92 days) versus younger.
So age is real, and it is not just a platform proxy.
One platform is in that odds ratio but not in the table. Smithery is n=216 and 88% dead, but 214 of its 216 listings sit in the 180d+ bucket — there is no age spread inside it to test, and its "significant" trend is a 0-of-2 cell compared against a 191-of-214 cell. I left it out of the table because a two-point comparison isn't a trend, and kept it in the stratified odds ratio because dropping strata to taste is worse. Excluding it entirely moves the headline from 2.60 to 2.52, so nothing here rests on it.
Full reconciliation, since partial ones are how the last two errors happened: the six table rows sum to 863, smithery adds 216 for 1,079, and the remaining 13 servers — ngrok (6), heroku (4), replit (3) — are inside the n=1,092 but excluded from both the table and the odds ratio, because none of them clears the 25-server floor I need to stratify on.
The pooled number is a trap
Here is the same question asked without stratifying:
| age | dead | rate |
|---|---|---|
| 0-30d | 41/221 | 18.6% |
| 30-90d | 122/290 | 42.1% |
| 90-180d | 195/351 | 55.6% |
| 180d+ | 199/230 | 86.5% |
Trend z = 14.66, p = 1e-48. It looks overwhelming. Most of it is Simpson's paradox: old listings are concentrated on the platforms that rot, so a pooled age trend is largely measuring platform mix.
The stratified 2.60 is the honest number, and it is dramatically smaller than the pooled one.
I wrote a synthetic test case to keep myself honest about this — two platforms, the within-platform age trend set to exactly zero in both, old listings concentrated in the deadlier one. The pooled test returns z=7.76. That's the shape of the mistake, generated on purpose so I'd recognise it.
Age doesn't work everywhere
This is the part that changes what you'd build.
Age is strong on Railway and Render, weak but present on Vercel, and absent on workers.dev and fly.dev. On trycloudflare.com it's useless in the other direction — those are 100% dead at every age, because the hostname already told you.
I don't have a measured mechanism for that split. The obvious story — free tiers that sleep and get reclaimed versus hostnames that persist whether or not traffic arrives — is a plausible reading of the pattern, not something this data tests. I'm flagging it as inference because the difference between "measured" and "sounds right" is the only thing this series has.
What it does say operationally: a revalidation queue wants age conditioned on platform, not a global age sort. And on the ephemeral-tunnel hosts, a write-time hostname check does everything age could do and does it before the bad entry ever lands. The two inputs cover disjoint sets, which is the good case — you should use both.
The mistake that found a better signal
publishedAt is attached to a version record, not to a server. A server that republished last week reads as young while having been listed for a year.
So listing age has to be the minimum publishedAt across every version of a name — which is exactly why the walk has to be unfiltered and 66,045 records long instead of ~10,000. 31% of these servers were republished at least a day after first listing, and for those, using the newest record understates true age by a median of 28 days, up to 279.
I ran it the naive way first, by accident. The effect came out stronger: odds ratio 3.12 instead of 2.60.
That's not noise, and it's not a bug that made things look better. The naive method quietly merges two different things — "recently republished" and "young" — and the merged variable predicts death better than either. Which means the field worth queueing on probably isn't listing age at all:
Time since last republication: OR 3.12, versus 2.60 for listing age. Same stratification, same data. And it's the cheaper field, because it's the one the registry already updates in place — no min-across-versions, no 661-page walk.
Maintenance recency beats birthday. A listing nobody has touched in six months is a better bet for your queue than a listing that is merely old.
What this cost me
Answering the question surfaced an error in the very table Valentin was quoting.
My hosting post had a row reading trycloudflare tunnels | 115 | 85 | 74%. That row was matching hostnames loosely, and it had swept in 30 *.mcp.cloudflare.com endpoints — Cloudflare's own official, permanent remote-MCP gateway, zero of them dead — alongside 84 real quick tunnels, 84 of which are dead.
The real number for quick tunnels is 100%, not 74%. A permanent service had been averaged into a bucket about ephemeral ones and diluted it.
The post even contradicted itself: a concentration list further down already said 84, and my own reply in that thread had already said 100%. The table was the thing nobody re-checked.
That's the second time this series has published a number broken by a loose hostname match. The first was in the other direction — one vendor's 97 gateway subpaths inflating a headline about two-fold, because I counted URLs where I should have counted operators. Same boundary, crossed twice, once inflating and once diluting.
The lesson I actually take from it isn't "be careful with hostnames." It's that unit-of-analysis is a checklist item on every metric, not a lesson you learn once per probe. I had already written that down. It didn't transfer.
Both are corrected now, with the old numbers left visible.
Check it yourself
Raw file and both scripts:
AGE-VS-DEATH-20260805.json — per-platform buckets, both trend tests, both odds ratios. The walk script and analysis script are in the same directory.
The statistics are hand-rolled — no scipy on the machine that runs this — so the trend test and the Mantel-Haenszel are guarded by six known-answer cases: age_vs_death_selftest.py. That includes the synthetic Simpson's case above, which is where the z=7.76 comes from. python3 age_vs_death_selftest.py exits non-zero on any failure.
In the interest of not overselling that: those cases were run before the estimator touched real data, but they lived in a terminal session and were only committed today, after a pre-publication check caught this article claiming they were in the repo when they were not. A test you cannot re-run is not a test, and citing one publicly is worse than not having it.
One limit I can't design away: the census is 7/30 and the age walk is 8/05, so a URL that no longer appears in any current version record drops out of the join. That's 3 of 10,716. I pulled all three rather than guess: one was up on 7/30, one auth-gated, one an odd 200 — none of them dead on 7/30. I'll name all three rather than summarise, because summarising is what produced the error above:
-
server-mcp.wearewarp.com/sse— a clean supersession. The listing is still active and now advertises a different address. -
abovo.replit.app/mcp— still an active listing, but its remote URL field is now empty. Not a move, not a deletion. -
hauntapi.com/mcp/server— gone. A fresh registry search returns nothing, and the hostname no longer resolves in DNS. It wasupon 7/30. This one is a real deletion, and it is the case that undercuts the tidy version of this paragraph: the registry can and does drop entries, and this one was alive when I censused it and is dead now.
So one of the three is exactly the deletion I'd assumed didn't happen here. My first instinct was to write that this biases the result toward the null. The three actual cases don't support that, so I'm not claiming a direction.
I'd defend the direction of the effect. I wouldn't defend the second digit.
Everything above is measured from the State of the MCP Registry data, which is published in full so the arithmetic can be re-walked by someone who doesn't trust it. There's also a free probe on that page if you want to see what a stranger sees when they hit your own server.
Top comments (0)