MCP Structured Output Validator

Paste a tools/call result, with the tool definition beside it, and get the conformance checked: whether the structured content matches the schema the server promised, whether the compatibility text block is there and agrees with it, and whether a failure was reported in a way the model can act on.

Paste below, or drop a file anywhere on this panel

Or drop a file anywhere on this panel. Nothing is uploaded: the analysis runs in this tab.

The answer appears here

Paste on the left and press Check. Nothing leaves this tab.

Wanted a different tool?

  • MCP Tool Schema Validator for the input side, because a schema can be perfectly valid and still constrain nothing once a client hands it to a model.
  • MCP JSON-RPC Message Validator if the result is not the problem and the envelope around it is, since a protocol error and a tool failure are different shapes.

Examples

Real input you can load into the tool above. Each one shows a different thing going wrong, because that is what the tool is for.

The schema says number, the server sent a string

A declared schema is a MUST for the server, and only a SHOULD for the client to check, so nothing else in the chain reports this

{
  "tool": {"name": "get_weather_data", "outputSchema": {"type": "object", "properties": {"temperature": {"type": "number"}, "conditions": {"type": "string"}, "humidity": {"type": "number"}}, "required": ["temperature", "conditions", "humidity"]}},
  "result": {"content": [{"type": "text", "text": "{\"temperature\":\"22.5\",\"conditions\":\"Partly cloudy\"}"}], "structuredContent": {"temperature": "22.5", "conditions": "Partly cloudy"}}
}

Structured data and an empty content array

Any client that does not read structuredContent sees a tool that returned nothing at all, and the call still succeeds

{
  "tool": {"name": "list_items", "outputSchema": {"type": "object", "properties": {"items": {"type": "array"}}}},
  "result": {"content": [], "structuredContent": {"items": []}}
}

A schema describing the envelope instead of the payload

outputSchema describes structuredContent only, so this one can never match any result the server returns

{
  "tool": {"name": "search", "outputSchema": {"type": "object", "properties": {"content": {"type": "array"}, "isError": {"type": "boolean"}}}},
  "result": {"content": [{"type": "text", "text": "{}"}], "structuredContent": {}}
}

A failure with nothing the model can act on

The point of reporting a failure in the result rather than as a protocol error is that the model reads the reason

{
  "result": {"content": [], "isError": true}
}

Common mistakes

These are the ones that fail silently. The config is accepted, nothing raises an error, and the consequence arrives later.

  1. Writing an output schema that describes the whole result

    `outputSchema` describes the contents of `structuredContent`, not the envelope. A schema declaring `content` or `isError` is one level too high and can never match anything the server returns.

    Instead:Describe the payload fields only, and leave `content` and `isError` out of the schema entirely.

  2. Returning structured content with no text block beside it

    The specification asks for the serialized JSON in a TextContent block for backwards compatibility. Without it, any client that does not read `structuredContent` sees a tool that returned nothing, and no error is raised anywhere.

    Instead:Emit both from one value: `JSON.stringify` the structured object into a text block as well.

  3. Building the text block separately from the structured data

    Two serialisations drift. Once they disagree, two clients reading the same result get different answers and neither sees a problem, which is worse than the block being absent.

    Instead:Serialize once and use the result twice. If the text is a human summary, keep it as well as the JSON rather than instead of it.

  4. Reporting a failed operation as a JSON-RPC error

    A protocol error is handled by the client, so the model usually learns only that the call failed. A rate limit, a missing record or a rejected business rule is a tool execution error, and reporting it as a protocol error takes the reason away from the participant who could act on it.

    Instead:Return a normal result with `isError: true` and the reason in a text block. Keep protocol errors for requests you could not process at all.

  5. Testing the schema against one populated response

    The shape that drifts is usually the empty one. A field that is present when a list has items and omitted when it does not will pass every test written against a successful call.

    Instead:Validate the empty case and the failure case as well, and decide deliberately whether structured content is returned at all when `isError` is true.

A declared schema is a MUST for the server and a SHOULD for the client

That asymmetry is why this page exists. The specification requires a server to conform to any output schema it declares, and only advises the client to check. So the party obliged to be correct is not the party obliged to notice, and a server that drifts from its own schema breaks nothing visibly: the result flows through, and something downstream reads a field that is not the shape it was promised.

outputSchema describes structuredContent, not the result

This is the mistake that produces a schema which can never match. The envelope is content, structuredContent and isError. The schema describes the contents of structuredContent alone, so a schema whose properties include content or isError is one level too high and will fail against every result the server ever returns. It looks entirely reasonable in review, which is why it survives.

The text block almost nobody emits

The specification says a tool returning structured content SHOULD also return the serialized JSON in a TextContent block. It is a SHOULD, so skipping it breaks no rule, and the consequence is invisible: a client that does not read structuredContent gets a result whose content array is empty or full of prose. The tool ran, the data came back, and the model was handed nothing. This page checks that the block exists AND that it carries the same data, because a mirror that disagrees is worse than one that is missing.

isError is not a JSON-RPC error

Two mechanisms, and choosing the wrong one changes what the model can do about it. A protocol error is handled by the client, and the model usually learns only that the call failed. A tool execution error is a normal result with isError true, delivered to the model as content it can read, so it can see that a rate limit resets in thirty seconds and behave accordingly. If the tool ran and the answer is no, that is isError. Reserve protocol errors for requests you could not process at all.

What this validator checks, and what it does not

The JSON Schema subset here is type, required, properties, additionalProperties, items, enum, const, minimum, maximum, exclusiveMinimum, exclusiveMaximum, minLength, maxLength, pattern, minItems, maxItems, uniqueItems. That covers the drift that actually happens between a published schema and a returned result: a field that disappeared, a number that became a string, an enum that gained a member, a required key that is only present when the list is non-empty. It does not evaluate the combinators, anyOf, oneOf, allOf, not, or $ref, and when it sees one it says so in a finding rather than passing quietly. A validator that ignores half a schema and reports no violations is the wrong kind of reassurance.

Paste both halves to check conformance

A result on its own can only be checked against the envelope rules, because there is nothing to compare its shape to. Paste the tool definition alongside it, as an object with tool and result keys, and the structured content is validated against the declared schema as well. A whole JSON-RPC response, a bare result, or a lone tool definition are all accepted too, since those are the things people actually have in front of them.

What this cannot see

Whether the schema describes the right thing. A tool can conform perfectly to a schema that documents the wrong fields, and everything here will pass. It also sees one result: a shape that is stable for a populated response and changes when a collection comes back empty is the most common real defect, and finding it means calling the tool twice and checking both. Nothing here contacts your server, so the results you paste are the only evidence it has.