Skip to content

Commit b326347

Browse files
committed
Add named protocol-version scalars and replace tuple indexing
shared/version.py gains four derived constants alongside the existing tuples: LATEST_PROTOCOL_VERSION (now derived here instead of a duplicate literal in types/_types.py), LATEST_HANDSHAKE_VERSION, LATEST_MODERN_VERSION, and OLDEST_SUPPORTED_VERSION. Call sites that previously wrote HANDSHAKE_PROTOCOL_VERSIONS[-1] / MODERN_PROTOCOL_VERSIONS[-1] / [0] now import the named scalar so the meaning is explicit at the use site and a future version bump is one edit. This also fixes a quiet drift: a handful of tests were passing LATEST_PROTOCOL_VERSION (now "2026-07-28") into InitializeRequest on the legacy handshake path; those now use LATEST_HANDSHAKE_VERSION.
1 parent cdfdfd1 commit b326347

14 files changed

Lines changed: 73 additions & 64 deletions

File tree

src/mcp/client/session.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,12 @@
2727
from mcp.shared.message import ClientMessageMetadata, SessionMessage
2828
from mcp.shared.session import RequestResponder
2929
from mcp.shared.transport_context import TransportContext
30-
from mcp.shared.version import HANDSHAKE_PROTOCOL_VERSIONS, MODERN_PROTOCOL_VERSIONS
30+
from mcp.shared.version import (
31+
HANDSHAKE_PROTOCOL_VERSIONS,
32+
LATEST_HANDSHAKE_VERSION,
33+
LATEST_MODERN_VERSION,
34+
MODERN_PROTOCOL_VERSIONS,
35+
)
3136
from mcp.types import (
3237
CLIENT_CAPABILITIES_META_KEY,
3338
CLIENT_INFO_META_KEY,
@@ -333,7 +338,7 @@ async def initialize(self) -> types.InitializeResult:
333338
result = await self.send_request(
334339
types.InitializeRequest(
335340
params=types.InitializeRequestParams(
336-
protocol_version=HANDSHAKE_PROTOCOL_VERSIONS[-1],
341+
protocol_version=LATEST_HANDSHAKE_VERSION,
337342
capabilities=self._build_capabilities(),
338343
client_info=self._client_info,
339344
),
@@ -413,7 +418,7 @@ async def probe(version: str) -> dict[str, Any]:
413418
return await self._dispatcher.send_raw_request("server/discover", params, opts)
414419

415420
try:
416-
raw = await probe(MODERN_PROTOCOL_VERSIONS[-1])
421+
raw = await probe(LATEST_MODERN_VERSION)
417422
except MCPError as e:
418423
if e.code != UNSUPPORTED_PROTOCOL_VERSION:
419424
raise

src/mcp/server/connection.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
from mcp.shared.dispatcher import CallOptions, Outbound
3232
from mcp.shared.exceptions import MCPDeprecationWarning, NoBackChannelError
3333
from mcp.shared.peer import Meta, dump_params
34-
from mcp.shared.version import HANDSHAKE_PROTOCOL_VERSIONS
34+
from mcp.shared.version import LATEST_HANDSHAKE_VERSION
3535
from mcp.types import (
3636
ClientCapabilities,
3737
CreateMessageRequest,
@@ -192,14 +192,12 @@ def for_loop(
192192
193193
Not born-ready: `initialized` is set later by the kernel when
194194
`notifications/initialized` arrives. `protocol_version` is seeded from
195-
the transport hint (or `HANDSHAKE_PROTOCOL_VERSIONS[-1]`) so it's never `None`;
195+
the transport hint (or `LATEST_HANDSHAKE_VERSION`) so it's never `None`;
196196
the handshake overwrites it once negotiated.
197197
"""
198198
return cls(
199199
outbound,
200-
protocol_version=protocol_version_hint
201-
if protocol_version_hint is not None
202-
else HANDSHAKE_PROTOCOL_VERSIONS[-1],
200+
protocol_version=protocol_version_hint if protocol_version_hint is not None else LATEST_HANDSHAKE_VERSION,
203201
session_id=session_id,
204202
)
205203

src/mcp/shared/version.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,18 @@
3535
Kept as the union for v1.x compatibility.
3636
"""
3737

38+
LATEST_PROTOCOL_VERSION: Final[str] = KNOWN_PROTOCOL_VERSIONS[-1]
39+
"""Newest protocol revision this SDK speaks (any era)."""
40+
41+
LATEST_HANDSHAKE_VERSION: Final[str] = HANDSHAKE_PROTOCOL_VERSIONS[-1]
42+
"""Newest revision reachable via the ``initialize`` handshake; the client's offer and server's counter-offer default."""
43+
44+
LATEST_MODERN_VERSION: Final[str] = MODERN_PROTOCOL_VERSIONS[-1]
45+
"""Newest per-request-envelope revision; the ``server/discover`` probe default."""
46+
47+
OLDEST_SUPPORTED_VERSION: Final[str] = HANDSHAKE_PROTOCOL_VERSIONS[0]
48+
"""Oldest revision this SDK still negotiates via the ``initialize`` handshake."""
49+
3850

3951
def is_version_at_least(version: str, minimum: str) -> bool:
4052
"""Return True if `version` is a known revision at least as new as `minimum`.

src/mcp/types/_types.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,9 @@
2020
from pydantic.alias_generators import to_camel
2121
from typing_extensions import NotRequired, TypedDict
2222

23+
from mcp.shared.version import LATEST_PROTOCOL_VERSION as LATEST_PROTOCOL_VERSION
2324
from mcp.types.jsonrpc import RequestId
2425

25-
LATEST_PROTOCOL_VERSION: Final[str] = "2026-07-28"
26-
"""The newest protocol version this SDK can negotiate.
27-
28-
See https://modelcontextprotocol.io/specification/latest.
29-
"""
30-
3126
DEFAULT_NEGOTIATED_VERSION: Final[str] = "2025-03-26"
3227
"""The default negotiated version of the Model Context Protocol when no version is specified.
3328

tests/client/test_session.py

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
from mcp.shared.message import SessionMessage
1919
from mcp.shared.session import RequestResponder
2020
from mcp.shared.transport_context import TransportContext
21-
from mcp.shared.version import HANDSHAKE_PROTOCOL_VERSIONS
21+
from mcp.shared.version import HANDSHAKE_PROTOCOL_VERSIONS, LATEST_HANDSHAKE_VERSION
2222
from mcp.types import (
2323
CONNECTION_CLOSED,
2424
INTERNAL_ERROR,
@@ -87,7 +87,7 @@ async def mock_server():
8787
assert isinstance(request, InitializeRequest)
8888

8989
result = InitializeResult(
90-
protocol_version=HANDSHAKE_PROTOCOL_VERSIONS[-1],
90+
protocol_version=LATEST_HANDSHAKE_VERSION,
9191
capabilities=ServerCapabilities(
9292
logging=None,
9393
resources=None,
@@ -140,7 +140,7 @@ async def message_handler( # pragma: no cover
140140

141141
# Assert the result
142142
assert isinstance(result, InitializeResult)
143-
assert result.protocol_version == HANDSHAKE_PROTOCOL_VERSIONS[-1]
143+
assert result.protocol_version == LATEST_HANDSHAKE_VERSION
144144
assert isinstance(result.capabilities, ServerCapabilities)
145145
assert result.server_info == Implementation(name="mock-server", version="0.1.0")
146146
assert result.instructions == "The server instructions."
@@ -171,7 +171,7 @@ async def mock_server():
171171
received_client_info = request.params.client_info
172172

173173
result = InitializeResult(
174-
protocol_version=HANDSHAKE_PROTOCOL_VERSIONS[-1],
174+
protocol_version=LATEST_HANDSHAKE_VERSION,
175175
capabilities=ServerCapabilities(),
176176
server_info=Implementation(name="mock-server", version="0.1.0"),
177177
)
@@ -228,7 +228,7 @@ async def mock_server():
228228
received_client_info = request.params.client_info
229229

230230
result = InitializeResult(
231-
protocol_version=HANDSHAKE_PROTOCOL_VERSIONS[-1],
231+
protocol_version=LATEST_HANDSHAKE_VERSION,
232232
capabilities=ServerCapabilities(),
233233
server_info=Implementation(name="mock-server", version="0.1.0"),
234234
)
@@ -278,7 +278,7 @@ async def mock_server():
278278
assert isinstance(request, InitializeRequest)
279279

280280
# Verify client offers the newest handshake protocol version
281-
assert request.params.protocol_version == HANDSHAKE_PROTOCOL_VERSIONS[-1]
281+
assert request.params.protocol_version == LATEST_HANDSHAKE_VERSION
282282

283283
# Server responds with a supported older version
284284
result = InitializeResult(
@@ -386,7 +386,7 @@ async def mock_server():
386386
received_capabilities = request.params.capabilities
387387

388388
result = InitializeResult(
389-
protocol_version=HANDSHAKE_PROTOCOL_VERSIONS[-1],
389+
protocol_version=LATEST_HANDSHAKE_VERSION,
390390
capabilities=ServerCapabilities(),
391391
server_info=Implementation(name="mock-server", version="0.1.0"),
392392
)
@@ -457,7 +457,7 @@ async def mock_server():
457457
received_capabilities = request.params.capabilities
458458

459459
result = InitializeResult(
460-
protocol_version=HANDSHAKE_PROTOCOL_VERSIONS[-1],
460+
protocol_version=LATEST_HANDSHAKE_VERSION,
461461
capabilities=ServerCapabilities(),
462462
server_info=Implementation(name="mock-server", version="0.1.0"),
463463
)
@@ -536,7 +536,7 @@ async def mock_server():
536536
received_capabilities = request.params.capabilities
537537

538538
result = InitializeResult(
539-
protocol_version=HANDSHAKE_PROTOCOL_VERSIONS[-1],
539+
protocol_version=LATEST_HANDSHAKE_VERSION,
540540
capabilities=ServerCapabilities(),
541541
server_info=Implementation(name="mock-server", version="0.1.0"),
542542
)
@@ -604,7 +604,7 @@ async def mock_server():
604604
assert isinstance(request, InitializeRequest)
605605

606606
result = InitializeResult(
607-
protocol_version=HANDSHAKE_PROTOCOL_VERSIONS[-1],
607+
protocol_version=LATEST_HANDSHAKE_VERSION,
608608
capabilities=expected_capabilities,
609609
server_info=expected_server_info,
610610
instructions=expected_instructions,
@@ -643,12 +643,12 @@ async def mock_server():
643643
assert result.server_info == expected_server_info
644644
assert result.capabilities == expected_capabilities
645645
assert result.instructions == expected_instructions
646-
assert result.protocol_version == HANDSHAKE_PROTOCOL_VERSIONS[-1]
646+
assert result.protocol_version == LATEST_HANDSHAKE_VERSION
647647
# Era-neutral accessors are populated from the InitializeResult.
648648
assert session.server_info == expected_server_info
649649
assert session.server_capabilities == expected_capabilities
650650
assert session.instructions == expected_instructions
651-
assert session.protocol_version == HANDSHAKE_PROTOCOL_VERSIONS[-1]
651+
assert session.protocol_version == LATEST_HANDSHAKE_VERSION
652652

653653

654654
@pytest.mark.anyio
@@ -671,7 +671,7 @@ async def mock_server():
671671
assert isinstance(request, InitializeRequest)
672672

673673
result = InitializeResult(
674-
protocol_version=HANDSHAKE_PROTOCOL_VERSIONS[-1],
674+
protocol_version=LATEST_HANDSHAKE_VERSION,
675675
capabilities=ServerCapabilities(),
676676
server_info=Implementation(name="mock-server", version="0.1.0"),
677677
)
@@ -1354,7 +1354,7 @@ async def send_raw_request(
13541354
self.calls.append((method, opts or {}))
13551355
if method == "initialize":
13561356
return InitializeResult(
1357-
protocol_version=HANDSHAKE_PROTOCOL_VERSIONS[-1],
1357+
protocol_version=LATEST_HANDSHAKE_VERSION,
13581358
capabilities=ServerCapabilities(),
13591359
server_info=Implementation(name="mock-server", version="0.1.0"),
13601360
).model_dump(by_alias=True, mode="json", exclude_none=True)

tests/interaction/_connect.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
from mcp.server.streamable_http import EventStore
3232
from mcp.server.streamable_http_manager import StreamableHTTPSessionManager
3333
from mcp.server.transport_security import TransportSecuritySettings
34-
from mcp.shared.version import HANDSHAKE_PROTOCOL_VERSIONS, MODERN_PROTOCOL_VERSIONS
34+
from mcp.shared.version import LATEST_HANDSHAKE_VERSION, MODERN_PROTOCOL_VERSIONS
3535
from mcp.types import (
3636
ClientCapabilities,
3737
Implementation,
@@ -70,7 +70,7 @@ def __call__(
7070
message_handler: MessageHandlerFnT | None = None,
7171
client_info: Implementation | None = None,
7272
elicitation_callback: ElicitationFnT | None = None,
73-
spec_version: str = HANDSHAKE_PROTOCOL_VERSIONS[-1],
73+
spec_version: str = LATEST_HANDSHAKE_VERSION,
7474
) -> AbstractAsyncContextManager[Client]: ...
7575

7676

@@ -85,7 +85,7 @@ async def connect_in_memory(
8585
message_handler: MessageHandlerFnT | None = None,
8686
client_info: Implementation | None = None,
8787
elicitation_callback: ElicitationFnT | None = None,
88-
spec_version: str = HANDSHAKE_PROTOCOL_VERSIONS[-1],
88+
spec_version: str = LATEST_HANDSHAKE_VERSION,
8989
) -> AsyncIterator[Client]:
9090
"""Yield a Client connected to the server over the in-memory transport.
9191
@@ -122,7 +122,7 @@ async def connect_over_streamable_http(
122122
message_handler: MessageHandlerFnT | None = None,
123123
client_info: Implementation | None = None,
124124
elicitation_callback: ElicitationFnT | None = None,
125-
spec_version: str = HANDSHAKE_PROTOCOL_VERSIONS[-1],
125+
spec_version: str = LATEST_HANDSHAKE_VERSION,
126126
) -> AsyncIterator[Client]:
127127
"""Yield a Client connected to the server's streamable HTTP app, entirely in process.
128128
@@ -276,7 +276,7 @@ def base_headers(*, session_id: str | None = None) -> dict[str, str]:
276276
headers = {
277277
"accept": "application/json, text/event-stream",
278278
"content-type": "application/json",
279-
"mcp-protocol-version": HANDSHAKE_PROTOCOL_VERSIONS[-1],
279+
"mcp-protocol-version": LATEST_HANDSHAKE_VERSION,
280280
}
281281
if session_id is not None:
282282
headers["mcp-session-id"] = session_id
@@ -286,7 +286,7 @@ def base_headers(*, session_id: str | None = None) -> dict[str, str]:
286286
def initialize_body(request_id: int = 1) -> dict[str, object]:
287287
"""A wire-level initialize JSON-RPC request body, exactly as an SDK client would send it."""
288288
params = InitializeRequestParams(
289-
protocol_version=HANDSHAKE_PROTOCOL_VERSIONS[-1],
289+
protocol_version=LATEST_HANDSHAKE_VERSION,
290290
capabilities=ClientCapabilities(),
291291
client_info=Implementation(name="raw", version="0.0.0"),
292292
)
@@ -354,7 +354,7 @@ async def connect_over_sse(
354354
message_handler: MessageHandlerFnT | None = None,
355355
client_info: Implementation | None = None,
356356
elicitation_callback: ElicitationFnT | None = None,
357-
spec_version: str = HANDSHAKE_PROTOCOL_VERSIONS[-1],
357+
spec_version: str = LATEST_HANDSHAKE_VERSION,
358358
) -> AsyncIterator[Client]:
359359
"""Yield a Client connected to the server's legacy SSE transport, entirely in process."""
360360
app, _ = build_sse_app(server)

tests/interaction/transports/test_hosting_resume.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
from mcp.client.streamable_http import streamable_http_client
2222
from mcp.server.mcpserver import Context, MCPServer
2323
from mcp.shared.message import ClientMessageMetadata
24-
from mcp.shared.version import HANDSHAKE_PROTOCOL_VERSIONS
24+
from mcp.shared.version import LATEST_HANDSHAKE_VERSION
2525
from mcp.types import (
2626
CallToolRequest,
2727
CallToolRequestParams,
@@ -431,7 +431,7 @@ async def collect(params: LoggingMessageNotificationParams) -> None:
431431
# The session id is only observable via the manager (the client transport does not expose it).
432432
(session_id,) = manager._server_instances
433433
http.headers["mcp-session-id"] = session_id
434-
http.headers["mcp-protocol-version"] = HANDSHAKE_PROTOCOL_VERSIONS[-1]
434+
http.headers["mcp-protocol-version"] = LATEST_HANDSHAKE_VERSION
435435
tg.cancel_scope.cancel()
436436

437437
with anyio.fail_after(5): # pragma: no branch

tests/issues/test_192_request_id.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
from mcp.server.lowlevel import NotificationOptions, Server
55
from mcp.server.models import InitializationOptions
66
from mcp.shared.message import SessionMessage
7+
from mcp.shared.version import LATEST_HANDSHAKE_VERSION
78
from mcp.types import (
8-
LATEST_PROTOCOL_VERSION,
99
ClientCapabilities,
1010
Implementation,
1111
InitializeRequestParams,
@@ -59,7 +59,7 @@ async def run_server():
5959
id="init-1",
6060
method="initialize",
6161
params=InitializeRequestParams(
62-
protocol_version=LATEST_PROTOCOL_VERSION,
62+
protocol_version=LATEST_HANDSHAKE_VERSION,
6363
capabilities=ClientCapabilities(),
6464
client_info=Implementation(name="test-client", version="1.0.0"),
6565
).model_dump(by_alias=True, exclude_none=True),

tests/issues/test_552_windows_hang.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
from mcp import ClientSession, StdioServerParameters
1111
from mcp.client.stdio import stdio_client
12-
from mcp.shared.version import HANDSHAKE_PROTOCOL_VERSIONS
12+
from mcp.shared.version import LATEST_HANDSHAKE_VERSION
1313
from mcp.types import InitializeResult
1414

1515

@@ -33,7 +33,7 @@ async def test_initialize_succeeds_and_shutdown_returns_after_the_server_exits_m
3333
"jsonrpc": "2.0",
3434
"id": request["id"],
3535
"result": {{
36-
"protocolVersion": {json.dumps(HANDSHAKE_PROTOCOL_VERSIONS[-1])},
36+
"protocolVersion": {json.dumps(LATEST_HANDSHAKE_VERSION)},
3737
"capabilities": {{}},
3838
"serverInfo": {{"name": "test-server", "version": "1.0"}}
3939
}}

tests/server/test_cancel_handling.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
from mcp.server import Server, ServerRequestContext
88
from mcp.shared.exceptions import MCPError
99
from mcp.shared.message import SessionMessage
10+
from mcp.shared.version import LATEST_HANDSHAKE_VERSION
1011
from mcp.types import (
11-
LATEST_PROTOCOL_VERSION,
1212
CallToolRequest,
1313
CallToolRequestParams,
1414
CallToolResult,
@@ -138,7 +138,7 @@ async def run_server():
138138
id=1,
139139
method="initialize",
140140
params=InitializeRequestParams(
141-
protocol_version=LATEST_PROTOCOL_VERSION,
141+
protocol_version=LATEST_HANDSHAKE_VERSION,
142142
capabilities=ClientCapabilities(),
143143
client_info=Implementation(name="test", version="1.0"),
144144
).model_dump(by_alias=True, mode="json", exclude_none=True),
@@ -212,7 +212,7 @@ async def run_server():
212212
id=1,
213213
method="initialize",
214214
params=InitializeRequestParams(
215-
protocol_version=LATEST_PROTOCOL_VERSION,
215+
protocol_version=LATEST_HANDSHAKE_VERSION,
216216
capabilities=ClientCapabilities(),
217217
client_info=Implementation(name="test", version="1.0"),
218218
).model_dump(by_alias=True, mode="json", exclude_none=True),

0 commit comments

Comments
 (0)