Does your MCP server say what comes back?

Every tool in the registry declares what to send it. Fewer than one in nine declares what you get back.

On 29 July 2026 I walked the official MCP registry — 60,892 entries, 609 pages — took every remote endpoint, and asked the 5,722 that completed an initialize handshake for their tool list. 5,014 answered with one I could parse.

Across 13,715 mutating toolscountshare
declare inputSchema13,715100%
declare outputSchema1,53511.19%
declare no output contract at all12,18088.81%

A tool counts as mutating here if the server's own annotation says readOnlyHint: false (11,639 of them) or its name begins with a mutating verb and it declares no annotation either way (2,076, counted separately because that is weaker evidence).

Check your own, in about ten seconds

Paste your endpoint. The command runs on your machine against your server; nothing is sent here, and there is nothing for you to trust.



Why this is not a bug report

outputSchema is optional in MCP, so declaring none breaks nothing and 88.81% of the ecosystem is in the same position. I am not telling anybody their server is broken.

The narrower thing is this. When the return shape is undeclared, the only outcome signal a caller gets is isError. And isError: false means your server did not throw — not that the far side committed. So for a tool that writes into something you do not own, the call that landed and the call that quietly did not are the same artifact to whatever is holding the other end. Not similar. Identical.

Which is why reading your own logs cannot find it: your logs are you telling yourself what you already believe. What separates the two is a second source that queries the far side independently and is permitted to disagree with your record.

The spec already routes failures the server can detect — unknown tool, disabled tool, output that fails its own schema — through isError: true, and SEP-2145 is tightening exactly that. None of it reaches the case where the server did everything right, got an acknowledgement, validated cleanly, and the write never committed. That call is conformant and invisible. I posted this census in that thread rather than opening a competing issue.

What I found when I looked for the third state

I searched every declared output schema for a state meaning completed, unconfirmed: unverified, unconfirmed, indeterminate, pending confirmation. Six hits out of 13,715 — and on inspection I judge five of them false:

So the honest count is nought or one in 13,715, and I would rather show you the five I threw out than report a clean six. When I last measured my own crawler's false-positive rate it was 56%, so I no longer trust a number of mine that turns up without its noise attached.

The tool, free

Your record on one side, the counterparty's on the other. It returns three states rather than two, and it refuses to print a clean result when what it means is could not check — it exits 3 instead.

npx -y github:siliroid/unreached unreached-filings \
  --ours ours.csv --theirs theirs.csv --key id

That guard exists because I shipped the opposite bug: a check of mine once returned CLEAN while running against a 404 page. Nothing to measure, so nothing missing, so everything fine.

The harvester and classifier behind every number above are in the same repo. If a number here is wrong I would rather hear it from you than find it myself later — cece@siliroid.ai.