Skip to content

Commit eb92227

Browse files
committed
simplify(client): collapse to single initialize_result proxy
- Remove _initialize_result captured state (tracked same invariant as _session) - Remove server_capabilities/server_info/server_instructions decomposed properties - Client.initialize_result proxies through session, non-nullable - Unreachable None branch marked pragma: no cover (self.session raises first) server_capabilities had no organic usage beyond a did-init-happen probe; every other result type (CallToolResult, etc.) comes back whole. Github-Issue: #1018
1 parent 3455447 commit eb92227

File tree

4 files changed

+13
-37
lines changed

4 files changed

+13
-37
lines changed

docs/migration.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ if result is not None:
191191
version = result.protocol_version
192192
```
193193

194-
The high-level `Client` exposes these directly as non-nullable properties (initialization is guaranteed inside the context manager): `client.server_capabilities`, `client.server_info`, and `client.server_instructions`.
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.
195195

196196
### `McpError` renamed to `MCPError`
197197

src/mcp/client/client.py

Lines changed: 9 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
ReadResourceResult,
3131
RequestParamsMeta,
3232
ResourceTemplateReference,
33-
ServerCapabilities,
3433
)
3534

3635

@@ -97,7 +96,6 @@ async def main():
9796
"""Callback for handling elicitation requests."""
9897

9998
_session: ClientSession | None = field(init=False, default=None)
100-
_initialize_result: InitializeResult | None = field(init=False, default=None)
10199
_exit_stack: AsyncExitStack | None = field(init=False, default=None)
102100
_transport: Transport = field(init=False)
103101

@@ -131,7 +129,7 @@ async def __aenter__(self) -> Client:
131129
)
132130
)
133131

134-
self._initialize_result = await self._session.initialize()
132+
await self._session.initialize()
135133

136134
# Transfer ownership to self for __aexit__ to handle
137135
self._exit_stack = exit_stack.pop_all()
@@ -142,7 +140,6 @@ async def __aexit__(self, exc_type: type[BaseException] | None, exc_val: BaseExc
142140
if self._exit_stack: # pragma: no branch
143141
await self._exit_stack.__aexit__(exc_type, exc_val, exc_tb)
144142
self._session = None
145-
self._initialize_result = None
146143

147144
@property
148145
def session(self) -> ClientSession:
@@ -158,25 +155,16 @@ def session(self) -> ClientSession:
158155
return self._session
159156

160157
@property
161-
def server_capabilities(self) -> ServerCapabilities:
162-
"""Capabilities the server advertised during initialization."""
163-
if self._initialize_result is None:
164-
raise RuntimeError("Client must be used within an async context manager")
165-
return self._initialize_result.capabilities
166-
167-
@property
168-
def server_info(self) -> Implementation:
169-
"""The server's name, version, and other implementation details."""
170-
if self._initialize_result is None:
171-
raise RuntimeError("Client must be used within an async context manager")
172-
return self._initialize_result.server_info
158+
def initialize_result(self) -> InitializeResult:
159+
"""The server's InitializeResult.
173160
174-
@property
175-
def server_instructions(self) -> str | None:
176-
"""Instructions describing how to use the server and its features, if provided."""
177-
if self._initialize_result is None:
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
178166
raise RuntimeError("Client must be used within an async context manager")
179-
return self._initialize_result.instructions
167+
return result
180168

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

tests/client/test_client.py

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -99,16 +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.server_info.name == "test"
111-
assert client.server_instructions is None
110+
assert client.initialize_result.server_info.name == "test"
112111

113112

114113
async def test_client_with_simple_server(simple_server: Server):
@@ -196,17 +195,6 @@ def test_client_session_property_before_enter(app: MCPServer):
196195
client.session
197196

198197

199-
def test_client_server_properties_before_enter(app: MCPServer):
200-
"""Test that server_* properties raise RuntimeError outside the context manager."""
201-
client = Client(app)
202-
with pytest.raises(RuntimeError, match="Client must be used within an async context manager"):
203-
client.server_capabilities
204-
with pytest.raises(RuntimeError, match="Client must be used within an async context manager"):
205-
client.server_info
206-
with pytest.raises(RuntimeError, match="Client must be used within an async context manager"):
207-
client.server_instructions
208-
209-
210198
async def test_client_reentry_raises_runtime_error(app: MCPServer):
211199
"""Test that reentering a client raises RuntimeError."""
212200
async with Client(app) as client:

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.tools is not None
72+
assert client.initialize_result.capabilities.tools is not None
7373

7474

7575
async def test_list_tools(mcpserver_server: MCPServer):

0 commit comments

Comments
 (0)