A server upgrade should not turn yesterday’s working tool call into today’s debugging session. Before changing a dependency, save the interface your workflow depends on and test it against the candidate release.

An MCP contract test checks that a server still exposes the expected tools and accepts representative calls with the expected results. It does not ask a model to decide whether an answer looks right. Keep that evaluation separate so a failure points to a specific interface change.

If the server does not connect yet, start with our local testing and debugging guide. This guide is for the next step: catching regressions after the first successful connection.

1. Define the upgrade boundary

Write down the server release, client or SDK version, transport, test identity, and enabled features. Keep the test identity’s permissions fixed between runs. Otherwise, you may mistake a changed credential or feature flag for a changed server.

Start with one workflow, not every tool in the directory. For a repository agent, that might mean reading an issue and retrieving a file. For a browser agent, use a disposable local page rather than a production checkout.

Use the same settings for the baseline and candidate. Change one version at a time.

2. Capture the advertised contract

MCP clients discover tool definitions through tools/list. In the November 2025 protocol revision, a definition includes an input schema and may also advertise an output schema and annotations. Listing supports pagination, so a custom snapshot collector must follow continuation cursors rather than assuming the first page is complete.1

The Inspector CLI provides a convenient starting point for a local Node server:

npx @modelcontextprotocol/inspector --cli node build/index.js \
  --method tools/list --format json > tools-baseline.json

Replace build/index.js with your built server entry point. The current CLI documents JSON output as an object containing result; inspect that envelope before writing a parser.2 Treat this command as a discovery probe, not a complete regression suite. For repeatable CI, pin a reviewed Inspector version in your development dependencies and lockfile rather than resolving an unspecified release on every run.

Build your reviewed snapshot from the fields you use:

  • Tool name and description.
  • Input properties, required fields, defaults, and constraints.
  • Output schema, when present.
  • Annotations your host uses when presenting or restricting calls.

Sort tools by name and object keys before comparing snapshots. Preserve meaningful descriptions and schema constraints. Removing them to make a diff smaller defeats the test.

3. Turn changes into review decisions

Compare the candidate inventory with the approved baseline. Flag removed tools, renamed arguments, new required fields, narrower accepted values, and changed output types.

Not every difference is a breaking change. An added optional argument may be compatible with your callers. A description edit may preserve the wire format but change how an agent selects the tool. Review those changes rather than automatically accepting or rejecting every diff.

Do not regenerate and approve the baseline in the same unattended job that detects the change. A snapshot only helps when someone reviews what changed.

For servers that vary tools by role, maintain a separate baseline for each supported test identity. One administrator snapshot is not evidence that a read-only account sees the right interface.

4. Add deterministic call fixtures

A schema snapshot cannot prove behavior. Add small fixtures against a seeded test dataset. Here is a proposed fixture format for your own test harness, not an Inspector configuration:

{
  "tool": "lookup_record",
  "arguments": {"record_id": "fixture-001"},
  "expect": {
    "isError": false,
    "structuredContent": {"record_id": "fixture-001", "status": "open"}
  }
}

lookup_record is an illustrative tool, not a tool supplied by MCP. Implement the harness around your server’s actual names and output structure.

For each important operation, cover a valid call, a missing required argument, an unknown record, and an unauthorized record. Assert stable business fields. Avoid exact comparisons of timestamps, generated IDs, or prose unless those values are part of the contract.

Use an SDK client or Inspector to establish the connection. Do not send an isolated tools/call message to a fresh process and call that an integration test. MCP initialization negotiates capabilities and protocol compatibility before normal operations.3

5. Test errors and side effects separately

Do not equate a completed request with a successful operation. The TypeScript SDK documents tool-level failures through result.isError, while protocol and SDK failures use separate exception paths. It also validates structured output when the tool advertises an output schema.4

Your harness should distinguish those outcomes. An expected “record not found” result should not pass merely because the transport stayed connected. Likewise, a connection timeout is not evidence that the server correctly rejected an invalid argument.

For write tools, use a disposable environment and verify the resulting state. A permission-denial test should confirm that no record changed. A successful creation test should read back the created fixture and clean it up through an approved test procedure. Our retry-safety guide covers duplicate-write concerns.

FAQ

Does a passing schema diff prove an upgrade is safe?

No. Treat it as one gate alongside behavior fixtures, permission tests, and a smoke test in your actual host. Keep release approval separate from snapshot generation.

Should every response match byte for byte?

Prefer assertions on stable fields and documented constraints. Reserve exact snapshots for outputs where formatting itself matters to the caller.

What belongs in CI first?

Start with a reviewed tool inventory and one deterministic read-only workflow. Add failure cases next, then isolated writes. Keep credentials and private records out of committed fixtures and captured artifacts.

Sources