|
11 | 11 | import pydantic |
12 | 12 | import pytest |
13 | 13 | from mcp_types import methods |
14 | | -from mcp_types.version import KNOWN_PROTOCOL_VERSIONS |
| 14 | +from mcp_types.version import HANDSHAKE_PROTOCOL_VERSIONS, KNOWN_PROTOCOL_VERSIONS |
15 | 15 | from pydantic import BaseModel |
16 | 16 |
|
17 | 17 | # Transcribed from each schema's ClientRequest/ServerRequest/ClientNotification/ |
@@ -470,6 +470,74 @@ def test_discovery_capabilities_preserve_nested_json_values() -> None: |
470 | 470 | assert methods.serialize_server_result("server/discover", "2026-07-28", raw) == raw |
471 | 471 |
|
472 | 472 |
|
| 473 | +@pytest.mark.parametrize("version", HANDSHAKE_PROTOCOL_VERSIONS) |
| 474 | +@pytest.mark.parametrize( |
| 475 | + "extensions", |
| 476 | + [ |
| 477 | + {}, |
| 478 | + {"io.modelcontextprotocol/ui": {}}, |
| 479 | + {"com.example/settings": {"nested": [None, True, 1, 1.5, "text", {"child": [False]}]}}, |
| 480 | + ], |
| 481 | +) |
| 482 | +def test_initialize_round_trip_preserves_server_extensions(version: str, extensions: dict[str, dict[str, Any]]) -> None: |
| 483 | + """SDK compatibility: legacy initialize responses carry extension advertisements, including opaque settings.""" |
| 484 | + raw = { |
| 485 | + "protocolVersion": version, |
| 486 | + "capabilities": {"tools": {}, "extensions": extensions}, |
| 487 | + "serverInfo": {"name": "server", "version": "1"}, |
| 488 | + } |
| 489 | + serialized = methods.serialize_server_result("initialize", version, raw) |
| 490 | + assert serialized == raw |
| 491 | + parsed = methods.parse_server_result("initialize", version, serialized) |
| 492 | + assert isinstance(parsed, types.InitializeResult) |
| 493 | + assert parsed.capabilities.extensions == extensions |
| 494 | + |
| 495 | + |
| 496 | +@pytest.mark.parametrize("version", HANDSHAKE_PROTOCOL_VERSIONS) |
| 497 | +@pytest.mark.parametrize( |
| 498 | + "extensions", |
| 499 | + [ |
| 500 | + {}, |
| 501 | + {"io.modelcontextprotocol/ui": {}}, |
| 502 | + {"com.example/settings": {"nested": [None, True, 1, 1.5, "text", {"child": [False]}]}}, |
| 503 | + ], |
| 504 | +) |
| 505 | +def test_initialize_round_trip_preserves_client_extensions(version: str, extensions: dict[str, dict[str, Any]]) -> None: |
| 506 | + """SDK compatibility: the legacy request wire model preserves the same extension settings as the public parser.""" |
| 507 | + raw = { |
| 508 | + "jsonrpc": "2.0", |
| 509 | + "id": 1, |
| 510 | + "method": "initialize", |
| 511 | + "params": { |
| 512 | + "protocolVersion": version, |
| 513 | + "capabilities": {"extensions": extensions}, |
| 514 | + "clientInfo": {"name": "client", "version": "1"}, |
| 515 | + }, |
| 516 | + } |
| 517 | + surface = methods.CLIENT_REQUESTS[("initialize", version)] |
| 518 | + serialized = surface.model_validate(raw).model_dump(by_alias=True, mode="json", exclude_none=True) |
| 519 | + assert serialized == raw |
| 520 | + parsed = methods.parse_client_request("initialize", version, serialized["params"]) |
| 521 | + assert isinstance(parsed, types.InitializeRequest) |
| 522 | + assert parsed.params.capabilities.extensions == extensions |
| 523 | + |
| 524 | + |
| 525 | +@pytest.mark.parametrize("version", HANDSHAKE_PROTOCOL_VERSIONS) |
| 526 | +@pytest.mark.parametrize("capabilities", [{}, {"extensions": None}]) |
| 527 | +def test_initialize_omits_unset_server_extensions(version: str, capabilities: dict[str, Any]) -> None: |
| 528 | + """SDK-defined: absent or null extension advertisements remain omitted on the legacy wire.""" |
| 529 | + raw = { |
| 530 | + "protocolVersion": version, |
| 531 | + "capabilities": capabilities, |
| 532 | + "serverInfo": {"name": "server", "version": "1"}, |
| 533 | + } |
| 534 | + serialized = methods.serialize_server_result("initialize", version, raw) |
| 535 | + assert serialized == {**raw, "capabilities": {}} |
| 536 | + parsed = methods.parse_server_result("initialize", version, serialized) |
| 537 | + assert isinstance(parsed, types.InitializeResult) |
| 538 | + assert parsed.capabilities.extensions is None |
| 539 | + |
| 540 | + |
473 | 541 | def test_elicit_request_surface_accepts_loose_property_schemas(): |
474 | 542 | """Older python-sdk emits `anyOf` for `Optional` form fields; the surface gate must let it through.""" |
475 | 543 | params = { |
|
0 commit comments