Skip to content

Output

The JSON encoder and the ToolResult builder.

A tool's result has two halves and they are toggled independently: structuredContent on the call result, and outputSchema on the tools/list entry. The MCP spec imposes one asymmetric rule between them — a tool that advertises outputSchema must return conforming structuredContent, while the reverse is allowed — so they are not a single switch.

Both are on by default, server-wide via SPEC_MCP["INCLUDE_STRUCTURED_CONTENT"] and ["INCLUDE_OUTPUT_SCHEMA"], and per tool via include_structured_content= and include_output_schema= at registration. The asymmetric rule is enforced: include_output_schema=True with include_structured_content=False raises at registration rather than advertising a schema nothing will satisfy.

That is a check on the settings, not on a response. To assert that a real result satisfies the schema its tool advertised — types and formats, not only the property names — see assert_tool_result_conforms.

See Omitting structuredContent and outputSchema for when to turn either off, and Hide plumbing from the model for what shapes the schema an agent sees — field markings project the payload and the advertised outputSchema from the same declaration.

encode_json

encode_json(payload: Any) -> str

Encode payload as a stable, pretty JSON string.

default=str renders a presented value containing Decimal, UUID or datetime without raising; keys are sorted so the output is deterministic. A payload sort_keys can sort is written by it unchanged, so its bytes and its nesting limit are json.dumps's own (test_encode_json_writes_what_sort_keys_writes_wherever_sort_keys_can); only one it refuses is put in order first.

build_tool_result

build_tool_result(
    payload: Any,
    *,
    is_error: bool = False,
    include_structured_content: bool = True,
    meta: dict[str, Any] | None = None,
    content_kind: ToolContentKind = ToolContentKind.TEXT,
    content_mime_type: str | None = None,
    binding_name: str | None = None,
) -> ToolResult

Build a ToolResult for a successful (or tool-level error) call.

Parameters:

Name Type Description Default
payload Any

The JSON-shaped tool output. Becomes structuredContent verbatim and is also rendered, as JSON, into the first content block.

required
is_error bool

Stamped onto the result as isError.

False
include_structured_content bool

False omits structuredContent entirely. The text block still carries the full payload, so a client that doesn't consume the structured field loses nothing.

True
meta dict[str, Any] | None

The base protocol's _meta bundle on the result envelope — per-call, unlike the static _meta already advertised on the tools/list entry. Omitted from the payload when empty.

None
content_kind ToolContentKind

The block type the binding declared. Anything other than TEXT is a media or resource-link block rather than JSON text, and a payload that doesn't match the declared kind comes back as an isError result naming the binding.

TEXT
content_mime_type str | None

Media type for a non-TEXT block.

None
binding_name str | None

Names the binding in that mismatch message.

None

A None payload is a result with nothing to present: an allow_none RETRIEVE that found no row, a service whose output re-read selector found none, or a service with no re-read declaring ServiceSpec(allow_none=True) that returned None. It is served as {}, both as structuredContent and as the text block, because MCP requires structured content to be an object and asks the text block to carry the same JSON. This is the one place the rule lives, so a selector and a service answer it the same way on every entry point. The advertised outputSchema of those tools admits the {} (build_output_schema(may_be_empty=...)). A service with no re-read that returns None without declaring allow_none is served {} too, against a schema that still requires its fields, because the kernel presents an undeclared None rather than refusing it (test_an_undeclared_none_is_still_served_empty_against_a_strict_schema); allow_none=True is the declaration that makes the schema admit it (test_a_service_declaring_allow_none_serves_an_empty_object_its_schema_admits).