Skip to content

Output

OutputFormat, encoders, 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 REST_FRAMEWORK_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.

OutputFormat

Bases: str, Enum

Encoding of a ToolResult's human-readable text block.

Only content[0] varies; structuredContent is always JSON.

Attributes:

Name Type Description
JSON

Pretty-printed JSON. The safe default.

TOON

Token-oriented object notation, compact for large uniform arrays. Falls back to JSON when the optional toon extra is absent.

AUTO

Per-payload choice — TOON for a uniform list of objects, JSON otherwise.

coerce classmethod

coerce(value: OutputFormat | str | None) -> OutputFormat

Accept either an enum member or its string value; default to JSON.

encode_json

encode_json(payload: Any) -> str

Encode payload as a stable, pretty JSON string.

default=str renders DRF outputs containing Decimal, UUID or datetime without raising; keys are sorted so the output is deterministic.

encode_toon

encode_toon(payload: Any) -> str

Encode payload as TOON (token-oriented object notation).

TOON is an optional dependency. Without python-toon installed this warns and falls back to JSON, so a tool call never breaks because the extra is absent. The warning fires every time — silence it with warnings.filterwarnings or install the extra.

The return value alone cannot say which encoder produced it, so a caller that labels the text — build_tool_result stamps a # format: toon marker — must ask toon_encoder rather than assume.

build_tool_result

build_tool_result(
    payload: Any,
    *,
    output_format: OutputFormat = OutputFormat.JSON,
    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 the first content block.

required
output_format OutputFormat

How content[0] renders the payload. TOON output is wrapped in a fenced toon block with a leading marker line so clients that don't parse TOON natively can still display it. On a deployment without the optional [toon] extra the encoder falls back to JSON, and the marker is left off with it — the label always names the format the bytes are actually in.

JSON
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 bypasses output_format entirely — there is no TOON rendering of a PNG — 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, a service and a chain 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 drf-services 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).