Skip to content

Commit 20dd946

Browse files
authored
feat(client): store InitializeResult as initialize_result (#2300)
1 parent 67201a9 commit 20dd946

File tree

7 files changed

+64
-37
lines changed

7 files changed

+64
-37
lines changed

docs/migration.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,30 @@ result = await session.list_resources(params=PaginatedRequestParams(cursor="next
169169
result = await session.list_tools(params=PaginatedRequestParams(cursor="next_page_token"))
170170
```
171171

172+
### `ClientSession.get_server_capabilities()` replaced by `initialize_result` property
173+
174+
`ClientSession` now stores the full `InitializeResult` via an `initialize_result` property. This provides access to `server_info`, `capabilities`, `instructions`, and the negotiated `protocol_version` through a single property. The `get_server_capabilities()` method has been removed.
175+
176+
**Before (v1):**
177+
178+
```python
179+
capabilities = session.get_server_capabilities()
180+
# server_info, instructions, protocol_version were not stored — had to capture initialize() return value
181+
```
182+
183+
**After (v2):**
184+
185+
```python
186+
result = session.initialize_result
187+
if result is not None:
188+
capabilities = result.capabilities
189+
server_info = result.server_info
190+
instructions = result.instructions
191+
version = result.protocol_version
192+
```
193+
194+
The high-level `Client.initialize_result` returns the same `InitializeResult` but is non-nullable — initialization is guaranteed inside the context manager, so no `None` check is needed. This replaces v1's `Client.server_capabilities`; use `client.initialize_result.capabilities` instead.
195+
172196
### `McpError` renamed to `MCPError`
173197

174198
The `McpError` exception class has been renamed to `MCPError` for consistent naming with the MCP acronym style used throughout the SDK.

src/mcp/client/client.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
EmptyResult,
2020
GetPromptResult,
2121
Implementation,
22+
InitializeResult,
2223
ListPromptsResult,
2324
ListResourcesResult,
2425
ListResourceTemplatesResult,
@@ -29,7 +30,6 @@
2930
ReadResourceResult,
3031
RequestParamsMeta,
3132
ResourceTemplateReference,
32-
ServerCapabilities,
3333
)
3434

3535

@@ -155,9 +155,16 @@ def session(self) -> ClientSession:
155155
return self._session
156156

157157
@property
158-
def server_capabilities(self) -> ServerCapabilities | None:
159-
"""The server capabilities received during initialization, or None if not yet initialized."""
160-
return self.session.get_server_capabilities()
158+
def initialize_result(self) -> InitializeResult:
159+
"""The server's InitializeResult.
160+
161+
Contains server_info, capabilities, instructions, and the negotiated protocol_version.
162+
Raises RuntimeError if accessed outside the context manager.
163+
"""
164+
result = self.session.initialize_result
165+
if result is None: # pragma: no cover
166+
raise RuntimeError("Client must be used within an async context manager")
167+
return result
161168

162169
async def send_ping(self, *, meta: RequestParamsMeta | None = None) -> EmptyResult:
163170
"""Send a ping request to the server."""

src/mcp/client/session.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ def __init__(
131131
self._logging_callback = logging_callback or _default_logging_callback
132132
self._message_handler = message_handler or _default_message_handler
133133
self._tool_output_schemas: dict[str, dict[str, Any] | None] = {}
134-
self._server_capabilities: types.ServerCapabilities | None = None
134+
self._initialize_result: types.InitializeResult | None = None
135135
self._experimental_features: ExperimentalClientFeatures | None = None
136136

137137
# Experimental: Task handlers (use defaults if not provided)
@@ -185,18 +185,19 @@ async def initialize(self) -> types.InitializeResult:
185185
if result.protocol_version not in SUPPORTED_PROTOCOL_VERSIONS:
186186
raise RuntimeError(f"Unsupported protocol version from the server: {result.protocol_version}")
187187

188-
self._server_capabilities = result.capabilities
188+
self._initialize_result = result
189189

190190
await self.send_notification(types.InitializedNotification())
191191

192192
return result
193193

194-
def get_server_capabilities(self) -> types.ServerCapabilities | None:
195-
"""Return the server capabilities received during initialization.
194+
@property
195+
def initialize_result(self) -> types.InitializeResult | None:
196+
"""The server's InitializeResult. None until initialize() has been called.
196197
197-
Returns None if the session has not been initialized yet.
198+
Contains server_info, capabilities, instructions, and the negotiated protocol_version.
198199
"""
199-
return self._server_capabilities
200+
return self._initialize_result
200201

201202
@property
202203
def experimental(self) -> ExperimentalClientFeatures:

tests/client/test_client.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,14 +99,15 @@ def greeting_prompt(name: str) -> str:
9999
async def test_client_is_initialized(app: MCPServer):
100100
"""Test that the client is initialized after entering context."""
101101
async with Client(app) as client:
102-
assert client.server_capabilities == snapshot(
102+
assert client.initialize_result.capabilities == snapshot(
103103
ServerCapabilities(
104104
experimental={},
105105
prompts=PromptsCapability(list_changed=False),
106106
resources=ResourcesCapability(subscribe=False, list_changed=False),
107107
tools=ToolsCapability(list_changed=False),
108108
)
109109
)
110+
assert client.initialize_result.server_info.name == "test"
110111

111112

112113
async def test_client_with_simple_server(simple_server: Server):

tests/client/test_session.py

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -540,8 +540,8 @@ async def mock_server():
540540

541541

542542
@pytest.mark.anyio
543-
async def test_get_server_capabilities():
544-
"""Test that get_server_capabilities returns None before init and capabilities after"""
543+
async def test_initialize_result():
544+
"""Test that initialize_result is None before init and contains the full result after."""
545545
client_to_server_send, client_to_server_receive = anyio.create_memory_object_stream[SessionMessage](1)
546546
server_to_client_send, server_to_client_receive = anyio.create_memory_object_stream[SessionMessage](1)
547547

@@ -551,6 +551,8 @@ async def test_get_server_capabilities():
551551
resources=types.ResourcesCapability(subscribe=True, list_changed=True),
552552
tools=types.ToolsCapability(list_changed=False),
553553
)
554+
expected_server_info = Implementation(name="mock-server", version="0.1.0")
555+
expected_instructions = "Use the tools wisely."
554556

555557
async def mock_server():
556558
session_message = await client_to_server_receive.receive()
@@ -564,7 +566,8 @@ async def mock_server():
564566
result = InitializeResult(
565567
protocol_version=LATEST_PROTOCOL_VERSION,
566568
capabilities=expected_capabilities,
567-
server_info=Implementation(name="mock-server", version="0.1.0"),
569+
server_info=expected_server_info,
570+
instructions=expected_instructions,
568571
)
569572

570573
async with server_to_client_send:
@@ -590,21 +593,17 @@ async def mock_server():
590593
server_to_client_send,
591594
server_to_client_receive,
592595
):
593-
assert session.get_server_capabilities() is None
596+
assert session.initialize_result is None
594597

595598
tg.start_soon(mock_server)
596599
await session.initialize()
597600

598-
capabilities = session.get_server_capabilities()
599-
assert capabilities is not None
600-
assert capabilities == expected_capabilities
601-
assert capabilities.logging is not None
602-
assert capabilities.prompts is not None
603-
assert capabilities.prompts.list_changed is True
604-
assert capabilities.resources is not None
605-
assert capabilities.resources.subscribe is True
606-
assert capabilities.tools is not None
607-
assert capabilities.tools.list_changed is False
601+
result = session.initialize_result
602+
assert result is not None
603+
assert result.server_info == expected_server_info
604+
assert result.capabilities == expected_capabilities
605+
assert result.instructions == expected_instructions
606+
assert result.protocol_version == LATEST_PROTOCOL_VERSION
608607

609608

610609
@pytest.mark.anyio

tests/client/transports/test_memory.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ async def test_with_mcpserver(mcpserver_server: MCPServer):
6969
async def test_server_is_running(mcpserver_server: MCPServer):
7070
"""Test that the server is running and responding to requests."""
7171
async with Client(mcpserver_server) as client:
72-
assert client.server_capabilities is not None
72+
assert client.initialize_result.capabilities.tools is not None
7373

7474

7575
async def test_list_tools(mcpserver_server: MCPServer):

tests/server/mcpserver/test_integration.py

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,7 @@ async def elicitation_callback(context: RequestContext[ClientSession], params: E
109109
async def test_basic_tools() -> None:
110110
"""Test basic tool functionality."""
111111
async with Client(basic_tool.mcp) as client:
112-
assert client.server_capabilities is not None
113-
assert client.server_capabilities.tools is not None
112+
assert client.initialize_result.capabilities.tools is not None
114113

115114
# Test sum tool
116115
tool_result = await client.call_tool("sum", {"a": 5, "b": 3})
@@ -128,8 +127,7 @@ async def test_basic_tools() -> None:
128127
async def test_basic_resources() -> None:
129128
"""Test basic resource functionality."""
130129
async with Client(basic_resource.mcp) as client:
131-
assert client.server_capabilities is not None
132-
assert client.server_capabilities.resources is not None
130+
assert client.initialize_result.capabilities.resources is not None
133131

134132
# Test document resource
135133
doc_content = await client.read_resource("file://documents/readme")
@@ -151,8 +149,7 @@ async def test_basic_resources() -> None:
151149
async def test_basic_prompts() -> None:
152150
"""Test basic prompt functionality."""
153151
async with Client(basic_prompt.mcp) as client:
154-
assert client.server_capabilities is not None
155-
assert client.server_capabilities.prompts is not None
152+
assert client.initialize_result.capabilities.prompts is not None
156153

157154
# Test review_code prompt
158155
prompts = await client.list_prompts()
@@ -223,8 +220,7 @@ async def progress_callback(progress: float, total: float | None, message: str |
223220
async def test_sampling() -> None:
224221
"""Test sampling (LLM interaction) functionality."""
225222
async with Client(sampling.mcp, sampling_callback=sampling_callback) as client:
226-
assert client.server_capabilities is not None
227-
assert client.server_capabilities.tools is not None
223+
assert client.initialize_result.capabilities.tools is not None
228224

229225
# Test sampling tool
230226
sampling_result = await client.call_tool("generate_poem", {"topic": "nature"})
@@ -294,9 +290,8 @@ async def message_handler(message: RequestResponder[ServerRequest, ClientResult]
294290
async def test_completion() -> None:
295291
"""Test completion (autocomplete) functionality."""
296292
async with Client(completion.mcp) as client:
297-
assert client.server_capabilities is not None
298-
assert client.server_capabilities.resources is not None
299-
assert client.server_capabilities.prompts is not None
293+
assert client.initialize_result.capabilities.resources is not None
294+
assert client.initialize_result.capabilities.prompts is not None
300295

301296
# Test resource completion
302297
completion_result = await client.complete(

0 commit comments

Comments
 (0)