|
18 | 18 | from mcp.server.context import ServerRequestContext |
19 | 19 | from mcp.server.lowlevel.server import NotificationOptions, Server |
20 | 20 | from mcp.server.models import InitializationOptions |
21 | | -from mcp.server.runner import ServerRunner, _extract_meta, otel_middleware |
| 21 | +from mcp.server.runner import ServerRunner, _extract_meta, _resolve_protocol_version, otel_middleware |
22 | 22 | from mcp.server.session import ServerSession |
23 | 23 | from mcp.shared.dispatcher import DispatchContext, DispatchMiddleware, OnRequest |
24 | 24 | from mcp.shared.exceptions import MCPError |
25 | 25 | from mcp.shared.jsonrpc_dispatcher import JSONRPCDispatcher |
| 26 | +from mcp.shared.message import ClientMessageMetadata, ServerMessageMetadata |
26 | 27 | from mcp.shared.peer import dump_params |
27 | 28 | from mcp.shared.transport_context import TransportContext |
28 | 29 | from mcp.shared.version import SUPPORTED_PROTOCOL_VERSIONS |
|
31 | 32 | INVALID_PARAMS, |
32 | 33 | LATEST_PROTOCOL_VERSION, |
33 | 34 | METHOD_NOT_FOUND, |
| 35 | + PROTOCOL_VERSION_META_KEY, |
34 | 36 | CallToolRequestParams, |
35 | 37 | ClientCapabilities, |
36 | 38 | ErrorData, |
|
41 | 43 | PaginatedRequestParams, |
42 | 44 | ProgressNotificationParams, |
43 | 45 | RequestParams, |
| 46 | + RequestParamsMeta, |
44 | 47 | SetLevelRequestParams, |
45 | 48 | Tool, |
46 | 49 | ) |
@@ -218,6 +221,7 @@ async def test_runner_routes_to_handler_and_builds_context(server: SrvT): |
218 | 221 | assert isinstance(ctx.session, ServerSession) |
219 | 222 | assert ctx.session is runner.session |
220 | 223 | assert ctx.request_id is not None |
| 224 | + assert ctx.protocol_version == LATEST_PROTOCOL_VERSION |
221 | 225 |
|
222 | 226 |
|
223 | 227 | @pytest.mark.anyio |
@@ -650,6 +654,65 @@ async def on_roots(ctx: Ctx, params: NotificationParams | None) -> None: |
650 | 654 | ] |
651 | 655 |
|
652 | 656 |
|
| 657 | +def test_resolve_protocol_version_handshake_committed_value_wins(): |
| 658 | + md = ServerMessageMetadata(protocol_version="2025-03-26") |
| 659 | + meta: RequestParamsMeta = {PROTOCOL_VERSION_META_KEY: "2025-03-26"} |
| 660 | + assert _resolve_protocol_version("2025-06-18", meta, md) == "2025-06-18" |
| 661 | + |
| 662 | + |
| 663 | +def test_resolve_protocol_version_reads_per_request_meta_when_no_handshake(): |
| 664 | + md = ServerMessageMetadata(protocol_version="2025-03-26") |
| 665 | + meta: RequestParamsMeta = {PROTOCOL_VERSION_META_KEY: "2025-06-18"} |
| 666 | + assert _resolve_protocol_version(None, meta, md) == "2025-06-18" |
| 667 | + |
| 668 | + |
| 669 | +def test_resolve_protocol_version_skips_unsupported_meta_value(): |
| 670 | + md = ServerMessageMetadata(protocol_version="2025-03-26") |
| 671 | + meta: RequestParamsMeta = {PROTOCOL_VERSION_META_KEY: "1900-01-01"} |
| 672 | + assert _resolve_protocol_version(None, meta, md) == "2025-03-26" |
| 673 | + |
| 674 | + |
| 675 | +def test_resolve_protocol_version_skips_non_string_meta_value(): |
| 676 | + md = ServerMessageMetadata(protocol_version="2025-03-26") |
| 677 | + meta: RequestParamsMeta = {PROTOCOL_VERSION_META_KEY: 42} |
| 678 | + assert _resolve_protocol_version(None, meta, md) == "2025-03-26" |
| 679 | + |
| 680 | + |
| 681 | +def test_resolve_protocol_version_reads_transport_hint_when_no_handshake_or_meta(): |
| 682 | + md = ServerMessageMetadata(protocol_version="2025-06-18") |
| 683 | + assert _resolve_protocol_version(None, None, md) == "2025-06-18" |
| 684 | + assert _resolve_protocol_version(None, {}, md) == "2025-06-18" |
| 685 | + |
| 686 | + |
| 687 | +def test_resolve_protocol_version_skips_unsupported_transport_hint(): |
| 688 | + """The `initialize` params version reaches the metadata unvalidated; surface validation must never see it.""" |
| 689 | + md = ServerMessageMetadata(protocol_version="1900-01-01") |
| 690 | + assert _resolve_protocol_version(None, None, md) == "2025-11-25" |
| 691 | + |
| 692 | + |
| 693 | +def test_resolve_protocol_version_terminal_default_with_no_signals(): |
| 694 | + assert _resolve_protocol_version(None, None, None) == "2025-11-25" |
| 695 | + assert _resolve_protocol_version(None, None, ServerMessageMetadata()) == "2025-11-25" |
| 696 | + assert _resolve_protocol_version(None, None, ClientMessageMetadata()) == "2025-11-25" |
| 697 | + |
| 698 | + |
| 699 | +@pytest.mark.anyio |
| 700 | +async def test_runner_ctx_protocol_version_is_terminal_default_on_stateless_in_memory(server: SrvT): |
| 701 | + async with connected_runner(server, initialized=False, stateless=True) as (client, runner): |
| 702 | + await client.send_raw_request("tools/list", None) |
| 703 | + ctx = _seen_ctx[0] |
| 704 | + assert ctx.protocol_version == "2025-11-25" |
| 705 | + assert ctx.session.protocol_version is None |
| 706 | + assert runner.connection.protocol_version is None |
| 707 | + |
| 708 | + |
| 709 | +@pytest.mark.anyio |
| 710 | +async def test_runner_ctx_protocol_version_tracks_per_request_meta_on_stateless(server: SrvT): |
| 711 | + async with connected_runner(server, initialized=False, stateless=True) as (client, _): |
| 712 | + await client.send_raw_request("tools/list", {"_meta": {PROTOCOL_VERSION_META_KEY: "2025-06-18"}}) |
| 713 | + assert _seen_ctx[0].protocol_version == "2025-06-18" |
| 714 | + |
| 715 | + |
653 | 716 | def test_extract_meta_returns_none_for_absent_or_malformed(): |
654 | 717 | """Context construction is independent of `_meta` validity; the params |
655 | 718 | validation inside `call_next()` is what surfaces the error.""" |
|
0 commit comments