Key takeaways

  • An MCP output schema is a discovered contract. In SDK 1.30.0, Client.callTool can validate it only after tool metadata has populated the client’s validator cache.
  • Before listTools, the probe accepted {score:"ninety-nine"} even though the server would later advertise score as an integer. After listTools, the same payload raised InvalidParams.
  • The high-level McpServer validates registered output schemas server-side; the fixture used the low-level Server API specifically to model a buggy or hostile peer. This is a client sequencing result, not proof that normal McpServer handlers emit invalid output.
  • Post-call validation protects downstream consumers, not the world outside the client. In the validating run, the remote handler had already executed before the client rejected its result.
  • Make discovery, schema pinning and validator installation an explicit connection gate. Re-discover on tool-list changes, and keep authorization and side-effect controls separate from output validation.
01

The sharp question: when does an AI agent actually enforce an MCP output schema?

A tool can advertise outputSchema and return machine-readable structuredContent. That looks like a clean boundary for an agent: validate the object, then let planners, routers and application code consume typed fields instead of parsing prose. But the validator cannot appear from nowhere. The client must first learn the schema and associate it with the tool name.

My local probe found a sequencing edge in the official TypeScript SDK 1.30.0. A direct call to risk_score returned {score:"ninety-nine"} successfully. The server’s advertised contract required an integer between 0 and 100. After the client called listTools, the same tool result raised MCP InvalidParams because data/score must be integer. The payload did not change. The client’s metadata state did.

02

Chronology: portable tools acquired portable structured contracts

Anthropic introduced MCP on November 25, 2024 as an open standard for connecting AI assistants to data sources, repositories and business tools. Tool discovery gave clients a portable way to learn names, descriptions and input contracts before a model proposed an invocation.

Later protocol versions added structured tool results and optional output schemas so a tool can describe a JSON-shaped result alongside human-readable content. The current July 28, 2026 tools contract says structuredContent must conform to outputSchema when one is defined. TypeScript SDK 1.30.0, released July 27, implements both high-level server validation and client-side output validators. The unresolved operational question is not whether validation exists; it is whether a client has installed the relevant validator before every call path.

03

The protocol has two layers of shape

CallToolResult defines the envelope. Its structuredContent field, when present, is a JSON object. That generic result parser can reject a scalar in the wrong place, but it does not know that risk_score requires an integer property named score. The tool-specific rule lives in Tool.outputSchema, learned through discovery.

That separation is sensible: one response type serves many tools. It also means “the response passed the MCP result schema” and “the response matched this tool’s output schema” are different claims. Agent infrastructure should log and test both instead of treating protocol parsing as semantic validation.

04

The fixture promised an integer and returned a word

The checked-in audit creates one low-level SDK Server with a synthetic risk_score tool. Its input is an empty object. Its output schema requires exactly one score property, typed as an integer with minimum 0 and maximum 100; additional properties are forbidden. The call handler deliberately returns {score:"ninety-nine"} plus fixed text.

This is intentionally nonconforming. I used the low-level Server API because the high-level McpServer validates registered tool output before sending it. A production peer could become nonconforming through a bug, version mismatch, custom implementation or hostile behavior. The fixture models the receiving client’s response, not the expected behavior of a correctly configured high-level SDK server.

05

Before discovery, callTool had no tool-specific validator

The client connected over the SDK’s linked InMemoryTransport and immediately called risk_score without first listing tools. The handler ran once. Client.callTool returned the invalid structured object unchanged, and getToolOutputValidator("risk_score") returned undefined. Assertions confirmed score remained the string “ninety-nine.”

Nothing in that direct call supplied an output schema. The generic CallToolResult parser accepted the object because structuredContent was an object. This is the important boundary: validation cannot enforce a contract the calling path has not loaded.

06

listTools installed the missing contract

The audit then called listTools. The discovered definition contained the integer schema exactly as declared. SDK 1.30.0’s cacheToolMetadata method clears its known output validators and compiles one for each discovered tool that has outputSchema. The probe confirmed that getToolOutputValidator now returned a validator for risk_score.

This cache is not merely an optimization in the observed path. Client.callTool asks getToolOutputValidator for the named tool and enters tool-specific validation only when that lookup succeeds. Discovery is therefore a functional precondition for this validation behavior.

07

After discovery, the same result failed closed

The second call used the same name, empty arguments and server handler. This time Client.callTool raised McpError code -32602, InvalidParams, with the message “Structured content does not match the tool’s output schema: data/score must be integer.” The call did not return a value to downstream application code.

That is useful client defense. A nonconforming result did not silently enter an agent planner once the metadata cache existed. The result also shows why deterministic negative fixtures matter: a happy-path tool returning a correct integer would not reveal whether the validator was present or merely unnecessary.

08

Validation happened after the remote action

The server call counter reached two. On the validating run, the remote handler executed and generated its response before the client could inspect structuredContent. Rejecting the result protected consumers on the client side; it did not rewind whatever the tool may already have read, written, sent or purchased.

Output validation is therefore not authorization, approval, transaction control or rollback. It can stop malformed data from steering later agent steps. It cannot make an unsafe tool call safe, and it cannot prove that a valid-looking response accurately describes the action that occurred.

09

The high-level server adds a separate guard

SDK 1.30.0’s high-level McpServer normalizes a registered output schema, requires structuredContent for a successful schema-bearing result and validates that object after the callback returns. A mismatch is converted into an error before a normal successful result crosses the transport. Teams using that API should preserve this guard and test it.

Client validation is still valuable because the client may connect to servers written in other languages, older versions or custom stacks. Defense in depth is appropriate, but the two checks have different owners and timing. A server-side check catches its own handler defect before delivery; a client-side check distrusts the peer’s delivery before local consumption.

10

Tool-list changes can invalidate yesterday’s validator state

A server can announce that its tool list changed. A tool name can remain stable while its output schema changes, or a replacement deployment can present a different contract under the same endpoint. Since the validator is derived from discovered metadata, stale discovery can mean validating against the wrong version rather than having no validation at all.

Pin a digest of the server identity, tool name and canonicalized input/output schemas. On a list-changed notification or reconnect, pause calls, fetch the complete inventory, compare digests, compile validators and run negative contract probes before restoring autonomy. Treat schema removal or widening as a review event, not a transparent metadata refresh.

11

A valid object can still carry hostile meaning

JSON Schema can establish type, required fields, ranges, enums and structural constraints. It cannot establish that a summary is truthful, that a URL is safe to fetch, that text is free of prompt injection, or that an identifier belongs to the caller’s tenant. A string can satisfy its schema while containing an instruction aimed at the next model.

OWASP’s independent prompt-injection guidance explicitly includes forged tool outputs and recommends treating intermediate content as untrusted while validating tool calls against user permissions and session context. That guidance does not reproduce this SDK result. It supplies the broader defensive boundary: schema-valid tool output still needs provenance, content handling and consequence-aware authorization.

12

Make discovery a connection state, not an optional convenience

Wrap the SDK so application code cannot call a tool on a newly connected session until capability negotiation and complete tool discovery succeed. Build an immutable per-session registry containing server identity, tool definition, compiled validators and review status. Calls should resolve only through that registry, not from an arbitrary model-supplied string.

If discovery times out, returns duplicate names, presents unsupported schema features or cannot compile a validator, fail the tool closed. Do not fall back from structured validation to “best effort” parsing merely to keep an agent loop moving. A bounded text-only path can be a separate reviewed mode, but it should not inherit trust from a missing schema.

13

Test both negative output and effect containment

For every consequential tool, preserve fixtures for missing structuredContent, wrong primitive types, boundary values, extra fields, oversized arrays, unknown enum values and schema-version drift. Assert not only that the client throws, but also that invalid results never enter memory, planning, persistence, analytics or a follow-up tool call.

Separately constrain the invocation itself with least-privilege credentials, tenant checks, destination allowlists, spend limits, idempotency keys and reversible staging. Record whether the handler executed when validation failed. This keeps teams from confusing downstream data rejection with prevention of upstream effects.

14

What failed, and what the probe does not establish

An initial source lookup through a combined shell command was blocked by the local command-safety gate, so I did not use its absent output. I instead installed the pinned package with lifecycle scripts disabled, inspected the local distributed source and ran the checked-in isolated harness. The final audit passed ten assertions and removed its temporary dependency tree.

The probe did not test a language model, production host, remote HTTP or stdio transport, authentication, concurrent discovery, reconnect races, list-changed notifications, streaming tasks, another language SDK, hostile schema complexity, content injection or real side effects. It tested one direct-before-discovery sequence and one post-discovery sequence in TypeScript SDK 1.30.0. It does not establish how often applications call tools before listing them, and it is not a vulnerability disclosure or protocol conformance assessment.

15

Decision: gate calls on discovered and pinned contracts

Deploy structured MCP outputs into an autonomous agent only when every connection reaches a verified ready state: server identity is authenticated, tool inventory is complete, schemas are pinned, validators compile, negative fixtures fail, schema changes suspend calls and invalid results cannot reach downstream planning. Keep server-side validation enabled where the SDK supports it.

Constrain to a synthetic trial when discovery or schema provenance is uncertain. Reject automatic calls when application code can bypass the validated registry, when a schema failure is ignored, or when output validation is being presented as permission to execute. Roll back on any call before readiness, schema drift without re-approval, invalid result reaching a model, or effect outside the tool’s independent authorization envelope.

16

Copy-ready MCP structured-output gate

Complete this record for one server artifact and connection lifecycle. A valid output protects downstream data handling; it never authorizes the upstream action.

Entries stay in this browser tab and are not submitted to AccessAllGPT. Blank responses are copied as [Unresolved].

Operator, endpoint, transport, authentication, artifact digest, deployment identity and review expiry.

Connection states, complete listTools requirement, timeout behavior and proof that callTool is unreachable before readiness.

Tool name plus canonical input/output schema digests, supported schema dialect and handling for removals or widening.

SDK and validator versions, compilation result, negative fixtures, error codes and proof invalid data cannot reach consumers.

Framework output validation path, mismatch behavior and tests for missing, malformed and schema-invalid structuredContent.

Reconnect and list-changed workflow, cache invalidation, inventory diff, call suspension and re-approval threshold.

Treatment of schema-valid strings, URLs and identifiers; prompt-injection isolation, provenance and tenant/object checks.

Credential scope, destinations, resources, spend, idempotency and evidence that result rejection is not mistaken for rollback.

Discovery version, schema digest, validator decision, handler execution, downstream suppression and correlation ID per call.

Pre-readiness call, drift, bypass, invalid consumption or undeclared effect; kill switch, credential revocation and residue cleanup.

Primary sources

  1. Tools (version 2026-07-28)Model Context Protocol Specification · Reviewed: Tool definition, output schema, structured content, calling tools, error handling and security considerations · Retrieved · Supports: The current protocol defines outputSchema as an optional contract for structuredContent and says a conforming structured result must match that schema; it also separates tool execution errors from protocol errors.
  2. Schema reference: CallToolResult (version 2026-07-28)Model Context Protocol Specification · Reviewed: CallToolResult content, structuredContent, isError and result metadata fields · Retrieved · Supports: The schema requires structuredContent to be a JSON object when present, but the tool-specific output contract comes from the separately discovered Tool.outputSchema.
  3. @modelcontextprotocol/sdk 1.30.0 client implementationModel Context Protocol TypeScript SDK · Reviewed: Client.callTool, cacheToolMetadata, getToolOutputValidator and listTools implementation · Retrieved · Supports: The pinned client validates a tool result only when a validator is present in its metadata cache; listTools clears and repopulates that cache from discovered output schemas.
  4. @modelcontextprotocol/sdk 1.30.0 server implementationModel Context Protocol TypeScript SDK · Reviewed: High-level McpServer tool registration, input validation, output validation and callback dispatch · Retrieved · Supports: The high-level McpServer validates registered tool structuredContent against its configured output schema before returning a successful result; this differs from the deliberately nonconforming low-level fixture used in the client probe.
  5. TypeScript SDK release 1.30.0Model Context Protocol on GitHub · Reviewed: Release identity, tag, publication date and package change record · Retrieved · Supports: The official release record binds the tested package to SDK version 1.30.0, released July 27, 2026.
  6. LLM Prompt Injection Prevention Cheat SheetOWASP Cheat Sheet Series · Reviewed: Agent-specific attacks, thought and observation injection, least privilege, tool validation and trust-boundary guidance · Retrieved · Supports: Independent defensive guidance treats tool output as untrusted content and recommends validating tool calls against user permissions and session context; it does not test this SDK cache behavior.
  7. Introducing the Model Context ProtocolAnthropic · Reviewed: November 25, 2024 announcement, architecture, SDKs and ecosystem purpose · Retrieved · Supports: Anthropic introduced MCP as an open standard for connecting AI assistants to data sources and tools, establishing why discovered tool contracts enter an agent client’s trust boundary.

Limitations

This is a bounded local reproduction on Node v24.10.0, Darwin arm64, @modelcontextprotocol/sdk 1.30.0 and Zod 3.25.76 using one deliberately nonconforming low-level in-memory server, one tool, two calls and one integer schema. It did not test a model, production MCP client, high-level server mismatch end to end, remote or stdio transport, authentication, concurrency, reconnect, list-change races, task streaming, another SDK, hostile schema complexity, content injection, malware, external effects or real data. It demonstrates that the tested Client.callTool path lacked tool-specific validation before listTools populated its cache and rejected the same invalid result afterward; it does not measure prevalence, prove exploitability, allege a protocol defect or replace a security review. OWASP guidance is adjacent defensive evidence, not validation of this probe.

Disclosures

AccessAllGPT created and ran only the synthetic local fixture in the checked-in harness. No model, production client, third-party MCP server, credential, external endpoint or user data was used. The MCP project, Anthropic, OWASP and SDK maintainers did not review this article or provide access, private data, payment or endorsement. AccessAllGPT Research is operated by NeuralArc, is independent, and is not affiliated with OpenAI, Anthropic, the MCP project or OWASP. Publication-wide relationships are listed on the disclosures page.

Further AccessAllGPT guidance

  1. MCP’s readOnlyHint Is Not a Permission Boundary
  2. MCP Token Passthrough Needs an Audience Boundary
  3. Before You Connect an MCP Server to Production
  4. Prompt Injection: Set the Deployment Gates Before Your LLM Can Act
  5. Design an Agent Benchmark That Predicts Production
  6. AccessAllGPT Research methodology
  7. Publication disclosures

Continue the research

Get evidence-led updates for teams making production AI decisions.