Skip to content

Commit 47a422c

Browse files
committed
Use MODERN_PROTOCOL_VERSIONS membership instead of a threshold constant
The stateless-era predicate is set membership, not an ordering threshold; drop FIRST_MODERN_VERSION and is_version_at_least at the four call sites. The modern entry now takes the protocol_version that matched (threaded from the manager) instead of hard-coding it.
1 parent 4954ed4 commit 47a422c

6 files changed

Lines changed: 11 additions & 15 deletions

File tree

src/mcp/client/session.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
from mcp.shared.message import ClientMessageMetadata, SessionMessage
2222
from mcp.shared.session import RequestResponder
2323
from mcp.shared.transport_context import TransportContext
24-
from mcp.shared.version import FIRST_MODERN_VERSION, SUPPORTED_PROTOCOL_VERSIONS, is_version_at_least
24+
from mcp.shared.version import MODERN_PROTOCOL_VERSIONS, SUPPORTED_PROTOCOL_VERSIONS
2525
from mcp.types import (
2626
CLIENT_CAPABILITIES_META_KEY,
2727
CLIENT_INFO_META_KEY,
@@ -156,9 +156,7 @@ def __init__(
156156
self._session_read_timeout_seconds = read_timeout_seconds
157157
self._client_info = client_info or DEFAULT_CLIENT_INFO
158158
self._pinned_version = protocol_version
159-
self._stateless_pinned = protocol_version is not None and is_version_at_least(
160-
protocol_version, FIRST_MODERN_VERSION
161-
)
159+
self._stateless_pinned = protocol_version in MODERN_PROTOCOL_VERSIONS
162160
self._sampling_callback = sampling_callback or _default_sampling_callback
163161
self._sampling_capabilities = sampling_capabilities
164162
self._elicitation_callback = elicitation_callback or _default_elicitation_callback

src/mcp/client/streamable_http.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
from mcp.shared._context_streams import ContextReceiveStream, ContextSendStream, create_context_streams
2222
from mcp.shared._httpx_utils import create_mcp_http_client
2323
from mcp.shared.message import ClientMessageMetadata, SessionMessage
24-
from mcp.shared.version import FIRST_MODERN_VERSION, is_version_at_least
24+
from mcp.shared.version import MODERN_PROTOCOL_VERSIONS
2525
from mcp.types import (
2626
INTERNAL_ERROR,
2727
INVALID_REQUEST,
@@ -107,7 +107,7 @@ def _per_message_headers(self, message: JSONRPCMessage) -> dict[str, str]:
107107
MCP-Protocol-Version is not emitted here — `_prepare_headers()` already adds it
108108
from `self.protocol_version` for every request.
109109
"""
110-
if self.protocol_version is None or not is_version_at_least(self.protocol_version, FIRST_MODERN_VERSION):
110+
if self.protocol_version not in MODERN_PROTOCOL_VERSIONS:
111111
return {}
112112
if not isinstance(message, JSONRPCRequest | JSONRPCNotification):
113113
return {}

src/mcp/server/_streamable_http_modern.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@
3434
from mcp.shared.exceptions import MCPError, NoBackChannelError
3535
from mcp.shared.message import MessageMetadata, ServerMessageMetadata
3636
from mcp.shared.transport_context import TransportContext
37-
from mcp.shared.version import FIRST_MODERN_VERSION as MODERN_PROTOCOL_VERSION
3837
from mcp.types import (
3938
INTERNAL_ERROR,
4039
INVALID_PARAMS,
@@ -160,14 +159,16 @@ async def handle(self, req: JSONRPCRequest, on_request: OnRequest) -> JSONRPCRes
160159
async def handle_modern_request(
161160
app: Server[Any],
162161
security_settings: TransportSecuritySettings | None,
162+
protocol_version: str,
163163
scope: Scope,
164164
receive: Receive,
165165
send: Send,
166166
) -> None:
167-
"""ASGI handler for a single 2026-07-28 POST.
167+
"""ASGI handler for a single stateless-era POST.
168168
169169
Called from `StreamableHTTPSessionManager.handle_request` when the
170-
`MCP-Protocol-Version` header is `2026-07-28`. Never sets `Mcp-Session-Id`.
170+
`MCP-Protocol-Version` header is in `MODERN_PROTOCOL_VERSIONS`; the header
171+
value is passed as `protocol_version`. Never sets `Mcp-Session-Id`.
171172
"""
172173
request = Request(scope, receive)
173174

@@ -207,7 +208,7 @@ async def handle_modern_request(
207208
stateless=True,
208209
dispatch_middleware=[otel_middleware],
209210
)
210-
runner.connection.protocol_version = MODERN_PROTOCOL_VERSION
211+
runner.connection.protocol_version = protocol_version
211212
try:
212213
msg = await dispatcher.handle(req, runner._compose_on_request()) # type: ignore[reportPrivateUsage]
213214
finally:

src/mcp/server/streamable_http_manager.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ async def handle_request(self, scope: Scope, receive: Receive, send: Send) -> No
156156
# (per SEP-2575) is a follow-up. 2025 paths below remain unchanged.
157157
pv = next((v.decode("latin-1") for k, v in scope["headers"] if k == b"mcp-protocol-version"), None)
158158
if pv in MODERN_PROTOCOL_VERSIONS:
159-
await handle_modern_request(self.app, self.security_settings, scope, receive, send)
159+
await handle_modern_request(self.app, self.security_settings, pv, scope, receive, send)
160160
return
161161

162162
# Dispatch to the appropriate handler

src/mcp/shared/version.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,6 @@
2020
)
2121
"""Every released protocol revision, oldest to newest."""
2222

23-
FIRST_MODERN_VERSION: Final[str] = "2026-07-28"
24-
"""First protocol revision with the stateless per-request envelope (no `initialize`)."""
25-
2623
MODERN_PROTOCOL_VERSIONS: Final[tuple[str, ...]] = ("2026-07-28",)
2724
"""Protocol revisions that use the stateless per-request envelope."""
2825

tests/server/test_streamable_http_modern.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ async def on_request(ctx: DispatchContext[Any], method: str, params: Mapping[str
9191

9292
def _asgi_client(server: Server[Any], security_settings: TransportSecuritySettings | None = None) -> httpx.AsyncClient:
9393
async def app(scope: Scope, receive: Receive, send: Send) -> None:
94-
await handle_modern_request(server, security_settings, scope, receive, send)
94+
await handle_modern_request(server, security_settings, "2026-07-28", scope, receive, send)
9595

9696
return httpx.AsyncClient(transport=httpx.ASGITransport(app=app), base_url="http://testserver")
9797

0 commit comments

Comments
 (0)