Keep your MCP tool surface stable
What we checked
We compare two captures of your server’s surface and look for destabilising changes. For a hosted endpoint that is a rolling 30-day window of daily snapshots. For a package it is the versions we captured inside that same window, because a published version cannot change once released, so what matters is what changes between them:
Why it matters
Agents bind to your tools by name and argument shape. When a tool disappears or its parameters change incompatibly, every agent and saved workflow that depended on it breaks, usually silently, mid-task. An MCP server is a contract with everyone using it, whether they connect to it or install it, so your tool surface should be treated like a public API, where a backwards-incompatible change is the kind that warrants a major version bump under semantic versioning: changes are cheap to make and expensive for everyone downstream.
Additive changes are safe; mutations and removals are not. The distinction is the whole game.
How to fix it
This is mostly process. Add new capabilities rather than mutating existing ones, and when something truly must go, deprecate it with a migration path before removing it. The example below contrasts a safe additive change with a breaking one.
// SAFE (additive): add a NEW optional parameter. Existing callers keep working.
inputSchema: {
city: z.string().describe("City name."),
units: z
.enum(["metric", "imperial"])
.optional()
.describe("Optional. Defaults to metric."),
}
// BREAKING: renaming or removing a parameter invalidates every existing call.
// city -> location // every caller passing 'city' now fails
// BREAKING: removing a whole tool. Prefer a new tool + a deprecation period.
// server.registerTool("get_weather_v2", ...) // add
// // keep get_weather working, mark it deprecated in its description, then
// // remove only after consumers have migrated. // A quick rule of thumb for any schema change:
{
"additive_safe": [
"add a new tool",
"add a new OPTIONAL parameter",
"loosen a constraint (e.g. widen an enum)",
"improve a description"
],
"breaking_avoid": [
"remove a tool",
"rename a tool or parameter",
"add a new REQUIRED parameter",
"change a parameter type",
"tighten a constraint (e.g. narrow an enum)"
]
} What counts as a breaking change?
Anything that invalidates a call an existing agent already makes: renaming or removing a tool, renaming a parameter, adding a required parameter, changing a parameter’s type, or tightening a constraint such as narrowing an enum. Additive changes are safe, so a new tool, a new optional parameter, a loosened constraint, or a clearer description all read as a steady surface. Only the breaking kind counts as churn.
On a package, only a change between versions can count as churn. If we capture the same version twice and the two disagree, we report that we could not tell what changed rather than counting it against you: either our sandbox is unreliable for your package (usually a tool list that varies with a secret or a native dependency), or you publish under a coordinate that can be re-pointed, such as an OCI tag. Two captures of one version that agree are simply stable.
How we re-check
We keep capturing your surface: on each crawl for a hosted endpoint, and on a regular refresh plus every time you publish a new version for a package. Every clean observed day adds credit on the next score refresh, and once the window contains no destabilising changes the check records a fully stable surface.
Give this to your AI
Paste this into Claude Code (or any coding agent) from inside your server's repository. It states the failing signal, the outcome we re-check for, and the format the fix has to take.
Context: this repository publishes an MCP server, either as a hosted endpoint or as a package on a registry such as npm or PyPI. VerifyMCP's stability check failed, which means a comparison of two captures of your tool surface found a destabilising change: a tool was renamed or removed, a parameter was renamed, a new required parameter appeared, a parameter's type changed, or a constraint (e.g. an enum) was tightened. For a hosted endpoint the two captures are 30 days apart. For a package they are two different published versions, so the change is what a consumer meets when they upgrade. Goal: the tool surface stays steady across the rolling 30-day window (only additive changes), so the next crawl records it as stable. Do this: 1. Find the change that caused the churn (compare the current tool definitions against the version from before the change, e.g. via git history or a diff of the registered tools). 2. If the change genuinely must happen, add it as a NEW tool or a NEW optional parameter alongside the existing one, rather than mutating what is already there. 3. Mark the old tool or parameter as deprecated in its `description`, naming the replacement, and keep it working. 4. Only remove the deprecated tool or parameter after a real migration window has passed and you have confirmed existing consumers no longer call it. Rules: - A breaking change (rename, removal, new required parameter, narrowed type or enum) is a product decision, not a formatting one: ask the human before shipping it, and check whether existing clients depend on the current shape first. - Do not "fix" churn by reverting the change and reintroducing it later under a different name; that still breaks callers mid-window. Report back: which changes were additive versus which you held back to ask about, and the current state of the deprecated tool's description. Reference: https://verifymcp.io/docs/remote/stability