You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
With mcp==2.3.0, mcp-types==2.3.0, and protocol 2026-07-28, the official @modelcontextprotocol/conformance@0.2.0-alpha.12 client scenario sep-2322-client-request-state sends a successful tools/call result without resultType to verify the client fallback to complete.
The modern strict union validator runs before any normalization. ClientSession.send_request raises pydantic.ValidationError (CallToolResult.resultType Field required, InputRequiredResult.resultType Field required), and clients using PydanticAI MCPToolset / FastMCP 4.1.3 exit 1. The conformance harness reports all 5/5 message-exchange checks PASS but marks the scenario overall FAILED because the client process exits nonzero.
Reproduce
Use the official runner 0.2.0-alpha.12 with --scenario sep-2322-client-request-state --spec-version 2026-07-28; run a PydanticAI MCPToolset client that calls the scenario's test_mrtr_echo_state, test_mrtr_unrelated, test_mrtr_no_state, test_mrtr_no_result_type tools. The final call returns an omitted discriminator and triggers the exception in mcp_types.methods.validate_server_result.
Expected
For an inbound modern result with no resultType, inputRequests, or requestState, default the discriminator to complete before strict per-version validation. Preserve explicit discriminator values, untagged pending-input envelopes, and legacy protocol behavior. Do not alter server output or the MCP profile.
Evidence and proposed change
A 7-case SDK regression suite passes with this normalization. All four official scenarios driven through PydanticAI MCPToolset pass (2/2, 6/6, 9/9, 5/5 checks; process exit 0), and the full official Python SDK scored client baseline remains 32/32 PASS. Candidate fix: #3766 (automatically closed pending this issue / maintainer assignment).
The conformance result here is the useful signal and it is easy to misread: five of five exchange checks pass while the scenario is marked failed, because the client process exits nonzero on the validation error. The harness is telling you the wire behaviour is correct and the client is being punished for being strict about a discriminator the scenario deliberately omits. That is a normalization ordering problem, not a union design problem.
The seam matters. A per-class default of resultType: "complete" will not fix this, because in a strict discriminated union the discriminator is what selects the class, so InputRequiredResult cannot default its own way into existence. The place that works is a pre-validator on the inbound envelope, ahead of the union: if resultType is absent and the payload carries neither inputRequests nor requestState, then treat it as complete, while leaving any payload that does carry those markers to select its own class and leaving an explicit discriminator untouched.
Keeping that asymmetry is what protects the rest of the behaviour you describe. Normalization should be inbound only, gated on the modern spec version, and it must not weaken outbound validation, since the server side is where a malformed result still needs to fail loudly.
Two cases worth adding to the seven, because they are where a normalization like this usually goes wrong. A result that omits resultType but includes a non-empty inputRequests must still raise, otherwise the fallback has swallowed a pending-input envelope. And a result that omits resultType while matching neither the complete nor the input-required shape should surface the original validation error, not a missing-field error, or reporters will chase the discriminator instead of the actual schema violation.
With PR 3766 auto-closed, reopening against the envelope pre-validator plus your suite is probably the shortest path, and referencing this conformance failure in the PR description should stop it being closed again for lack of a link to a live defect.
Activity
Ben-Home commentedon Oct 28, 2026
The conformance result here is the useful signal and it is easy to misread: five of five exchange checks pass while the scenario is marked failed, because the client process exits nonzero on the validation error. The harness is telling you the wire behaviour is correct and the client is being punished for being strict about a discriminator the scenario deliberately omits. That is a normalization ordering problem, not a union design problem.
The seam matters. A per-class default of
resultType: "complete"will not fix this, because in a strict discriminated union the discriminator is what selects the class, soInputRequiredResultcannot default its own way into existence. The place that works is a pre-validator on the inbound envelope, ahead of the union: ifresultTypeis absent and the payload carries neitherinputRequestsnorrequestState, then treat it as complete, while leaving any payload that does carry those markers to select its own class and leaving an explicit discriminator untouched.Keeping that asymmetry is what protects the rest of the behaviour you describe. Normalization should be inbound only, gated on the modern spec version, and it must not weaken outbound validation, since the server side is where a malformed result still needs to fail loudly.
Two cases worth adding to the seven, because they are where a normalization like this usually goes wrong. A result that omits
resultTypebut includes a non-emptyinputRequestsmust still raise, otherwise the fallback has swallowed a pending-input envelope. And a result that omitsresultTypewhile matching neither the complete nor the input-required shape should surface the original validation error, not a missing-field error, or reporters will chase the discriminator instead of the actual schema violation.With PR 3766 auto-closed, reopening against the envelope pre-validator plus your suite is probably the shortest path, and referencing this conformance failure in the PR description should stop it being closed again for lack of a link to a live defect.