Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/mcp/client/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,17 @@ def _clamp_inbound_ttl(raw: dict[str, Any]) -> None:
raw["ttlMs"] = 0


def _default_inbound_complete_result(raw: dict[str, Any], version: str) -> dict[str, Any]:
if (
version not in MODERN_PROTOCOL_VERSIONS
or "resultType" in raw
or "inputRequests" in raw
or "requestState" in raw
):
return raw
return {"resultType": "complete", **raw}


@cache
def _wire_fields(target: type[BaseModel] | UnionType) -> frozenset[str]:
"""Top-level wire keys `target` declares (its members', for a union)."""
Expand Down Expand Up @@ -587,6 +598,7 @@ async def send_request(
_clamp_inbound_ttl(raw)
# Literal fallback covers pre-handshake and stateless; matches runner.py.
version = self._negotiated_version or "2025-11-25"
raw = _default_inbound_complete_result(raw, version)
try:
_methods.validate_server_result(method, version, raw)
except KeyError:
Expand Down
30 changes: 30 additions & 0 deletions tests/client/test_result_type_compat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from __future__ import annotations

import pytest

from mcp.client.session import _default_inbound_complete_result
from mcp_types import methods


def test_modern_missing_discriminator_is_a_complete_result_for_the_client() -> None:
original = {"content": [{"type": "text", "text": "ok"}]}
normalized = _default_inbound_complete_result(original, "2026-07-28")
assert original == {"content": [{"type": "text", "text": "ok"}]}
assert normalized["resultType"] == "complete"
methods.validate_server_result("tools/call", "2026-07-28", normalized)


@pytest.mark.parametrize("body", [
{"resultType": "input_required", "inputRequests": {"confirm": {}}},
{"resultType": "task", "content": []},
{"inputRequests": {"confirm": {}}},
{"requestState": "sealed"},
{"resultType": None, "content": []},
])
def test_explicit_or_pending_result_types_are_never_downgraded(body: dict) -> None:
assert _default_inbound_complete_result(body, "2026-07-28") == body


def test_legacy_wire_is_never_rewritten() -> None:
body = {"content": []}
assert _default_inbound_complete_result(body, "2025-11-25") is body
Loading