{"slug": "versioning-mcp-tools-without-breaking-the-agents-that-call-them", "title": "Versioning MCP Tools Without Breaking the Agents That Call Them", "summary": "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.", "body_md": "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.\n\nThe goal is not to freeze your tools forever. It is to make change visible, additive where possible, and explicit when it cannot be.\n\nBecause 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:\n\n`status: 3` from \"shipped\" to \"returned\").\nThe following are safe and should be your default move:\n\nNotice that \"the HTTP API behind the tool stayed compatible\" does not matter if the tool description changed. The MCP surface is its own contract.\n\nWhen a tool must change incompatibly, ship the replacement before removing the old one and make the old one announce itself:\n\n```\n{\n  \"name\": \"search_orders_legacy\",\n  \"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.\",\n  \"inputSchema\": { \"type\": \"object\", \"properties\": {} }\n}\n```\n\nThree 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.\n\nKeep 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.\n\nFor a genuinely different behavior rather than a reshaped parameter, a version suffix is the clearest signal available:\n\n`create_invoice` (v1, net-30 terms implied)`create_invoice_v2` (explicit terms object, multi-currency)\nRun 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.\n\nUse the protocol's own versioning facilities for structural changes:\n\n`initialize` response so clients and logs can correlate behavior.\nA 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:\n\n```\n# Pseudocode for a CI step: generate the catalog from the merged spec\n# and compare against the main branch, allowing additions only.\nnpx @acme/oas-to-mcp build openapi.yaml --out tools/\nnode scripts/assert-no-breaking-tool-diff.js \\\n  --before main:tools/list.json \\\n  --after tools/list.json\n```\n\nThe 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.\n\nOne 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.\n\nHand-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:\n\n`deprecated: true`), and generated tool descriptions inherit the marker automatically.\nThat 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.", "url": "https://wpnews.pro/news/versioning-mcp-tools-without-breaking-the-agents-that-call-them", "canonical_source": "https://dev.to/jeff_pdc/versioning-mcp-tools-without-breaking-the-agents-that-call-them-1ihg", "published_at": "2026-10-03 23:24:38+00:00", "updated_at": "2026-10-03 23:38:00.331639+00:00", "lang": "en", "topics": ["ai-agents", "agent-protocols", "ai-tools", "developer-tools", "mlops"], "entities": ["MCP", "OpenAPI"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/versioning-mcp-tools-without-breaking-the-agents-that-call-them", "markdown": "https://wpnews.pro/news/versioning-mcp-tools-without-breaking-the-agents-that-call-them.md", "text": "https://wpnews.pro/news/versioning-mcp-tools-without-breaking-the-agents-that-call-them.txt", "jsonld": "https://wpnews.pro/news/versioning-mcp-tools-without-breaking-the-agents-that-call-them.jsonld"}}