Docs As Code

Testing and Validating MCP Tool Outputs from Docs

Senior Writer · · 9 min read
Cover illustration for “Testing and Validating MCP Tool Outputs from Docs”
Turning Docs into MCP Servers · July 28, 2026 · 9 min read · 2,106 words

The June 18, 2025 revision of the MCP specification introduced structured tool outputs as a first-class concept, and the specific language it used matters more than the feature itself. Before that revision, tool results lived in a content array: free-form text, images, audio, resource links, whatever the server chose to return. The spec described the shape but imposed no machine-verifiable contract.

The 2025-06-18 revision changed that in a precise way. When a tool declares an outputSchema, the server MUST return a structuredContent field conforming to it. Clients SHOULD validate those structured results against the declared schema. That asymmetry, MUST on the server side and SHOULD on the client side, is not a drafting oversight; it is an acknowledgment that client implementations vary and that the spec defines obligations without supplying a mechanism to enforce them.

A backwards-compatibility rule sits underneath this: tools returning structuredContent should also serialize it into a TextContent block so that clients that have not yet adopted the structured output path continue to function. Sensible for adoption; it also quietly creates a problem most teams do not notice until they are already downstream of it. A tool can appear to work because the TextContent block is present and the agent receives something, while the structuredContent is absent or malformed. The surface appearance of success masks a real conformance failure.

What the spec does not resolve is enforcement. No built-in mechanism forces a client to reject a non-conforming response. The MUST obligation sits on the server; the SHOULD obligation sits on the client; between them is a gap that most of the ecosystem's validation problems flow through. This is, I think, less a flaw than an acknowledgment of how open standards actually propagate. But understanding that gap is the prerequisite for understanding why treating validation as a finishing step is a structural mistake with this protocol, not just a bad habit.

Venn diagram: MCP Tool Validation: Server vs. Client Obligations. Compares Server Obligations and Client Obligations; overlap: Shared Gap.

Where Doc-Defined Tools Actually Break: The Gap Between Declared Schemas and Live Behavior

The most common source of silent failure is almost embarrassingly straightforward: outputSchema is not declared because the application appears to work without it. When everything downstream is consuming the TextContent fallback, there is no immediate pressure to write the schema. The bug surfaces when a client tries to render structured output or route a decision on a field that was not formally guaranteed. By that point, the assumption has been baked into multiple layers of the system, and unwinding it is expensive.

A bug reported against IBM's MCP Context Forge documented what "silent pass" actually looks like in practice. A gateway treated "no structured data found" as a valid response state, returning is_error=False while leaving the declared outputSchema unsatisfied, in direct violation of the spec. The MCP Python SDK's server-side validator does surface this condition; it emits an output validation error when outputSchema is defined but no structured output is returned. The gateway layer swallowed it anyway. The SDK caught the violation; a layer above simply chose not to act on it. That sequence is the structural argument for why validation at a single point is insufficient: the failure passed through a correct check because another layer ignored the result.

Cross-client schema inconsistency compounds this in ways that are irritating to debug. Optional fields and $ref combinations that pass cleanly in Claude Desktop can hard-fail in Cursor or VS Code Copilot. Azure AI Foundry, for instance, does not support certain JSON Schema validation keywords, including anyOf. A schema that is technically valid and correctly declared can still cause tool invocation failure depending on which client is running it. You find this out in production if you haven't tested across clients, and the failure mode is not loud about announcing itself.

Tools generated from documentation content face a particular version of this problem. The outputSchema must reflect the actual data structure of the content being returned, not merely the API surface described in the docs. Docs drift. The schema must track live behavior, not the original design document, and keeping those two things synchronized requires more active discipline than most teams initially anticipate. That last part took me longer to accept than it should have.

Why Tool Descriptions Are a Validation Surface, Not Just Documentation

The MCP spec requires three fields per tool: name, description, and inputSchema. The description field is mandatory, and it is not cosmetic. It functions in practice as the instruction manual the model consults when deciding whether and how to invoke a tool. If the description is wrong or vague, the model routes incorrectly, invokes the tool under the wrong conditions, or fails to invoke it at all.

A 2025 study analyzing MCP tool descriptions found that 97.1% contain at least one quality issue, and more than half have unclear purpose statements. Tools with complete, precise descriptions have three to four times fewer failed invocations than tools with one-line descriptions. This is not a style problem. It affects latency, token cost, and correctness in ways that compound across a system with many tools.

The difference between a testable description and an untestable one is whether the model can route without guessing. "Query data" is not a description; it is a placeholder. "Search the PostgreSQL database for customer records matching a name, email, or account ID" tells the model exactly what the tool does, when it applies, and what inputs it expects. That specificity is what enables correct invocation, and correct invocation is what makes output validation meaningful at all. You cannot sensibly validate the output of a tool that was invoked for the wrong reason.

Schema complexity is a parallel dimension of the same problem. Deeply nested structures using oneOf or allOf increase token count and introduce parsing ambiguity. Keeping schemas flat is a testability practice as much as a stylistic one. Complexity that cannot be clearly described cannot be reliably invoked. Browser-based validators like MCPServerSpot Schema Validator flag vague descriptions as severity-rated findings alongside missing input validation; that classification is right, because description quality is a conformance issue, not a documentation nicety.

The Layered Testing Approach: From Unit Tests to Protocol Conformance

Diagram: The Five Layers of MCP Validation. Visualizes: Visualize a five-layer stack showing the distinct validation layers described in the article, each catching failures the others cannot.

The instinct to validate only at the protocol boundary is understandable, and it is also insufficient. A complete validation strategy requires distinct layers, each catching a class of failure the others cannot.

Layer 1: Unit Tests on Tool Handler Logic

Test the underlying business logic independently of the MCP transport. Schema constraints, type safety, and error message behavior should be validated using Zod for TypeScript or Pydantic for Python before the tool ever touches a live connection. Treat the MCP server as a testable service from the moment the first tool exists. Waiting until the server is "feature complete" to start validating contract behavior means deferring discoveries that are cheap to fix early and expensive to fix late.

Layer 2: Output Schema Conformance on Every Tool Call

If a tool declares an outputSchema, every result must be validated against it, not just during initial development. Validation should occur twice in a healthy system: server-side before the response is sent, and client-side before the result is consumed. Absence of structuredContent when an outputSchema is declared is a conformance failure even when is_error=False. That specific condition, the silent false-success, causes the most downstream damage because nothing in the call stack raises an exception; the agent just quietly develops a corrupted understanding of what happened.

Layer 3: Schema Drift Detection in CI

Schema drift is what happens when declared tool schemas diverge from actual implementation as a server iterates. It is quiet and cumulative, and it surfaces in production rather than in development. Running schema validation in CI catches contract drift before it corrupts LLM tool routing. Specmatic's MCP Auto-Test tool, released in November 2025, positions itself specifically as a schema drift detector for MCP servers. Whether using that tool or a custom harness, treating the host-loaded MCP state as one of three conditions, MATCH, DRIFT, or UNVERIFIABLE, is a useful forcing function. Anything that is not MATCH requires action before deployment.

Layer 4: Protocol Conformance and Version Matrix Testing

MCP versions should be treated the way API versions are treated in production systems. The official @modelcontextprotocol/conformance package enables filtering conformance scenarios by spec version, which matters because the versioning boundary is not merely additive. Dated versions through 2025-11-25 use a stateful lifecycle requiring an initialize handshake; the 2026 draft shifts to a stateless per-request lifecycle. A client and server negotiating different versions can produce a degraded capability set without either side throwing an error. Compatibility gates in CI/CD that block rollout on failed version negotiation are the mechanism that keeps version complexity from becoming a production incident.

Layer 5: Load and Stress Testing

Latency spikes under concurrent load are a production failure mode that schema tests cannot catch. mcpdrill addresses load testing; peta handles governance and control-plane validation covering identity, policy, and secret handling. A tool that passes schema conformance under a single-user test can exhibit substantially different behavior under realistic concurrent load, and the difference is not reliably predictable from first principles.

Using MCP Inspector as the Primary Protocol-Level Validation Tool

MCP Inspector is an interactive developer tool comprising a React-based web UI and a Node.js proxy that bridges the UI to servers over stdio, SSE, or streamable-HTTP transports. Its protocol validation is automatic: when a server returns a tool schema with missing required fields, or when response formatting deviates from the spec, Inspector flags it with an explanatory error message. That immediate, specific feedback makes it the fastest path to identifying conformance failures during development, and it tends to surface issues that schema validators alone miss because it operates at the transport level.

CLI mode enables CI integration. Running npx @modelcontextprotocol/inspector --cli node build/index.js --method tools/list validates a server's tool inventory without a browser. Adding --tool-name and --tool-arg flags enables targeted validation of specific tool responses. Exit codes signal pass and fail states that CI runners can consume directly, which means Inspector can sit in a pipeline the way a linter does, without requiring any architectural accommodation.

Interactive mode serves a different purpose: manual cross-client comparison. Connecting to a server, listing its tools, invoking them with specific arguments, and inspecting the raw structuredContent alongside the TextContent fallback reveals discrepancies that automated tests sometimes normalize around. Seeing both representations side-by-side is where the backwards-compatibility dual-return problem tends to become suddenly, viscerally obvious. I have seen engineers spend hours chasing a bug in their business logic who, the moment they looked at that view, stopped, went quiet, and immediately saw what was actually wrong. The issue was never in the logic.

One security note that warrants immediate attention: CVE-2025-49596, addressed in Inspector version 0.14.1 released June 13, 2025, covered CSRF and DNS rebinding vulnerabilities. The fix introduced session tokens and Host/Origin header validation; any deployment running an earlier version should update. This is not theoretical for a tool that bridges arbitrary server connections over local network interfaces.

Security Failure Modes That Output Validation Must Cover: Tool Poisoning and Silent Redefinition

The conversation about MCP output validation tends to stay anchored to correctness, which is where it should start. That focus should not be where it ends.

Tool poisoning is the practice of embedding malicious instructions in tool metadata, specifically in descriptions and parameters, rather than in user inputs. It exploits the client-server trust model: clients receive tool definitions that appear as legitimate configuration and pass them, without sanitization, into the model's context. CVE-2025-54136 (MCPoison) and CVE-2025-54135 (CurXecute) formalized this category. An attacker with control over an MCP server can write directives into descriptors that the agent passes to its model, with no provenance check and effectively full ambient authority over the agent's context window. These are assigned CVEs with documented exploitation paths.

The "rug pull" or silent redefinition attack is the adversarial form of schema drift. A tool approved as safe on installation can quietly reroute API keys or alter its own behavior after the fact. The same MATCH/DRIFT/UNVERIFIABLE framing used for accidental drift applies here, with one additional variable: drift may be intentional. If a server changes tool descriptions, schemas, or authentication behavior after an agent has integrated it, that change should trigger a new approval or verification event. Whether the mutation is accidental or deliberate, the detection mechanism is the same.

The practical implication is that output validation and description validation are not separate from security posture. They are the detection mechanism. Documentation that stays synchronized with live tool behavior is the baseline that makes drift visible. Without that baseline, neither accidental divergence nor deliberate manipulation is reliably detectable, and at that point the question of which one you are dealing with stops mattering.

Sources

  1. modelcontextprotocol.io

More in Turning Docs into MCP Servers