Skip to content

Client treats JSON-null structuredContent as missing, skipping outputSchema validation #3345

Description

@epistemedeus

Initial Checks

Release line

2.x (current stable)

Description

On current main (57394b0548d1e2dc2dce8d67d84985769df3b8bb), ClientSession.validate_tool_result treats structured_content is None as "the tool did not return structured content".

That collapses two different wire shapes:

  1. omitted structuredContent (field absent)
  2. explicit JSON null ("structuredContent": null)

SEP-2106 / spec 2026-07-28 allow structuredContent to be any JSON value, including null. The TypeScript SDK already checks === undefined (not falsy / not null) for this reason.

Pydantic stores both omitted and JSON null as None. model_fields_set distinguishes them: a CallToolResult parsed from {"content": [], "structuredContent": null} has "structured_content" in model_fields_set, while an omitted field does not.

What happens today

  • Tool advertises "outputSchema": {"type": "null"} (or {"type": ["object", "null"], ...}).
  • Server returns "structuredContent": null.
  • Client raises Tool {name} has an output schema but did not return structured content and never runs jsonschema against the value.

What I expected

  • Omitted structuredContent still raises the existing missing-field error.
  • Explicit JSON null is validated against the advertised schema: accept if the schema allows null, reject as a schema mismatch if it does not.
  • Falsy JSON values (0, false, "") stay validated (they already are, because the current check is is None rather than falsy).

This is not #3224 (server injecting nulls for NotRequired keys). That issue is about serializing omitted object keys as null. This one is the client presence check before outputSchema validation.

I hit this while checking official SDK conformance of declared outputSchema against structuredContent. I have a small backwards-compatible test and fix ready and would like to send the PR if a maintainer wants it.

AI assistance: researched and drafted with Grok 4.6; I reviewed the spec text, the TypeScript v2 presence check, and the Pydantic model_fields_set behavior before filing.

Example Code

from mcp_types import CallToolResult

omitted = CallToolResult.model_validate({"content": []})
explicit_null = CallToolResult.model_validate({"content": [], "structuredContent": None})

assert omitted.structured_content is None
assert explicit_null.structured_content is None
assert "structured_content" not in omitted.model_fields_set
assert "structured_content" in explicit_null.model_fields_set

Against a tool whose outputSchema is {"type": "null"}, validate_tool_result currently raises the missing-field RuntimeError for explicit_null. After a presence check that uses model_fields_set, that result validates.

Python & MCP Python SDK

  • Python 3.12
  • MCP Python SDK main at 57394b0548d1e2dc2dce8d67d84985769df3b8bb (2.x)

Activity

  1. added
    v2Affects the v2 line (2.x on main)
    spec-2026-07-28Concerns the SDK's implementation of the 2026-07-28 MCP spec revision
    on Aug 20, 2026
  2. lucianoon commented on Aug 27, 2026

    @lucianoon

    I'd like to take this one if it's still open.

    I've confirmed the diagnosis on main (6705402): validate_tool_result keys the
    missing-structured-content check on result.structured_content is None, which
    cannot distinguish an omitted structuredContent from an explicit JSON null —
    pydantic stores both as None, and only model_fields_set separates them.

    My plan is the narrow fix you describe:

    • key the check on "structured_content" in result.model_fields_set, matching
      the === undefined line the TypeScript SDK draws;
    • omitted stays as the existing error, message unchanged;
    • explicit null goes through the compiled validator, so it's accepted when the
      advertised schema allows null and reported as a schema mismatch when it doesn't.

    One note for whoever reviews it: servers built on this SDK can't produce the
    explicit-null shape at all, because outbound results are dumped with
    exclude_none=True in mcp/shared/peer.py. So the tests have to parse
    CallToolResult from a wire payload rather than construct it — constructing the
    model can't express the distinction. That also means the change is inert for
    in-SDK servers and only affects sessions with peers that do send null.

    The change and three tests are ready and pushed, if it's useful to look before
    deciding:
    https://github.com/lucianoon/mcp-python-sdk/tree/fix/structured-content-explicit-null

    The two explicit-null tests fail without the fix and pass with it; the third pins
    the omitted-field behaviour so it can't drift. Locally: ruff check,
    ruff format --check and pyright (strict) clean on both touched files, the
    changed lines fully covered with no new no cover pragmas, and the full suite
    shows no new failures.

    Happy to open the PR as soon as you assign me — I understand the intake gate
    closes it otherwise.

    Disclosure: I used an AI coding agent to write the patch and the tests. I ran
    the reproduction and the suite myself and can explain the change.

  3. Alqudimi commented on Aug 27, 2026

    @Alqudimi

    I reproduced this against current main (6705402e246dc4eb1fcdf4d9902b78d3c9c36e1b). An explicitly supplied structuredContent: null has structured_content in model_fields_set, while an omitted field does not; both currently raise Tool nullable has an output schema but did not return structured content before JSON Schema validation runs.

    I would keep the fix narrow in ClientSession.validate_tool_result: use field presence for the missing-content check, then let the existing validator accept or reject the explicit JSON null according to outputSchema. I would add focused regression coverage for an explicit null accepted by {"type": "null"}, an explicit null rejected by an object schema, and an omitted field retaining the existing missing-content error. This should not require a public API, dependency, or protocol change.

    I used AI assistance while inspecting the code and preparing this reproduction, and I am accountable for verifying the behavior and any resulting patch. If this direction matches the intended contract, I would appreciate assignment before opening a PR.

  4. maxisbey commented on Oct 2, 2026

    @maxisbey
    Contributor

    #3621 fixes this with the same model_fields_set check you proposed in #3346, so thank you for both the report and the approach.

    An explicit null structuredContent is now validated against the tool's outputSchema, and only an absent field counts as missing.

    If your case still isn't covered, feel free to open a new issue.

    AI Disclaimer

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    spec-2026-07-28Concerns the SDK's implementation of the 2026-07-28 MCP spec revisionv2Affects the v2 line (2.x on main)

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions