DEV Community

Sam Novak
Sam Novak

Posted on

tools/list is an API contract, and you are versioning it by accident

If you run an MCP server, your tool catalog is a public API. I do not think most of us are treating it like one.

Here is the shape of the problem. A client connects, calls tools/list, gets your catalog, and starts using it. Then you rename a tool for clarity. Or you tighten a parameter schema because someone was passing nonsense. Or you split one tool into two better ones.

Every one of those is a breaking change shipped with no version number, no deprecation window, and no changelog.

Why it is worse than a normal API break

With an HTTP API, a break produces a 404 or a 400 and somebody's error tracker lights up. The failure is loud and it points at you.

With a tool catalog, the consumer is a language model. It does not throw. It adapts. Give it a tool that no longer exists and it will try a plausible neighbour, or invent arguments that match the old schema, or quietly do something adjacent and report success. The failure surfaces as a weird outcome three steps later, in someone else's log, attributed to the model being unreliable.

That is a genuinely bad debugging situation, and the person who has to debug it is not you.

Rules I would follow

Tool names are permanent. Pick badly and live with it. A slightly awkward name that has been stable for six months is worth more than a clean rename. If you must rename, keep the old name working and have it do the same thing.

Additive changes only, on parameters. New optional parameters are fine. Making an optional parameter required is a break. Narrowing an enum is a break. Renaming a field is a break. Tightening validation on a field that previously accepted sloppy input is a break, and it is the one people do not notice they are doing.

A removed tool should fail loudly, not vanish. If something has to go, leaving it in the catalog with a description that says it is removed and an error that says what to use instead is far kinder than deleting it. The model will read the error. It cannot read your absence.

Say so if your catalog is dynamic. This is the one I would most want documented. If the tools a client sees depend on its permissions, its connection, or the team it is pinned to, then no client can safely cache the catalog once and reuse it. That is a legitimate design - it is how you keep a connection from becoming a second, broader authority than the person who created it - but it has to be stated, because the default client assumption is that a catalog is stable.

Make the catalog itself authoritative. Documentation drifts. If your docs list tool names and your server also lists them, one of the two will be wrong within a quarter. Say plainly which one wins.

The read-before-write pattern

The corollary on the client side, which I have come round to: for anything with an effect, read current state immediately before acting, in the same run. Not at the start of the session - immediately before.

This is not paranoia about your server. It is that the gap between "agent decided to do X" and "agent does X" can contain a human doing something else entirely, and a stale read makes the agent confidently overwrite it. A revision or version check on write is the cheap version of the same protection.

Wagglet's MCP documentation is a decent worked example of these constraints being written down rather than left implied - a connection that is named and revocable and pinned to one person and one team, an explicit statement that the connection never becomes a second authority, and reading current state before an explicit action. Whether or not you use it, the doc is a reasonable checklist of the things a server author should be deciding on purpose.

The short version: your tool names are your API surface, your clients cannot see your git history, and your consumers do not raise exceptions. Version accordingly.

Top comments (0)