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).
I hit this from a different direction, and I think it needs a decision on intended behaviour
before a fix.
I was scoring a stdio security proxy with my benchmark
(mcp-defense-bench-py). When the proxy
blocks a call, it writes its own {"content": [...], "isError": true} with no resultType, on
a session that negotiated 2026-07-28 (the proxy forwards server/discover to a v2 server).
FastMCP 4.1 on mcp 2.3.0 raises the ValidationError described here. I reported the missing
tag to the proxy (vdalal/agentx-security-sdk#1).
Two things point to rejection being the intended behaviour:
The TypeScript client (@modelcontextprotocol/client 2.4.0, versionNegotiation: { mode: "auto" })
rejects the same reply: SdkError: Invalid result for tools/call: missing required resultType — servers implementing protocol revision 2026-07-28 MUST include it (the absent-means-complete bridge applies only to earlier-revision servers).
If rejection stays, the Python error could be as direct as the TypeScript one. Today it reads 2 validation errors for union[CallToolResult,InputRequiredResult], and its second line, InputRequiredResult.resultType Field required, sent me looking at input-required handling.
The actual cause was a missing tag from a non-conformant intermediary.
Minimal repro (plain-Python stdio server, no SDK, so the reply bytes are exact):
repro.py
"""A 2026-07-28 tools/call result without `resultType`: what the Python client raises.The "server" is a few lines of plain Python speaking JSON-RPC over stdio, so the replybytes are exact. It answers server/discover (2026-07-28) and returns an isError toolresult with no resultType, the shape a stdio security proxy writes when it blocks a call."""importsysimportanyiofrommcpimportClient, StdioServerParametersSERVER=r'''import json, sysfor line in sys.stdin: msg = json.loads(line) if "id" not in msg: continue reply = {"jsonrpc": "2.0", "id": msg["id"]} if msg["method"] == "server/discover": reply["result"] = {"resultType": "complete", "supportedVersions": ["2026-07-28"], "capabilities": {"tools": {}}, "ttlMs": 0, "cacheScope": "private"} elif msg["method"] == "tools/call": reply["result"] = {"content": [{"type": "text", "text": "blocked by policy"}], "isError": True} else: reply["error"] = {"code": -32601, "message": "Method not found"} print(json.dumps(reply), flush=True)'''asyncdefmain() ->None:
asyncwithClient(StdioServerParameters(command=sys.executable, args=["-c", SERVER])) asclient:
print("negotiated:", client.protocol_version)
awaitclient.call_tool("echo", {"text": "hi"})
anyio.run(main)
On a 2025-11-25 session the same untagged reply is accepted as complete, as the spec says.
AI disclosure: I used Claude Code to search the SDK source, write the repro and run it on my machine; I reviewed the reproduction and output and can answer questions on it.
Activity
Ben-Home commentedon Oct 28, 2026
Gowthaman90 commentedon Nov 4, 2026
I hit this from a different direction, and I think it needs a decision on intended behaviour
before a fix.
I was scoring a stdio security proxy with my benchmark
(mcp-defense-bench-py). When the proxy
blocks a call, it writes its own
{"content": [...], "isError": true}with noresultType, ona session that negotiated 2026-07-28 (the proxy forwards
server/discoverto a v2 server).FastMCP 4.1 on
mcp2.3.0 raises theValidationErrordescribed here. I reported the missingtag to the proxy (vdalal/agentx-security-sdk#1).
Two things point to rejection being the intended behaviour:
resultType, andscopes the absent-means-
completefallback to servers on earlier versions.sep-2322 absent-resultType probe is an invalid stimulus on the 2026-07-28 wire it negotiates conformance#475 argues that the conformance probe this issue relies on
is invalid on the 2026-07-28 wire it negotiates.
@modelcontextprotocol/client2.4.0,versionNegotiation: { mode: "auto" })rejects the same reply:
SdkError: Invalid result for tools/call: missing required resultType — servers implementing protocol revision 2026-07-28 MUST include it (the absent-means-complete bridge applies only to earlier-revision servers).If rejection stays, the Python error could be as direct as the TypeScript one. Today it reads
2 validation errors for union[CallToolResult,InputRequiredResult], and its second line,InputRequiredResult.resultType Field required, sent me looking at input-required handling.The actual cause was a missing tag from a non-conformant intermediary.
Minimal repro (plain-Python stdio server, no SDK, so the reply bytes are exact):
repro.py
On a 2025-11-25 session the same untagged reply is accepted as
complete, as the spec says.mcp 2.3.0, mcp-types 2.3.0, pydantic 2.14.2, Python 3.12.13, macOS arm64.
AI disclosure: I used Claude Code to search the SDK source, write the repro and run it on my machine; I reviewed the reproduction and output and can answer questions on it.