1010from typing import Any
1111
1212import pytest
13+ from mcp_types import SERVER_INFO_META_KEY
14+ from mcp_types .version import MODERN_PROTOCOL_VERSIONS
1315
1416from mcp .client .client import Client
1517from mcp .server import Server
1618from mcp .server .mcpserver import MCPServer
19+ from tests ._stamp import R , Unstamp
20+ from tests ._stamp import unstamped as _strip_required_stamp
1721from tests .interaction ._connect import (
1822 Connect ,
1923 connect_in_memory ,
@@ -39,26 +43,56 @@ def pytest_generate_tests(metafunc: pytest.Metafunc) -> None:
3943 metafunc .parametrize ("connect" , compute_cells (requirements ), indirect = True )
4044
4145
42- @pytest .fixture
43- def connect (request : pytest .FixtureRequest ) -> Connect :
44- """The transport-parametrized connection factory: a test using it runs once per matrix cell.
46+ class CellConnect :
47+ """The cell's connection factory, also naming the cell's `spec_version`.
4548
46- Tests that are tied to one transport (the wire-recording tests, the bare-ClientSession tests,
47- the transport-specific tests under transports/) do not use this fixture and connect directly .
49+ Callable exactly like the `Connect` factories it wraps; the attribute lets
50+ sibling fixtures (`unstamped`) key on the cell's era without re-deriving it .
4851 """
49- transport , spec_version = request .param
50- assert isinstance (transport , str )
51- assert isinstance (spec_version , str )
52- factory = _FACTORIES [transport ]
5352
54- def _connect (server : Server | MCPServer , ** kwargs : Any ) -> AbstractAsyncContextManager [Client ]:
53+ def __init__ (self , factory : Connect , spec_version : str ) -> None :
54+ self ._factory = factory
55+ self .spec_version = spec_version
56+
57+ def __call__ (self , server : Server | MCPServer , ** kwargs : Any ) -> AbstractAsyncContextManager [Client ]:
5558 # The matrix compares exact result payloads, and the 2026-era serverInfo
5659 # `_meta` stamp carries the server version, which defaults to the
5760 # commit-dependent installed package version. Pin it so expected
5861 # payloads stay deterministic across commits.
5962 lowlevel = server ._lowlevel_server if isinstance (server , MCPServer ) else server
6063 if lowlevel .version is None :
6164 lowlevel .version = "1.0.0"
62- return factory (server , spec_version = spec_version , ** kwargs )
65+ return self ._factory (server , spec_version = self .spec_version , ** kwargs )
66+
67+
68+ @pytest .fixture
69+ def connect (request : pytest .FixtureRequest ) -> CellConnect :
70+ """The transport-parametrized connection factory: a test using it runs once per matrix cell.
71+
72+ Tests that are tied to one transport (the wire-recording tests, the bare-ClientSession tests,
73+ the transport-specific tests under transports/) do not use this fixture and connect directly.
74+ """
75+ transport , spec_version = request .param
76+ assert isinstance (transport , str )
77+ assert isinstance (spec_version , str )
78+ return CellConnect (_FACTORIES [transport ], spec_version )
79+
80+
81+ @pytest .fixture
82+ def unstamped (connect : CellConnect ) -> Unstamp :
83+ """The cell's era-aware serverInfo-stamp normalizer, for full-result comparisons.
84+
85+ On a modern cell the stamp MUST be present (asserted) and is stripped so
86+ one expected payload stays valid across eras; on a handshake-era cell the
87+ result must not be stamped at all. Either direction failing is a runner
88+ regression, so the same comparison line enforces both.
89+ """
90+ if connect .spec_version in MODERN_PROTOCOL_VERSIONS :
91+ return _strip_required_stamp
92+
93+ def _assert_never_stamped (result : R ) -> R :
94+ meta = result .meta
95+ assert meta is None or SERVER_INFO_META_KEY not in meta , "handshake-era results are never stamped"
96+ return result
6397
64- return _connect
98+ return _assert_never_stamped
0 commit comments