Skip to content

Commit 61dd41b

Browse files
committed
Resolver takes the extracted _meta directly; trim docstrings
The match-on-params form tripped a 3.10-only branch-coverage quirk on the case-body fall-through arc. Reading the already-extracted RequestParamsMeta avoids the match, drops the per-message dict copy, and keeps a single _meta extraction per request. Docstrings/comments added in this PR trimmed to one-liners or dropped where the test/function name already says it.
1 parent 0ae934a commit 61dd41b

6 files changed

Lines changed: 33 additions & 66 deletions

File tree

src/mcp/server/connection.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,7 @@ class Connection:
8787
"""The protocol version negotiated during `initialize`; `None` before
8888
initialization. Stateless connections don't require the handshake, so this
8989
normally stays `None` there (a client that sends `initialize` anyway still
90-
commits it). For the version that applies to the current request - which is
91-
always set, including on stateless connections - read
92-
`ctx.protocol_version` instead."""
90+
commits it). For the per-request value, read `ctx.protocol_version`."""
9391

9492
initialized: anyio.Event
9593
"""Set when `notifications/initialized` arrives (matches TS `oninitialized`);

src/mcp/server/runner.py

Lines changed: 16 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -82,28 +82,21 @@ def _extract_meta(params: Mapping[str, Any] | None) -> RequestParamsMeta | None:
8282

8383
def _resolve_protocol_version(
8484
negotiated: str | None,
85-
params: Mapping[str, Any] | None,
85+
meta: RequestParamsMeta | None,
8686
md: MessageMetadata,
8787
) -> str:
88-
"""Resolve the protocol version that applies to this inbound message.
89-
90-
A handshake-committed value governs the whole connection when present.
91-
Otherwise the per-request signals apply: `_meta` (2026-07-28+), then the
92-
transport's hint (the validated `MCP-Protocol-Version` header on
93-
streamable HTTP). Values outside `SUPPORTED_PROTOCOL_VERSIONS` are
94-
skipped so an unrecognized declaration falls through rather than poisons
95-
surface validation. The literal terminal default is the last
96-
handshake-based revision and is fixed regardless of LATEST bumps.
88+
"""Resolve the protocol version for this inbound message.
89+
90+
Handshake-committed value wins; else per-request `_meta`, else the
91+
transport hint. Unsupported values fall through so surface validation
92+
never sees them.
9793
"""
9894
if negotiated is not None:
9995
return negotiated
100-
match params:
101-
case {"_meta": {**meta}}:
102-
v = meta.get(PROTOCOL_VERSION_META_KEY)
103-
if isinstance(v, str) and v in SUPPORTED_PROTOCOL_VERSIONS:
104-
return v
105-
case _:
106-
pass
96+
if meta is not None:
97+
v = meta.get(PROTOCOL_VERSION_META_KEY)
98+
if isinstance(v, str) and v in SUPPORTED_PROTOCOL_VERSIONS:
99+
return v
107100
if isinstance(md, ServerMessageMetadata):
108101
hint = md.protocol_version
109102
if hint is not None and hint in SUPPORTED_PROTOCOL_VERSIONS:
@@ -250,8 +243,9 @@ async def _on_request(
250243
method: str,
251244
params: Mapping[str, Any] | None,
252245
) -> dict[str, Any]:
253-
version = _resolve_protocol_version(self.connection.protocol_version, params, dctx.message_metadata)
254-
ctx = self._make_context(dctx, _extract_meta(params), version)
246+
meta = _extract_meta(params)
247+
version = _resolve_protocol_version(self.connection.protocol_version, meta, dctx.message_metadata)
248+
ctx = self._make_context(dctx, meta, version)
255249
is_spec_method = method in _methods.SPEC_CLIENT_METHODS
256250

257251
async def _inner() -> HandlerResult:
@@ -318,8 +312,9 @@ async def _on_notify(
318312
method: str,
319313
params: Mapping[str, Any] | None,
320314
) -> None:
321-
version = _resolve_protocol_version(self.connection.protocol_version, params, dctx.message_metadata)
322-
ctx = self._make_context(dctx, _extract_meta(params), version)
315+
meta = _extract_meta(params)
316+
version = _resolve_protocol_version(self.connection.protocol_version, meta, dctx.message_metadata)
317+
ctx = self._make_context(dctx, meta, version)
323318

324319
async def _inner() -> None:
325320
if method in _methods.SPEC_CLIENT_NOTIFICATION_METHODS:

src/mcp/server/session.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,8 @@ def client_params(self) -> types.InitializeRequestParams | None:
5555
def protocol_version(self) -> str | None:
5656
"""The protocol version negotiated during `initialize`.
5757
58-
`None` before initialization completes. Stateless connections don't
59-
require the handshake, so this is normally `None` there. For the
60-
version that applies to the current request - which is always set,
61-
including on stateless connections - read `ctx.protocol_version`
62-
instead.
58+
`None` before initialization, and normally `None` on stateless
59+
connections. For the per-request value, read `ctx.protocol_version`.
6360
"""
6461
return self._connection.protocol_version
6562

src/mcp/shared/message.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,8 @@ class ServerMessageMetadata:
3535
# transports, None for stdio). Typed as Any because the server layer is
3636
# transport-agnostic.
3737
request_context: Any = None
38-
# Protocol version the transport derived for this inbound message (the
39-
# validated `MCP-Protocol-Version` header on streamable HTTP). `None` when
40-
# the transport has no per-message version signal.
38+
# Per-message protocol version observed by the transport (e.g. the
39+
# validated MCP-Protocol-Version header).
4140
protocol_version: str | None = None
4241
# Callback to close SSE stream for the current request without terminating
4342
close_sse_stream: CloseSSEStreamCallback | None = None

tests/server/test_runner.py

Lines changed: 11 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
PaginatedRequestParams,
4444
ProgressNotificationParams,
4545
RequestParams,
46+
RequestParamsMeta,
4647
SetLevelRequestParams,
4748
Tool,
4849
)
@@ -654,69 +655,49 @@ async def on_roots(ctx: Ctx, params: NotificationParams | None) -> None:
654655

655656

656657
def test_resolve_protocol_version_handshake_committed_value_wins():
657-
"""A handshake-committed version governs the whole connection; per-request
658-
`_meta` and the transport hint are ignored."""
659658
md = ServerMessageMetadata(protocol_version="2025-03-26")
660-
params = {"_meta": {PROTOCOL_VERSION_META_KEY: "2025-03-26"}}
661-
assert _resolve_protocol_version("2025-06-18", params, md) == "2025-06-18"
659+
meta: RequestParamsMeta = {PROTOCOL_VERSION_META_KEY: "2025-03-26"}
660+
assert _resolve_protocol_version("2025-06-18", meta, md) == "2025-06-18"
662661

663662

664663
def test_resolve_protocol_version_reads_per_request_meta_when_no_handshake():
665-
"""With no handshake, a supported `_meta` value is the answer (even with a
666-
transport hint also present)."""
667664
md = ServerMessageMetadata(protocol_version="2025-03-26")
668-
params = {"_meta": {PROTOCOL_VERSION_META_KEY: "2025-06-18"}}
669-
assert _resolve_protocol_version(None, params, md) == "2025-06-18"
665+
meta: RequestParamsMeta = {PROTOCOL_VERSION_META_KEY: "2025-06-18"}
666+
assert _resolve_protocol_version(None, meta, md) == "2025-06-18"
670667

671668

672669
def test_resolve_protocol_version_skips_unsupported_meta_value():
673-
"""A `_meta` value the SDK does not serve falls through to the transport
674-
hint rather than poisoning surface validation."""
675670
md = ServerMessageMetadata(protocol_version="2025-03-26")
676-
params = {"_meta": {PROTOCOL_VERSION_META_KEY: "1900-01-01"}}
677-
assert _resolve_protocol_version(None, params, md) == "2025-03-26"
671+
meta: RequestParamsMeta = {PROTOCOL_VERSION_META_KEY: "1900-01-01"}
672+
assert _resolve_protocol_version(None, meta, md) == "2025-03-26"
678673

679674

680675
def test_resolve_protocol_version_skips_non_string_meta_value():
681676
md = ServerMessageMetadata(protocol_version="2025-03-26")
682-
params: dict[str, Any] = {"_meta": {PROTOCOL_VERSION_META_KEY: 42}}
683-
assert _resolve_protocol_version(None, params, md) == "2025-03-26"
677+
meta: RequestParamsMeta = {PROTOCOL_VERSION_META_KEY: 42}
678+
assert _resolve_protocol_version(None, meta, md) == "2025-03-26"
684679

685680

686681
def test_resolve_protocol_version_reads_transport_hint_when_no_handshake_or_meta():
687-
"""The streamable-HTTP header path: stateless connection, no `_meta`."""
688682
md = ServerMessageMetadata(protocol_version="2025-06-18")
689-
assert _resolve_protocol_version(None, {"name": "x"}, md) == "2025-06-18"
690683
assert _resolve_protocol_version(None, None, md) == "2025-06-18"
684+
assert _resolve_protocol_version(None, {}, md) == "2025-06-18"
691685

692686

693687
def test_resolve_protocol_version_skips_unsupported_transport_hint():
694-
"""The transport may pass through an unvalidated value (the raw `initialize`
695-
params version on streamable HTTP); a value the SDK does not serve falls
696-
through to the terminal default."""
688+
"""The `initialize` params version reaches the metadata unvalidated; surface validation must never see it."""
697689
md = ServerMessageMetadata(protocol_version="1900-01-01")
698690
assert _resolve_protocol_version(None, None, md) == "2025-11-25"
699691

700692

701693
def test_resolve_protocol_version_terminal_default_with_no_signals():
702-
"""stdio and in-memory transports attach no metadata; before the handshake
703-
only `ping` and `initialize` reach the runner, and both exist at the last
704-
handshake-based revision."""
705694
assert _resolve_protocol_version(None, None, None) == "2025-11-25"
706-
assert _resolve_protocol_version(None, {}, None) == "2025-11-25"
707695
assert _resolve_protocol_version(None, None, ServerMessageMetadata()) == "2025-11-25"
708696
assert _resolve_protocol_version(None, None, ClientMessageMetadata()) == "2025-11-25"
709697

710698

711-
def test_resolve_protocol_version_ignores_non_mapping_meta():
712-
assert _resolve_protocol_version(None, {"_meta": "oops"}, None) == "2025-11-25"
713-
714-
715699
@pytest.mark.anyio
716700
async def test_runner_ctx_protocol_version_is_terminal_default_on_stateless_in_memory(server: SrvT):
717-
"""In-memory transport attaches no per-message metadata, so on a stateless
718-
connection `ctx.protocol_version` is the resolver's terminal default while
719-
the handshake-only `ctx.session.protocol_version` stays `None`."""
720701
async with connected_runner(server, initialized=False, stateless=True) as (client, runner):
721702
await client.send_raw_request("tools/list", None)
722703
ctx = _seen_ctx[0]

tests/shared/test_streamable_http.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1371,10 +1371,7 @@ async def stateless_context_app() -> AsyncIterator[Starlette]:
13711371
async def test_streamablehttp_stateless_ctx_protocol_version_tracks_the_header(
13721372
stateless_context_app: Starlette, header_value: str | None, expected: str
13731373
) -> None:
1374-
"""A stateless server has no handshake-committed version; the validated
1375-
`MCP-Protocol-Version` header reaches the handler as `ctx.protocol_version`
1376-
(with the spec's `2025-03-26` default when absent) while the handshake-only
1377-
`ctx.session.protocol_version` stays `None`."""
1374+
"""No handshake on stateless: the header (or the spec's 2025-03-26 default) reaches `ctx.protocol_version`."""
13781375
body = JSONRPCRequest(
13791376
jsonrpc="2.0",
13801377
id=1,

0 commit comments

Comments
 (0)