Back to Blog
testingaimcpreliability

Testing an AI app before you ship it

Jeff Cameron

Part 6 of 6 in Beyond the Demo.

The assistant says it published the release.

There are at least three things worth checking: whether the intended content is public, whether it was authorized, and whether it was published only as many times as intended.

A fluent success message proves none of them.

Testing an AI application becomes more manageable when you stop asking one evaluation to answer every question. Protocol compatibility, application correctness, and model quality are related, but they fail in different ways and need different evidence.

Here is a release gate built around that distinction.

Write the claim before the test

Use Release Desk, an illustrative application for drafting and publishing release notes.

“Works with AI” is not a testable claim. “An authorized owner can publish the exact approved release version through a supported MCP client, and recover the result after losing the response” is much closer.

Break that into observable commitments. The correct record becomes public. The approved version is preserved. The publication receipt can be retrieved. A repeated request does not produce an extra logical publication. A caller without the right authority cannot perform the same transition.

Now define where the evidence lives. Read committed application state for publication status. Inspect the destination for external effects. Use the transcript to understand how the assistant got there, not as the sole source of truth about what happened.

Anthropic's guide to agent evaluations makes a useful distinction between a trial's transcript and its outcome in the environment. Apply it literally: “I published it” is an output; a correctly authorized publication is an outcome.

Gate one: can the client actually use the capability?

Start with the interface. Can the intended client authenticate, discover the relevant tools, call them, and interpret their results? Do declared resources and other capabilities actually work? Does an unsupported UI extension have a safe fallback?

OpZero's earlier MCP compatibility field report describes an internal suite passing while the independent MCP Inspector rejected a server that advertised logging without implementing the method the Inspector called. The lesson is not that one client proves universal compatibility. It is that your own probe can share your implementation's blind spots.

Record the client, version, protocol revision, extensions, authentication mode, and test date. The 2026-07-28 MCP specification includes a stateless core and optional extensions; that does not prove every deployed client or server supports the same combination.

Keep protocol requirements separate from observed client workarounds. An unexplained exception tends to become permanent infrastructure.

Passing this gate establishes that the interface is usable under the tested conditions. It does not establish that the application performs the correct work.

Gate two: does the runtime preserve the rules without a model?

Remove the model from the next set of tests.

Call the application operations directly with controlled identities, known records, and a fake external destination. The destination should let the harness observe accepted effects and inject failures at precise points.

For Release Desk, a compact scenario set looks like this:

ScenarioDeliberate disruptionRequired observation
Lost responseCommit locally, then drop the responseStatus lookup finds the original publication
Duplicate requestRepeat the same logical actionNo additional logical publication
Changed intentReuse a retry key with a different payloadExplicit mismatch; no extra effect
Stale approvalEdit the draft after approvalNo unapproved version becomes public
Wrong authorityUse another project's valid credentialNo cross-project read or mutation
Concurrent attemptsRun two workers against one actionCoordination and effect semantics match the declared contract
RevocationRevoke the grant before a queued dispatchNo new dispatch after the enforcement check denies it
Unknown outcomeDestination accepts, then connection disappearsReconciliation or explicit unknown state, not blind success

These are proposed tests, not results from an OpZero benchmark.

For a destination without idempotency or authoritative reconciliation, change the expected behavior accordingly. A correct test may require the action to pause in an unknown state. Do not write a test that demands a guarantee the integration cannot provide.

Restart the process during recovery tests. Testing an in-memory object twice does not demonstrate persistence across failure.

Make at least one test prove that it can fail

A test suite that has only seen working implementations has not shown whether its important assertions detect anything.

Deliberately remove a version check. Make the fake provider ignore the retry key. Let the mutation occur before authorization. The appropriate tests should fail for the intended reason.

These mutations belong in an isolated fixture or test environment, not in production. Their purpose is to calibrate the detector.

Also separate fixture failure from application failure. A fake provider that never records requests cannot prove that the application prevented duplicate effects. A broken authorization stub cannot prove that tenant isolation holds.

You are testing the evidence-producing machinery as well as the application.

Gate three: can the model use the interface well?

Only after the runtime's rules hold should the model be asked to complete the workflow.

Use representative requests, not just the prompt that produced the nicest demonstration. Ask for an ambiguous release. Refer to a draft by an outdated name. Provide irrelevant changes. Ask to publish without the required approval. Include content that tries to instruct the assistant to bypass policy.

Keep the exact deterministic assertions from the previous gate. Add model-specific grading for whether it finds the right records, asks a necessary question, prepares useful content, and accurately describes the outcome.

A refusal can be the correct result. So can asking the user to choose between two genuinely ambiguous records. Do not reward a confident mutation merely because it finishes the conversation faster.

For writing quality, use a clear rubric and human calibration rather than pretending a string match measures usefulness. For published version, destination, and effect count, use direct checks instead of a model judge.

Run multiple trials when evaluating model behavior. Report the number of tasks and trials, configuration, and failure categories. A single successful run is a useful example, not a reliability rate.

Do not average away a boundary violation

It is tempting to combine every score into one percentage.

For this application, that would hide an important distinction. A weak summary and an unauthorized publication are not interchangeable failures. Better prose must not compensate for crossing a permission boundary.

Make deterministic safety invariants hard release gates for the tested scenarios. Track model quality separately, with thresholds appropriate to the task. Report latency, tool calls, and usage as operational metrics rather than automatically treating the cheapest run as the best one.

A compact report can say:

Build and configuration: recorded
Protocol/client checks: pass, fail, or explicitly unsupported
Application invariants: pass/fail by scenario
Model task outcomes: counts by outcome and failure category
Writing quality: rubric results with review notes
Operational metrics: latency, usage, calls, recovery attempts
Known gaps: named and scoped

Do not fill in green statuses without running the checks. “Not tested” is useful information.

Preserve a small evidence bundle

For each test run, keep the versioned fixture, initial state, application build reference, model configuration when relevant, tool requests and responses, final state, and destination receipts. Record injected failures and the assertion results.

Redact secrets and minimize personal content. An evidence bundle is not a reason to copy every credential and document into logs. It should explain the result without creating a second uncontrolled data store.

Replay against a fake or isolated destination. Replaying a trace should not silently resend production announcements.

This makes a failure actionable. “The agent behaved strangely” becomes “the second attempt created a new action ID after the first provider response was lost.” One is an impression. The other points to a repair.

The release gate is a product decision

Not every small app needs every test in this article. A read-only reference tool has different risks from a service that publishes externally.

Start with its promises and consequences. Choose the tests that can disprove those promises. Expand the gate when you add shared data, authority, asynchronous work, or external effects.

OpZero's existing interoperability work addresses an important part of the boundary. Your deployed application still needs evidence about its own domain behavior.

The goal is not an impressive test-count badge. It is a system whose claims get narrower and more defensible as you inspect it.

A demo shows that a path exists. A release gate shows which paths you have tested, what they preserve, and where the uncertainty remains.