Skip to content

Commit 1d79d07

Browse files
fix(client): default omitted modern resultType to complete before validation
1 parent a525a3d commit 1d79d07

2 files changed

Lines changed: 42 additions & 0 deletions

File tree

‎src/mcp/client/session.py‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,17 @@ def _clamp_inbound_ttl(raw: dict[str, Any]) -> None:
7878
raw["ttlMs"] = 0
7979

8080

81+
def _default_inbound_complete_result(raw: dict[str, Any], version: str) -> dict[str, Any]:
82+
if (
83+
version not in MODERN_PROTOCOL_VERSIONS
84+
or "resultType" in raw
85+
or "inputRequests" in raw
86+
or "requestState" in raw
87+
):
88+
return raw
89+
return {"resultType": "complete", **raw}
90+
91+
8192
@cache
8293
def _wire_fields(target: type[BaseModel] | UnionType) -> frozenset[str]:
8394
"""Top-level wire keys `target` declares (its members', for a union)."""
@@ -587,6 +598,7 @@ async def send_request(
587598
_clamp_inbound_ttl(raw)
588599
# Literal fallback covers pre-handshake and stateless; matches runner.py.
589600
version = self._negotiated_version or "2025-11-25"
601+
raw = _default_inbound_complete_result(raw, version)
590602
try:
591603
_methods.validate_server_result(method, version, raw)
592604
except KeyError:
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
from __future__ import annotations
2+
3+
import pytest
4+
5+
from mcp.client.session import _default_inbound_complete_result
6+
from mcp_types import methods
7+
8+
9+
def test_modern_missing_discriminator_is_a_complete_result_for_the_client() -> None:
10+
original = {"content": [{"type": "text", "text": "ok"}]}
11+
normalized = _default_inbound_complete_result(original, "2026-07-28")
12+
assert original == {"content": [{"type": "text", "text": "ok"}]}
13+
assert normalized["resultType"] == "complete"
14+
methods.validate_server_result("tools/call", "2026-07-28", normalized)
15+
16+
17+
@pytest.mark.parametrize("body", [
18+
{"resultType": "input_required", "inputRequests": {"confirm": {}}},
19+
{"resultType": "task", "content": []},
20+
{"inputRequests": {"confirm": {}}},
21+
{"requestState": "sealed"},
22+
{"resultType": None, "content": []},
23+
])
24+
def test_explicit_or_pending_result_types_are_never_downgraded(body: dict) -> None:
25+
assert _default_inbound_complete_result(body, "2026-07-28") == body
26+
27+
28+
def test_legacy_wire_is_never_rewritten() -> None:
29+
body = {"content": []}
30+
assert _default_inbound_complete_result(body, "2025-11-25") is body

0 commit comments

Comments
 (0)