BlogQuality

What to test before an MCP server reaches users

A demo shows that the happy path works with one client. Before users connect, test what the demo skips: the tool contract that clients depend on, requests that must be refused, failures in the systems behind the server, and writes that could happen twice or without approval. This plan takes them in that order and ends with the checks we would run in CI.

Updated

What a working demo does not prove

A demo can pass with one user, generous credentials, healthy APIs and polite inputs. Each of those conditions hides a class of failure. Write the tests below against a staging server with test identities: at least two ordinary users with different rights, one user with no access, and a client registered the way production clients will be.

Contract tests: the tool list is an interface

Clients, prompts and evaluation sets depend on tool names, descriptions and schemas, so a silent change is a breaking change. The current specification lets the tool list vary by the authorisation on the request, which means you snapshot it per test identity.

  • tools/list for each identity matches the reviewed snapshot, and any difference needs an approved change
  • Every structured result validates against its output schema, which the specification requires when one is declared
  • Wrong types, unexpected fields and out-of-range values are rejected with a message that names the problem

Negative authorisation tests

For a server protected with the HTTP authorization flow, each case should assert the refusal, the absence of side effects and the absence of data in the error:

  • No token, or an expired one: HTTP 401 with a WWW-Authenticate header pointing to the resource metadata
  • A correctly signed token issued for another audience: rejected, because the server must only accept tokens meant for itself
  • A valid token without the needed scope: 403 with an insufficient_scope challenge, if your server and clients implement the recommended step-up
  • User A asking for user B's record through a valid tool: refused, with no record content in the message
  • A state handle created by user A and replayed by user B: refused, since a handle must not count as authentication
  • A write without a matching approval, or with a payload changed after approval: refused
  • Downstream request logs in staging identify the server's own test identity, by client ID, audience or key fingerprint, and never contain the inbound token or any credential value

Failures in the systems behind the server

MCP separates protocol errors, such as an unknown tool or a malformed request, from tool execution errors returned with isError set to true. Tests should confirm that each failure lands in the right category. Inject faults in staging: downstream timeouts, rate-limit responses, server errors, truncated payloads and responses slower than the client's timeout. For each one, the tool should return an execution error the model can act on, with no stack trace, internal hostname or token in the text. Also confirm that the server's own rate limits, which the specification requires, actually trigger.

State-change tests for every write

  1. 1

    Repeat

    Send the same confirming call twice and expect one record. Then make the downstream write succeed but time out the response, retry, and still expect one record.

  2. 2

    Interrupt

    Fail a multi-step write halfway. Where the downstream writes cannot be atomic, the server keeps durable partial state and a compensation or reconciliation step completes or reverses the work. The tool result says which state the case is in.

  3. 3

    Abandon

    Prepare a change and never confirm it: nothing changes. Confirm after the handle expires: an execution error explains that a new proposal is needed.

  4. 4

    Collide

    Change the record in the business system between prepare and confirm. The tool reports a conflict instead of overwriting.

Run these against a sandbox of the business system with synthetic records, never against production.

Running the plan in CI and before client upgrades

The MCP Inspector has a command-line mode for scripts with documented exit codes: 3 when the server requires authentication, 5 when a tool call returns isError or the tool is missing, and 6 when the strict check finds a schema portability problem. Because exit 5 covers both a rule denial and a missing tool, the example checks that the tool exists and that the error carries the expected message. The stored-auth-only flag makes a CI run fail at once instead of waiting for a browser login. The URL, tools and message are hypothetical.

Hypothetical CI checks with the MCP Inspector CLI
set -euo pipefail
SERVER="https://mcp.staging.internal/mcp"
inspect() { npx @modelcontextprotocol/inspector --cli "$SERVER" --transport http --stored-auth-only "$@"; }

# Contract: both tools are listed, and schemas pass the strict check
inspect --method tools/list --format json \
  | jq -e '.result.tools | map(.name) | (index("find_orders") != null and index("prepare_return") != null)' > /dev/null
inspect --method tools/list --strict > /dev/null

# Business rule: a return outside the policy window comes back as a tool error (exit 5)
# with the expected rule message, not merely as a missing tool
set +e
out=$(inspect --method tools/call --tool-name prepare_return --tool-arg order_number=SO-00000001 --format json)
code=$?
set -e
test "$code" -eq 5
echo "$out" | jq -e '.result.isError == true and (.result.content[0].text | test("return window"))' > /dev/null

The Inspector covers protocol behaviour. Identity, approval and state-change cases, including the check that a refused call created nothing in the sandbox, usually need your own harness that switches test users and inspects the business system. Run the full plan again when the protocol revision, SDK or a supported client changes; the 2026-07-28 revision, for example, removed protocol sessions, which breaks tests that assumed them.

Questions

Can we run these tests against production data?

Avoid it. Permission tests deliberately try to show records to the wrong user, and write tests create and repeat changes. Use a sandbox with synthetic records that mirror the structure of real ones.

Do we need to test each AI client separately?

For the clients you support, yes. Clients implement different parts and revisions of the protocol, including authorisation. Keep a short smoke set per named client and run it after each client update, alongside the protocol-level plan.

Who should own these tests after launch?

The team that owns the server, with the owner of each business system reviewing the permission cases for their system. A test that nobody may change without that review is how the permission matrix stays true.

Talk to an engineer

Bring one piece of work you would like to improve. The call takes 30 minutes and needs no access to your systems or your data.

Booking runs on Google Calendar.

Prefer to write first? Leave your email and a few lines, and we reply with times.

The button prepares an email in your own email program, for you to send. Privacy policy (draft)