diff --git a/docs/migration.md b/docs/migration.md index 7cf032553..d10b48b12 100644 --- a/docs/migration.md +++ b/docs/migration.md @@ -169,6 +169,30 @@ result = await session.list_resources(params=PaginatedRequestParams(cursor="next result = await session.list_tools(params=PaginatedRequestParams(cursor="next_page_token")) ``` +### `ClientSession.get_server_capabilities()` replaced by `server_params` property + +`ClientSession` now stores the full `InitializeResult` via a `server_params` property, mirroring `ServerSession.client_params`. This is the new way to access server metadata after initialization — `server_info`, `capabilities`, `instructions`, and the negotiated `protocol_version` are all available through this single property. The `get_server_capabilities()` method has been removed. + +**Before (v1):** + +```python +capabilities = session.get_server_capabilities() +# server_info, instructions, protocol_version were not stored — had to capture initialize() return value +``` + +**After (v2):** + +```python +params = session.server_params +if params is not None: + capabilities = params.capabilities + server_info = params.server_info + instructions = params.instructions + version = params.protocol_version +``` + +The high-level `Client.server_capabilities` property has similarly been replaced by `Client.server_params`. + ### `McpError` renamed to `MCPError` The `McpError` exception class has been renamed to `MCPError` for consistent naming with the MCP acronym style used throughout the SDK. diff --git a/src/mcp/client/client.py b/src/mcp/client/client.py index 7dc67c584..ab50ae80a 100644 --- a/src/mcp/client/client.py +++ b/src/mcp/client/client.py @@ -19,6 +19,7 @@ EmptyResult, GetPromptResult, Implementation, + InitializeResult, ListPromptsResult, ListResourcesResult, ListResourceTemplatesResult, @@ -29,7 +30,6 @@ ReadResourceResult, RequestParamsMeta, ResourceTemplateReference, - ServerCapabilities, ) @@ -155,9 +155,12 @@ def session(self) -> ClientSession: return self._session @property - def server_capabilities(self) -> ServerCapabilities | None: - """The server capabilities received during initialization, or None if not yet initialized.""" - return self.session.get_server_capabilities() + def server_params(self) -> InitializeResult | None: + """The server's initialization response. None if not yet initialized. + + Contains server_info, capabilities, instructions, and the negotiated protocol_version. + """ + return self.session.server_params async def send_ping(self, *, meta: RequestParamsMeta | None = None) -> EmptyResult: """Send a ping request to the server.""" diff --git a/src/mcp/client/session.py b/src/mcp/client/session.py index a0ca751bd..2b3559358 100644 --- a/src/mcp/client/session.py +++ b/src/mcp/client/session.py @@ -131,7 +131,7 @@ def __init__( self._logging_callback = logging_callback or _default_logging_callback self._message_handler = message_handler or _default_message_handler self._tool_output_schemas: dict[str, dict[str, Any] | None] = {} - self._server_capabilities: types.ServerCapabilities | None = None + self._server_params: types.InitializeResult | None = None self._experimental_features: ExperimentalClientFeatures | None = None # Experimental: Task handlers (use defaults if not provided) @@ -185,18 +185,20 @@ async def initialize(self) -> types.InitializeResult: if result.protocol_version not in SUPPORTED_PROTOCOL_VERSIONS: raise RuntimeError(f"Unsupported protocol version from the server: {result.protocol_version}") - self._server_capabilities = result.capabilities + self._server_params = result await self.send_notification(types.InitializedNotification()) return result - def get_server_capabilities(self) -> types.ServerCapabilities | None: - """Return the server capabilities received during initialization. + @property + def server_params(self) -> types.InitializeResult | None: + """The server's initialization response. None if not yet initialized. - Returns None if the session has not been initialized yet. + Mirrors ServerSession.client_params. Contains server_info, capabilities, + instructions, and the negotiated protocol_version. """ - return self._server_capabilities + return self._server_params @property def experimental(self) -> ExperimentalClientFeatures: diff --git a/tests/client/test_client.py b/tests/client/test_client.py index 45300063a..a32ea279e 100644 --- a/tests/client/test_client.py +++ b/tests/client/test_client.py @@ -99,7 +99,8 @@ def greeting_prompt(name: str) -> str: async def test_client_is_initialized(app: MCPServer): """Test that the client is initialized after entering context.""" async with Client(app) as client: - assert client.server_capabilities == snapshot( + assert client.server_params is not None + assert client.server_params.capabilities == snapshot( ServerCapabilities( experimental={}, prompts=PromptsCapability(list_changed=False), diff --git a/tests/client/test_session.py b/tests/client/test_session.py index d6d13e273..76ac21269 100644 --- a/tests/client/test_session.py +++ b/tests/client/test_session.py @@ -540,8 +540,8 @@ async def mock_server(): @pytest.mark.anyio -async def test_get_server_capabilities(): - """Test that get_server_capabilities returns None before init and capabilities after""" +async def test_server_params(): + """Test that server_params is None before init and contains the full result after.""" client_to_server_send, client_to_server_receive = anyio.create_memory_object_stream[SessionMessage](1) server_to_client_send, server_to_client_receive = anyio.create_memory_object_stream[SessionMessage](1) @@ -551,6 +551,8 @@ async def test_get_server_capabilities(): resources=types.ResourcesCapability(subscribe=True, list_changed=True), tools=types.ToolsCapability(list_changed=False), ) + expected_server_info = Implementation(name="mock-server", version="0.1.0") + expected_instructions = "Use the tools wisely." async def mock_server(): session_message = await client_to_server_receive.receive() @@ -564,7 +566,8 @@ async def mock_server(): result = InitializeResult( protocol_version=LATEST_PROTOCOL_VERSION, capabilities=expected_capabilities, - server_info=Implementation(name="mock-server", version="0.1.0"), + server_info=expected_server_info, + instructions=expected_instructions, ) async with server_to_client_send: @@ -590,21 +593,17 @@ async def mock_server(): server_to_client_send, server_to_client_receive, ): - assert session.get_server_capabilities() is None + assert session.server_params is None tg.start_soon(mock_server) await session.initialize() - capabilities = session.get_server_capabilities() - assert capabilities is not None - assert capabilities == expected_capabilities - assert capabilities.logging is not None - assert capabilities.prompts is not None - assert capabilities.prompts.list_changed is True - assert capabilities.resources is not None - assert capabilities.resources.subscribe is True - assert capabilities.tools is not None - assert capabilities.tools.list_changed is False + params = session.server_params + assert params is not None + assert params.server_info == expected_server_info + assert params.capabilities == expected_capabilities + assert params.instructions == expected_instructions + assert params.protocol_version == LATEST_PROTOCOL_VERSION @pytest.mark.anyio diff --git a/tests/client/transports/test_memory.py b/tests/client/transports/test_memory.py index 47be3e208..4d9944bdb 100644 --- a/tests/client/transports/test_memory.py +++ b/tests/client/transports/test_memory.py @@ -69,7 +69,7 @@ async def test_with_mcpserver(mcpserver_server: MCPServer): async def test_server_is_running(mcpserver_server: MCPServer): """Test that the server is running and responding to requests.""" async with Client(mcpserver_server) as client: - assert client.server_capabilities is not None + assert client.server_params is not None async def test_list_tools(mcpserver_server: MCPServer):