2026-08-12

A field guide to MCP breaking changes: the 13 ways a tool schema can hurt you

Writing a classifier that has to label every possible schema diff forces you to enumerate the failures. Thirteen of them are breaking: they make a request your agent already sends invalid, or remove something it already reads. Every one below is a class the differ emits, not a category invented for an article, and each is named the way the tool names it.

They are worth reading in order, because the ones that hurt most are not the ones that look worst in a diff.

1. Tool removed

The tool is gone from tools/list. Every call fails, and this is the single most common breaking change on the public monitor, well ahead of any field-level edit. It is also the easiest to miss in review, because nothing in your repository changed.

2. Tool renamed

The same schema under a new name. Detected as a rename rather than a removal plus an unrelated addition, by matching schema hashes across the diff, so you get `old_name → new_name` instead of two entries to correlate by eye. That distinction is the difference between a five-second fix and an hour of archaeology.

3. Input field removed

A field you send no longer exists. Strict servers reject the call outright, which is the good outcome. Lenient ones accept it and ignore the field, so you get a 200 and a result computed without the argument you thought you passed. The second failure is far more expensive to find.

4. Output field removed

A field you read is gone. Your code sees undefined and, if a model is downstream of it, the model papers over the hole and produces something plausible. Nothing throws, and the symptom arrives days later as output quality nobody can attribute.

5. Input field renamed

The same rename detection as tools, applied to arguments. Without it this shows up as a removal and an addition, and the fix looks like schema redesign rather than a find-and-replace.

6. Output field renamed

The read side of the same. The rarest of the thirteen in practice, because servers tend to add a replacement field alongside the old one rather than rename what consumers are already parsing, which registers as an addition and a removal instead.

7. Input field became required

An optional argument is now mandatory. Every existing caller that omits it fails on the next call, with no grace period. This is the cruelest class to ship, because the documentation usually shows a sensible default that the schema does not actually apply.

8. Input type changed

The accepted type moved under you, string to integer, scalar to array. Values you have been sending for months are now rejected, and the error rarely names the field.

9. Output type changed

The returned shape moved. Parsing that assumed a scalar meets an object, or a single item meets a list. Typed clients throw here; untyped ones carry the wrong shape further downstream before failing.

10. Enum narrowed

An open string became a closed set. Nothing was removed in the usual sense, but every value outside the new set is now invalid, and the values you send were never enumerated anywhere for the server author to check against.

11. Enum value removed

A specific member of an existing set is gone. Breaks exactly the callers that send that value and no one else, which is why severity without consumption data overstates the damage.

12. Resource removed

A resource URI your agent reads no longer resolves. Resources get less attention than tools in most reviews, and on the public monitor resource removals are the second most common breaking change.

13. Prompt removed

A named prompt your agent invokes is gone. Prompts are the part of the contract most often treated as decoration, so this is the class most likely to be waved through in review until an agent that depends on one stops working.

Risky: nothing errors, behaviour shifts

Below breaking sits a class that fails no call and still changes what your agent does. An optional field added, which models sometimes start populating unprompted. An enum value added, or a default changed, and routing and tiering drift.

And the one nobody diffs: the description rewritten. Description text is executable in the practical sense that it steers which tool the model selects and how it builds arguments. A heavy rewrite can change behaviour with a byte-identical schema. No conventional API differ looks at it; this one computes a word-level delta and flags rewrites above a threshold.

Severity is a per-consumer question

The same change is breaking for one agent and irrelevant to another. A removed field only breaks consumers that send or read it, which is why blast radius needs a manifest, a machine-generated declaration of exactly which fields each agent touches. Classification without consumption data is noise; with it, an alert names the affected agent, the field, and the file and line.

How often each one actually happens

Frequencies are measured, not estimated, and they move as the monitor observes more, so they live on one page rather than being written into this one. The current distribution across every public server under monitoring is on the MCP breaking changes page, counted per individual change.

Keep reading

Why MCP servers break agents silently (and your error tracker stays green)

How to pin MCP server schemas in CI (lockfiles for tool contracts)

Error tracking vs. contract monitoring: what each one structurally can't see