Editorial illustration. It explains the concept; it is not experimental evidence.
An MCP server can pass a happy-path demo and still ship a contract change that makes an agent less predictable. A tool name gains a space. An inputSchema disappears during a refactor. A generated list comes back in a different order than the build you reviewed.
The hard part is not writing another unit test. It is deciding what must stay stable before an agent is allowed to discover tools that can touch a real system.
My recommendation is small: validate each tool declaration structurally, then keep a reviewed snapshot of the ordered tool surface. The snapshot is not a substitute for integration tests or human approval. It is a cheap tripwire for a class of deployment drift that ordinary handler tests often miss.
The current MCP tools specification says a tool has a unique name and an inputSchema, and it recommends a deterministic tool order so clients can cache tool lists reliably. It also calls for a human to be able to deny tool invocations. MCP Tools specification
The gate I would put before deployment
Run this after generating or assembling your server's tools/list output, not only against TypeScript types. It checks the payload an MCP client will actually see.
type Tool = {
name: string;
inputSchema?: { type?: string } | null;
};
const toolName = /^[A-Za-z0-9_.-]{1,128}$/;
export function assertToolContract(tools: Tool[], approvedNames: string[]) {
for (const tool of tools) {
if (!toolName.test(tool.name)) {
throw new Error(`Invalid MCP tool name: ${tool.name}`);
}
if (!tool.inputSchema || tool.inputSchema.type !== "object") {
throw new Error(`Tool ${tool.name} needs an object-root inputSchema`);
}
}
const names = tools.map((tool) => tool.name);
if (JSON.stringify(names) !== JSON.stringify(approvedNames)) {
throw new Error("Tool surface changed: review the ordered tools/list snapshot");
}
}
This deliberately does not decide whether a tool is safe. It only makes a declared interface change visible. Authorization, output validation, test data, rate limits, and human approval still need their own controls.
A controlled drift study
I generated 240 synthetic three-tool server surfaces, then applied five deliberate mutations to each one: a name containing a space, a missing schema, a null schema, an array-root schema, and a changed tool order. That produced 1,200 injected contract changes.
The study compares two checks:
| Check | Caught | Missed | What it misses |
|---|---|---|---|
| Basic name + schema check | 960 / 1,200 (80%) | 240 | Order-only drift |
| Basic check + ordered snapshot | 1,200 / 1,200 (100%) | 0 in this study | Semantic or runtime problems |
Evidence image generated from the saved deterministic study output. It is a synthetic mutation study, not production telemetry.
The result is not surprising once you define the test: the basic check was not designed to flag order changes. That is exactly the point. If order matters to your clients, deployment review needs to compare it. If it does not, omit that rule and say so explicitly.
The complete method, seed, rows, and limitations are included with this draft in research/mcp-contract-drift-2026-08-22/. Rerun it with:
node scripts/run-mcp-contract-drift-study.mjs
Why an ordered snapshot can be useful
MCP does not turn list order into a universal correctness rule. A client can choose its own behavior. But deterministic declarations reduce accidental churn in a few practical places:
- build reviews become smaller because a real surface change is visible;
- cached prompts or tool registries do not receive a different ordering for the same server build;
- a generated tool list cannot quietly add, remove, or rename an operation without a reviewer seeing the diff.
The specification explicitly recommends deterministic ordering when the underlying set has not changed. Treat that as an operational affordance, not a security guarantee. MCP Tools specification
Keep structural drift separate from behavior drift
One common mistake is to call all of this “MCP testing.” It is only one layer.
| Layer | Question | Example evidence |
|---|---|---|
| Contract | Did the client-facing declaration change? |
tools/list snapshot and schema check |
| Handler | Does the tool reject invalid input safely? | unit tests with invalid payloads |
| Integration | Does the server negotiate and serve the intended tool? | protocol session test |
| Authorization | Can the caller perform only allowed actions? | scope and approval tests |
| Agent behavior | Does a model choose and interpret the tool correctly? | scoped evaluation set |
The July 2026 MCP release candidate also describes the move to full JSON Schema 2020-12 for tool input and output schemas. That raises the value of testing the serialized declaration rather than assuming a source type tells the whole story. MCP release-candidate notes
Exact prompt, input, and observed output
For the review step, I use this prompt with a bounded task:
Given this
tools/listJSON and the approved ordered-name snapshot, identify only contract drift. ReturnPASSor a list of changed names, missing schemas, invalid root types, and order changes. Do not recommend calling any tool.
Input: the candidate tools/list payload plus approvedNames from version control.
Observed output from the deterministic run: the structural checker detected 960 of 1,200 mutations; adding the ordered-name snapshot detected 1,200 of 1,200. The agent prompt is a review aid, not a source of truth—the code gate remains authoritative.
Where this approach fails
The study is intentionally narrow. It does not prove that a tool produces the right answer, that a client understands a complex schema, or that authorization is correct. It does not use live servers or real-model task completion. It also treats order drift as relevant by design; a team whose client does not care about order should not manufacture noise by enforcing it.
The more important failure mode is social: a green contract gate can create confidence that nobody has reviewed the action itself. Keep the human approval boundary close to consequential tool calls, as the protocol’s interaction guidance recommends.
Start with the tool surface you already have. Save one reviewed snapshot. Fail the build on unexpected drift. Then add behavior and authorization evaluations where the tool can change customer data, spend money, or ship code.
Disclosure: Human strategy, research design, code review, and editorial judgment led this article. AI assisted drafting and editing.

Top comments (0)