Repository navigation
Client treats JSON-null structuredContent as missing, skipping outputSchema validation #3345
Description
Activity
- addedv2Affects the v2 line (2.x on main)Affects the v2 line (2.x on main)spec-2026-07-28Concerns the SDK's implementation of the 2026-07-28 MCP spec revisionConcerns the SDK's implementation of the 2026-07-28 MCP spec revision
on Aug 20, 2026 I'd like to take this one if it's still open.
I've confirmed the diagnosis on
main(6705402):validate_tool_resultkeys the
missing-structured-content check onresult.structured_content is None, which
cannot distinguish an omittedstructuredContentfrom an explicit JSONnull—
pydantic stores both asNone, and onlymodel_fields_setseparates them.My plan is the narrow fix you describe:
- key the check on
"structured_content" in result.model_fields_set, matching
the=== undefinedline the TypeScript SDK draws; - omitted stays as the existing error, message unchanged;
- explicit
nullgoes 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=Trueinmcp/shared/peer.py. So the tests have to parse
CallToolResultfrom 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 sendnull.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-nullThe 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 --checkandpyright(strict) clean on both touched files, the
changed lines fully covered with no newno coverpragmas, 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.- key the check on
I reproduced this against current
main(6705402e246dc4eb1fcdf4d9902b78d3c9c36e1b). An explicitly suppliedstructuredContent: nullhasstructured_contentinmodel_fields_set, while an omitted field does not; both currently raiseTool nullable has an output schema but did not return structured contentbefore 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 JSONnullaccording tooutputSchema. 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.
#3621 fixes this with the same
model_fields_setcheck you proposed in #3346, so thank you for both the report and the approach.An explicit null
structuredContentis now validated against the tool'soutputSchema, and only an absent field counts as missing.If your case still isn't covered, feel free to open a new issue.
Initial Checks
Release line
2.x (current stable)
Description
On current
main(57394b0548d1e2dc2dce8d67d84985769df3b8bb),ClientSession.validate_tool_resulttreatsstructured_content is Noneas "the tool did not return structured content".That collapses two different wire shapes:
structuredContent(field absent)null("structuredContent": null)SEP-2106 / spec 2026-07-28 allow
structuredContentto be any JSON value, includingnull. The TypeScript SDK already checks=== undefined(not falsy / not null) for this reason.Pydantic stores both omitted and JSON null as
None.model_fields_setdistinguishes them: aCallToolResultparsed from{"content": [], "structuredContent": null}has"structured_content" in model_fields_set, while an omitted field does not.What happens today
"outputSchema": {"type": "null"}(or{"type": ["object", "null"], ...})."structuredContent": null.Tool {name} has an output schema but did not return structured contentand never runs jsonschema against the value.What I expected
structuredContentstill raises the existing missing-field error.0,false,"") stay validated (they already are, because the current check isis Nonerather than falsy).This is not #3224 (server injecting nulls for
NotRequiredkeys). That issue is about serializing omitted object keys as null. This one is the client presence check beforeoutputSchemavalidation.I hit this while checking official SDK conformance of declared
outputSchemaagainststructuredContent. 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_setbehavior before filing.Example Code
Against a tool whose
outputSchemais{"type": "null"},validate_tool_resultcurrently raises the missing-field RuntimeError forexplicit_null. After a presence check that usesmodel_fields_set, that result validates.Python & MCP Python SDK
mainat57394b0548d1e2dc2dce8d67d84985769df3b8bb(2.x)