Versioning MCP Tools Without Breaking the Agents That Call Them A developer outlines a versioning discipline for MCP tool catalogs, arguing that agents re-read tool names, descriptions, and JSON Schemas every session, so renames, tightened parameters, or changed error semantics break workflows without any deploy to bisect. The recommended approach is additive change by default, deprecation notices with concrete removal dates and proxy implementations for incompatible changes, version suffixes only for genuinely different behavior, and a CI gate that snapshots tools/list and fails the build on removed or narrowed surface area. Traditional API versioning assumes the caller is code someone wrote against a fixed contract, tested once, and deployed deliberately. MCP breaks that assumption on both ends: the caller is an agent that reads the tool catalog at the start of every session, and the "code" it writes exists only for the length of a task. Rename a tool, tighten a parameter, or change what an error means, and the breakage does not show up in any build. It shows up as an agent that suddenly cannot complete a workflow it handled last week, with no deploy to bisect. The goal is not to freeze your tools forever. It is to make change visible, additive where possible, and explicit when it cannot be. Because agents select tools by reading names, descriptions, and JSON Schemas, the surface area is wider than a typed client's. Treat the following as breaking, always: status: 3 from "shipped" to "returned" . The following are safe and should be your default move: Notice that "the HTTP API behind the tool stayed compatible" does not matter if the tool description changed. The MCP surface is its own contract. When a tool must change incompatibly, ship the replacement before removing the old one and make the old one announce itself: { "name": "search orders legacy", "description": " DEPRECATED, removal 2027-01-31 Use search orders v2, which takes date filters as a range object instead of two string fields. This tool now proxies to v2 with converted arguments.", "inputSchema": { "type": "object", "properties": {} } } Three things are happening there, and all three matter: the word DEPRECATED in the description models weight leading tokens heavily , a concrete removal date, and a proxy implementation that keeps old call patterns working during the window. Agents that already learned the old tool keep functioning; agents reading the catalog fresh learn the new one. Keep the old tool for at least one full model-memory cycle. In practice that means weeks, not days: prompts, saved instructions, and shared playbooks all contain tool names people copy and paste. For a genuinely different behavior rather than a reshaped parameter, a version suffix is the clearest signal available: create invoice v1, net-30 terms implied create invoice v2 explicit terms object, multi-currency Run both side by side during migration. Resist the urge to version everything from day one; a catalog of foo v1 , bar v1 , baz v1 teaches the model nothing about which to use. Version suffixes are for incompatibility, not for pride in iteration count. Use the protocol's own versioning facilities for structural changes: initialize response so clients and logs can correlate behavior. A tool catalog generated from an OpenAPI document gives you a diffable artifact. Snapshot tools/list and fail the build on removed or narrowed surface area, exactly as you would for an OpenAPI breaking change: Pseudocode for a CI step: generate the catalog from the merged spec and compare against the main branch, allowing additions only. npx @acme/oas-to-mcp build openapi.yaml --out tools/ node scripts/assert-no-breaking-tool-diff.js \ --before main:tools/list.json \ --after tools/list.json The same OpenAPI diff gate that protects REST consumers protects agents; the mechanics are identical to detecting breaking API changes with OpenAPI diffs in CI https://www.powerduck.com/blog/detect-breaking-api-changes-openapi-diff-ci/ . A removed operation, a tightened field, or a deleted enum value all show up as a tool catalog break before merge. One subtlety unique to agents: editing a description can change tool selection even when schemas are untouched. Rewriting "refunds a payment" to "cancels a pending payment and returns funds" changes which situations a model considers the tool appropriate for. That is usually why you are rewriting it, but treat behavioral edits to descriptions with the same review discipline as schema edits, and include them in the snapshot diff so reviewers see the change. Hand-maintained MCP servers make all of this painful because the tool catalog and the API it calls evolve independently. When the catalog is generated from the spec, a single source of truth drives both: deprecated: true , and generated tool descriptions inherit the marker automatically. That generated-server model, and why the MCP server should be treated as compiled output rather than maintained code, is laid out in the MCP server is a build artifact https://www.powerduck.com/blog/mcp-server-is-a-build-artifact/ . You can generate a versioned local server from any spec in the online demo https://www.powerduck.com/demo/ and inspect the exact tool catalog an agent would receive.