|
21 | 21 | from mcp.shared.message import ClientMessageMetadata, SessionMessage |
22 | 22 | from mcp.shared.session import RequestResponder |
23 | 23 | from mcp.shared.transport_context import TransportContext |
24 | | -from mcp.shared.version import SUPPORTED_PROTOCOL_VERSIONS, is_version_at_least |
| 24 | +from mcp.shared.version import FIRST_MODERN_VERSION, SUPPORTED_PROTOCOL_VERSIONS, is_version_at_least |
25 | 25 | from mcp.types import ( |
26 | 26 | CLIENT_CAPABILITIES_META_KEY, |
27 | 27 | CLIENT_INFO_META_KEY, |
@@ -156,15 +156,29 @@ def __init__( |
156 | 156 | self._session_read_timeout_seconds = read_timeout_seconds |
157 | 157 | self._client_info = client_info or DEFAULT_CLIENT_INFO |
158 | 158 | self._pinned_version = protocol_version |
159 | | - self._stateless_pinned = protocol_version is not None and is_version_at_least(protocol_version, "2026-07-28") |
| 159 | + self._stateless_pinned = protocol_version is not None and is_version_at_least( |
| 160 | + protocol_version, FIRST_MODERN_VERSION |
| 161 | + ) |
160 | 162 | self._sampling_callback = sampling_callback or _default_sampling_callback |
161 | 163 | self._sampling_capabilities = sampling_capabilities |
162 | 164 | self._elicitation_callback = elicitation_callback or _default_elicitation_callback |
163 | 165 | self._list_roots_callback = list_roots_callback or _default_list_roots_callback |
164 | 166 | self._logging_callback = logging_callback or _default_logging_callback |
165 | 167 | self._message_handler = message_handler or _default_message_handler |
166 | 168 | self._tool_output_schemas: dict[str, dict[str, Any] | None] = {} |
167 | | - self._initialize_result: types.InitializeResult | None = None |
| 169 | + self._initialize_result: types.InitializeResult | None |
| 170 | + if self._stateless_pinned: |
| 171 | + assert protocol_version is not None |
| 172 | + # A stateless-pinned session is born initialized: there is no handshake |
| 173 | + # at 2026-07-28+, so we synthesize the result locally. `server_info` is a |
| 174 | + # placeholder until `server/discover` is implemented to populate it. |
| 175 | + self._initialize_result = types.InitializeResult( |
| 176 | + protocol_version=protocol_version, |
| 177 | + capabilities=types.ServerCapabilities(), |
| 178 | + server_info=types.Implementation(name="", version=""), |
| 179 | + ) |
| 180 | + else: |
| 181 | + self._initialize_result = None |
168 | 182 | self._task_group: anyio.abc.TaskGroup | None = None |
169 | 183 | if dispatcher is not None: |
170 | 184 | if read_stream is not None or write_stream is not None: |
@@ -300,8 +314,8 @@ def _build_capabilities(self) -> types.ClientCapabilities: |
300 | 314 | return types.ClientCapabilities(sampling=sampling, elicitation=elicitation, experimental=None, roots=roots) |
301 | 315 |
|
302 | 316 | async def initialize(self) -> types.InitializeResult: |
303 | | - if self._stateless_pinned: |
304 | | - raise RuntimeError("initialize() must not be called on a session pinned to a stateless protocol version") |
| 317 | + if self._initialize_result is not None: |
| 318 | + return self._initialize_result |
305 | 319 | capabilities = self._build_capabilities() |
306 | 320 | result = await self.send_request( |
307 | 321 | types.InitializeRequest( |
@@ -329,7 +343,11 @@ async def initialize(self) -> types.InitializeResult: |
329 | 343 | def initialize_result(self) -> types.InitializeResult | None: |
330 | 344 | """The server's InitializeResult. None until initialize() has been called. |
331 | 345 |
|
332 | | - Contains server_info, capabilities, instructions, and the negotiated protocol_version. |
| 346 | + A stateless-pinned session (protocol_version >= 2026-07-28) is born |
| 347 | + initialized: this property is populated at construction with a |
| 348 | + synthesized result and `initialize()` returns it without touching the |
| 349 | + wire. Contains server_info, capabilities, instructions, and the |
| 350 | + negotiated protocol_version. |
333 | 351 | """ |
334 | 352 | return self._initialize_result |
335 | 353 |
|
|
0 commit comments