We scanned 578 Python MCP servers from the official
registry. The change that dominated every discussion of this revision — protocol
sessions and the Mcp-Session-Id header going away — appears in
3.3% of them.
The official MCP registry points at 19,646 unique GitHub repositories. That is up from 14,249 on 2026-08-01, a 38% increase in six weeks, while the dead-link rate barely moved (15.3% then, 14.4% now).
14.4% of them 404. In a random sample of 3,000 — seeded, drawn from the full deduplicated population rather than an alphabetical prefix — 433 pointed at repositories that no longer exist publicly: deleted, renamed, or made private.
Of the 2,567 live repositories in that sample:
| Language | Repos | Share |
|---|---|---|
| TypeScript | 1047 | 40.8% |
| Python | 580 | 22.6% |
| (none detected) | 384 | 15.0% |
| JavaScript | 339 | 13.2% |
| Rust | 59 | 2.3% |
| Go | 52 | 2.0% |
These are the changes the 2026-07-28 changelog leads with, and the ones that filled the threads. Each row is the share of the 578 scanned servers where the pattern actually appears.
| Rule | What it finds | Servers | Share |
|---|---|---|---|
| R001 | Uses Mcp-Session-Id, which no longer exists | 19 | 3.3% |
| R009 | Still implements the initialize / notifications/initialized handshake | 58 | 10.0% |
| R011 | Implements the removed ping request/response | 44 | 7.6% |
| R013 | Uses resources/subscribe or resources/unsubscribe, replaced by subscriptions/listen | 4 | 0.7% |
| R012 | Implements the removed logging/setLevel request | 3 | 0.5% |
| R017 | Uses the old -32002 resource-not-found error code | 2 | 0.3% |
| R014 | Implements SSE resumability (Last-Event-ID / event redelivery) | 7 | 1.2% |
483 of 578 servers (83.6%) have nothing breaking to fix at all. Most never touched the transport directly; their framework did, and the framework absorbed the change.
| Rule | Severity | What it finds | Servers | Share |
|---|---|---|---|---|
| R010 | advisory | Registers MCP request handlers but never implements server/discover | 274 | 47.4% |
| R004 | advisory | tools/list order is not deterministic | 131 | 22.7% |
| R003 | advisory | Custom HTTP client does not send Mcp-Method (or Mcp-Name where required) | 68 | 11.8% |
| R009 | breaking | Still implements the initialize / notifications/initialized handshake | 58 | 10.0% |
| R016 | advisory | List/read results are returned without ttlMs / cacheScope | 49 | 8.5% |
| R015 | advisory | Results are returned without the required resultType field | 48 | 8.3% |
| R011 | breaking | Implements the removed ping request/response | 44 | 7.6% |
| R006 | deprecated | Uses the deprecated HTTP+SSE transport | 25 | 4.3% |
| R001 | breaking | Uses Mcp-Session-Id, which no longer exists | 19 | 3.3% |
| R008 | advisory | Does not propagate OpenTelemetry trace context from _meta | 17 | 2.9% |
| R002 | breaking | Keeps per-connection state in a module-level dict | 12 | 2.1% |
| R020 | deprecated | Uses Dynamic Client Registration (RFC 7591), now deprecated | 12 | 2.1% |
| R018 | breaking | Uses a server-initiated request replaced by Multi Round-Trip Requests | 8 | 1.4% |
| R005 | advisory | Server capabilities declare no extensions map | 7 | 1.2% |
| R014 | breaking | Implements SSE resumability (Last-Event-ID / event redelivery) | 7 | 1.2% |
| R013 | breaking | Uses resources/subscribe or resources/unsubscribe, replaced by subscriptions/listen | 4 | 0.7% |
| R012 | breaking | Implements the removed logging/setLevel request | 3 | 0.5% |
| R021 | advisory | Pins an older JSON Schema dialect than 2020-12 | 3 | 0.5% |
| R007 | deprecated | Depends on a deprecated core feature (Roots / Sampling / Logging) | 2 | 0.3% |
| R017 | breaking | Uses the old -32002 resource-not-found error code | 2 | 0.3% |
| R019 | breaking | Uses removed tasks/list or the removed blocking tasks/result | 1 | 0.2% |
| Grade | Servers | Share |
|---|---|---|
| A | 400 | 69.2% |
| B | 83 | 14.4% |
| C | 46 | 8.0% |
| D | 32 | 5.5% |
| F | 17 | 2.9% |
Read the A column carefully — we cannot fully stand behind it.
A server written in the current (2.x) SDK spelling is not recognised as an MCP server by this scanner at all yet. It gets scanned, produces nothing, and grades A without having been checked. So an A here means “we found nothing”, which is not the same claim as “there is nothing”.
174 servers produced zero findings. Split by the SDK version they declare, the gap is not subtle:
| Declares | Servers | Zero findings | Rate |
|---|---|---|---|
mcp 1.x | 289 | 12 | 4% |
mcp 2.x or later | 66 | 46 | 70% |
| nothing readable | 223 | 116 | 52% |
A server on the current SDK is 17× more likely to come back completely empty than one on 1.x. The likeliest explanation is not that 2.x servers are cleaner — it is that we are not reading them. Tracked as #255.
An earlier version of this scan led with a different headline:
78.2% of servers do not implement server/discover. We were
about to publish it. It was wrong, and the way it was wrong is worth more than the number
was.
R010 checked whether server/discover appeared in a project’s own
source. On the 2.x Python SDK it never does — Server.__init__ registers
the handler itself:
>>> from mcp.server.lowlevel import Server
>>> Server("demo")._request_handlers # already contains 'server/discover'
And on 1.x the method does not exist at all, so there is no handler anyone could add. The finding was unactionable in both directions: impossible on 1.x, unnecessary on 2.x. It was measuring where a string appears, not what a server implements.
The tell was in the contributions, not the data. Two separate pull requests tried to
write an autofixer for it and both emitted @app.discover() — an API in
no version of the SDK. That is not two people being careless; that is what you get when
you ask someone to scaffold an implementation of something that either cannot exist or
already does.
Corrected, R010 now reads the SDK a project declares and stays silent when it cannot tell. On our 18-server board, checked at pinned commits so the comparison is controlled, it fired on 17 of 18 before and 14 of 18 after. In this scan it appears on 274 of 578 servers (47.4%).
We publish grades about other people’s code. A false finding costs more than a missed one, and it costs most when it is the headline.
The registry was crawled through
registry.modelcontextprotocol.io/v0/servers?version=latest, paginated to
exhaustion and deduplicated to 19,646 GitHub repositories. A seeded
random sample of 3,000 was drawn from the full population — not a
prefix; registry names are not randomly ordered, and an earlier pass that sampled
alphabetically measured the dead-link rate at 35% instead of 14.4%.
Primary language came from the GitHub API. Every Python repository in the sample was
shallow-cloned, scanned, and deleted. Test directories are excluded by default, because
back-compat tests deliberately exercise legacy transports and would otherwise punish
well-tested projects.
These are aggregates on purpose. We are not publishing a per-server grade for 578 projects we have not read individually; the board is where per-server grades go, and every entry on it was read by a human before it was recorded.
Scanned with scripts/ecosystem_scan.py, mcp-migrate 0.5.0.
python scripts/ecosystem_scan.py --all