A month ago I shipped a 40-line padlock for MCP tools: takes a canonical SHA-256 over name + description + inputSchema and blocks when the live manifest stops matching the one you approved. That canonicalization is already the good kind of pin: it sorts keys and strips whitespace, so a re-serialized-but-identical manifest does not trip it. I checked that first, and it holds. The pin is not paranoid about JSON formatting.
It is paranoid about meaning. And that is the problem, because most meaning changes to a schema are harmless to your calls. The MCP spec itself is the clearest evidence. The protocol ships dated, breaking-capable revisions on a cadence; the schema directory in . Run both. The hash catches the description-poisoning and rug-pull shapes, where a byte-level change is the signal and "compatible" is the wrong frame entirely: a hidden instruction slipped into a description is backward-compatible by subtyping and still hostile. The pin is your detector for "changed at all." This gate is your filter for "changed in a way that breaks my calls." They answer different questions and they belong in the same pipeline.
It is not a semantic scanner. It does not read the meaning of a description or judge whether a new parameter is dangerous. It asks one structural question about the argument set.
The breaking fixtures are synthetic. C3 to C5 are fixtures I wrote. They are confirmed by independent replay (a call valid under old genuinely fails under new, and the tool prints the failing path), not by my say-so, but they are not a live server. The one real diff is C2, and I checksummed it.
A PASS is not a proof of full compatibility. It means: on the six schema keywords I model, zero breaking changes, and zero of your recorded calls broke. It does not mean nothing can go wrong. See the section above.
Run it against your noisiest server
Pull the tools/list for the MCP server your agent leans on hardest. Save the inputSchema you have now, save the next version when the server updates, dump a few hundred of your real recorded calls to a .jsonl, and run compat_gate.py gate. If it passes, you just re-approved an update without a human reading a diff at 3am. If it fails, it hands you the exact calls that would have broken, before the first one goes out. That is the cheapest pre-call check you will add this quarter, and it runs before the call instead of in the postmortem.
Here is the part I still have not settled, and I want your take. Signal (A) and signal (B) disagree in the C5 shape (breaking schema, untouched traffic), and I resolved it by failing closed with a loud annotation. I am not sure that is right for every team. If you re-version aggressively, C5 is going to fire constantly on parameters nobody sends, and constant firing is how a gate gets disabled. Do you gate on the schema signal (conservative, noisy) or the replay signal (permissive, blind to unseen paths)? Do you tie re-approval to a publisher signature and skip the diff entirely? I have run this against exactly one real diff and a pile of fixtures, so my line is drawn in pencil. If you have run a compat check against a server that updates for real, tell me where you put the line and why. I read every comment.
If this was useful, the is the layer that decides whether a specific action runs at all, and the MCP server token tax measures what each tool costs you in context. Follow along; the next one keeps walking down this stack.
SOCIAL SHARE CARD GENERATOR