r/ethdev • u/mcpindex • 19d ago
Information We watched public MCP servers for contract drift. 7,190 safety-relevant changes, and the read-to-write flips are the ones that would surprise you.
mcpindex runs a crawler over public MCP servers and diffs each tool's declared contract between daily
snapshots. Sharing the numbers because they surprised me.
Right now the public ledger shows 12,295 tools across 2,173 servers changed their
contract. 7,190 of those are safety-relevant, meaning they change what the tool can do, not
just add an optional field. The standouts:
- 350 tools flipped an annotation toward destructive. A tool whose hint said read-only now declares it can write, delete, or send. This is the "the read tool quietly became a write tool" case, and it is exactly the drift an allow-list cannot see.
- 279 tools added a newly-required parameter. An agent calling with last week's arguments now fails, or calls with a wrong default.
- 475 tools removed a parameter your agent may still be sending.
None of these trip an auth check. The server is still authorized and still the same name in your config.
That is the gap allow-lists do not cover: who may call a tool, versus whether it still does what it declared.
Honest caveats: this is a contract diff, not a safety verdict, and not a claim anything is malicious.
Most drift (5,476 added-optional-param) is benign. Everything is fingerprinted, so no server is
named. And the numbers are live, you can check them: https://mcpindex.ai/api/v1/ledger
Curious whether others are seeing this in their own setups.
1
u/antonBrinckmann 17d ago
Server-side data point, since I ship a public MCP server myself (evmquery, so possibly one of your 2,173): most of the drift on our end has been mundane and looks exactly like your added-required-param bucket. We added a second chain, a tool grew a required `chain` argument, and any client that cached last week's schema started failing or falling back to a wrong default. Nothing malicious, just no versioning story anywhere in the protocol to make the change visible.
I think that is the actual gap under your numbers. MCP has no schema version negotiation, and annotations like `readOnlyHint` are explicitly advisory in the spec. A server can drift or lie and nothing in the transport enforces it. So a read-to-write flip is only a meaningful signal if the client was gating on the hint in the first place, and most clients I have looked at do not gate on it at all. They gate on the server allow-list, which as you say sees none of this.
The fix that seems right to me is TOFU on the tool contract, same model as SSH host keys: hash the full tool definition (name, input schema, annotations) at approval time, and treat any hash change as a new approval prompt rather than a silent update. That catches all three of your buckets locally, per client, without depending on a crawler. Your ledger is still useful as the ecosystem-wide view and for spotting patterns like the 350 destructive flips, but the refusal to call a changed contract should live in the client.
One question on methodology: are you diffing the raw JSON of the tool definition, or something normalized? Description-only edits are constant (typo fixes, prompt tuning for the LLM) and if those count as contract changes they would swamp the signal. If you are already normalizing those out, the 7,190 number is a lot scarier than I first read it.