What the MCP 2026-07-28 revision actually breaks

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 registry is smaller than it looks

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:

LanguageReposShare
TypeScript104740.8%
Python58022.6%
(none detected)38415.0%
JavaScript33913.2%
Rust592.3%
Go522.0%

The removals everyone discussed

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.

RuleWhat it findsServersShare
R001Uses Mcp-Session-Id, which no longer exists193.3%
R009Still implements the initialize / notifications/initialized handshake5810.0%
R011Implements the removed ping request/response447.6%
R013Uses resources/subscribe or resources/unsubscribe, replaced by subscriptions/listen40.7%
R012Implements the removed logging/setLevel request30.5%
R017Uses the old -32002 resource-not-found error code20.3%
R014Implements SSE resumability (Last-Event-ID / event redelivery)71.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.

Everything, ranked

RuleSeverityWhat it findsServersShare
R010advisoryRegisters MCP request handlers but never implements server/discover27447.4%
R004advisorytools/list order is not deterministic13122.7%
R003advisoryCustom HTTP client does not send Mcp-Method (or Mcp-Name where required)6811.8%
R009breakingStill implements the initialize / notifications/initialized handshake5810.0%
R016advisoryList/read results are returned without ttlMs / cacheScope498.5%
R015advisoryResults are returned without the required resultType field488.3%
R011breakingImplements the removed ping request/response447.6%
R006deprecatedUses the deprecated HTTP+SSE transport254.3%
R001breakingUses Mcp-Session-Id, which no longer exists193.3%
R008advisoryDoes not propagate OpenTelemetry trace context from _meta172.9%
R002breakingKeeps per-connection state in a module-level dict122.1%
R020deprecatedUses Dynamic Client Registration (RFC 7591), now deprecated122.1%
R018breakingUses a server-initiated request replaced by Multi Round-Trip Requests81.4%
R005advisoryServer capabilities declare no extensions map71.2%
R014breakingImplements SSE resumability (Last-Event-ID / event redelivery)71.2%
R013breakingUses resources/subscribe or resources/unsubscribe, replaced by subscriptions/listen40.7%
R012breakingImplements the removed logging/setLevel request30.5%
R021advisoryPins an older JSON Schema dialect than 2020-1230.5%
R007deprecatedDepends on a deprecated core feature (Roots / Sampling / Logging)20.3%
R017breakingUses the old -32002 resource-not-found error code20.3%
R019breakingUses removed tasks/list or the removed blocking tasks/result10.2%

Grades

GradeServersShare
A40069.2%
B8314.4%
C468.0%
D325.5%
F172.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:

DeclaresServersZero findingsRate
mcp 1.x289 124%
mcp 2.x or later66 46 70%
nothing readable223 11652%

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.

What we got wrong, and how we know

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.

Method

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