An MCP tool result can carry machine-readable data in structuredContent alongside the
human-readable content blocks. A tool may also declare an outputSchema. The
two together create an obligation, quoted from the specification, revision 2026-07-28:
Servers MUST provide structured results that conform to this schema. Clients SHOULD validate structured results against this schema.
So an outputSchema is not documentation. Declare one and every result has to satisfy
it, including the awkward cases: an empty list, a partial result, a value your upstream returned as
null.
| Field | Where | What it is |
|---|---|---|
inputSchema | Tool definition | Required. MUST be a valid JSON Schema object, never null. |
outputSchema | Tool definition | Optional. Constrains structuredContent. |
structuredContent | Tool result | Any JSON value: object, array, string, number, boolean or null. |
Both schemas default to JSON Schema draft 2020-12 when no $schema field is present,
and an explicit $schema selects another draft. For a tool that takes no parameters the
specification recommends { "type": "object", "additionalProperties": false }, which
accepts an empty object and nothing else. { "type": "object" } is also valid and accepts
any object.
The specification asks for backwards compatibility: a tool that returns structured content SHOULD
also return the serialized JSON in a text content block. Clients written before structured content
existed, and clients that never implemented it, read content and nothing else. A result
with only structuredContent looks empty to them.
{
"content": [{ "type": "text", "text": "{\"basket_id\":\"bsk_a1b2c3\"}" }],
"structuredContent": { "basket_id": "bsk_a1b2c3" }
}
The specification says it plainly: structuredContent is server-produced result data
and is unrelated to model "structured outputs", meaning schema-constrained generation by a language
model. Same words, different mechanism. One is your server returning JSON it built. The other is a
model being forced to emit JSON. Nothing in MCP constrains what the model does with your result.
Declare one when the caller is code. A schema lets a client validate, a directory show the shape, and a downstream tool consume the result without parsing prose. Skip it when the honest answer is a free-form document, and skip it when the shape varies with the arguments in a way a single schema cannot express, because a schema you sometimes violate is worse than none: the client is told to validate, and validation failures are yours.
The other reason to be careful is error results. A result with isError: true still
travels in the same envelope, so decide whether your schema describes success only, and if it does,
do not attach structured content to failures.
Results in revision 2026-07-28 carry a resultType, with complete for an
ordinary answer and input_required for a call that needs more from the user before it
can finish. The second returns an inputRequests map and an opaque
requestState, and the client retries the call with inputResponses. The
specification notes that the JSON-RPC id MUST differ between the first request and the retry.
Source: the tools section of the MCP specification, revision 2026-07-28, at modelcontextprotocol.io, fetched 2026-09-09. Every MUST and SHOULD above is quoted or paraphrased from that page, and the schema defaults and the no-parameter forms are its recommendations.
Not reliably, which is why the specification asks you to duplicate the JSON as text. Clients advertise a protocol version at initialize, and older versions predate the field entirely. Sending both costs a few hundred bytes and works everywhere.
Yes. It is any JSON value, including an array, a bare string or null. If you declare an outputSchema, the schema has to describe whatever you actually send, so a top-level array needs a schema with type array.
The specification says clients SHOULD validate, so behaviour varies from ignoring it to rejecting the result. Neither is good for you. Validate against your own schema in your tests, on the same code path that builds the result.
It has to be a valid JSON Schema object, and it is what the model reads when deciding how to call you. Leaving arguments undescribed means the model guesses. Marking additionalProperties false on a no-argument tool is the recommended way to say that plainly.
2020-12 is the default when no $schema is present, so writing 2020-12 and omitting the field is the least surprising choice. Set $schema explicitly only when you need an older draft, and then set it on that schema rather than assuming a server-wide default.
All MCP servers and prices · All guides · Buy the bundle $39