Skip to content

Commit eed6aaa

Browse files
committed
Preserve full-SDK compatibility after the client split
1 parent 7db42b6 commit eed6aaa

14 files changed

Lines changed: 162 additions & 21 deletions

File tree

‎docs/client/session-groups.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ Create a `ClientSessionGroup` and call **`connect_to_server`** once per server:
3232
Put `client.py` next to the two servers and run it. The second `connect_to_server` refuses:
3333

3434
```text
35-
mcp_client.shared.exceptions.MCPError: {'search'} already exist in group tools.
35+
mcp.shared.exceptions.MCPError: {'search'} already exist in group tools.
3636
```
3737

3838
That is an `MCPError`, raised before anything from the second server is registered. A name must

‎docs/deprecated.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ That is the whole API. There is no per-method switch, and you don't want one: th
123123
`Error executing tool old_log`, and the captured server log names the culprit:
124124

125125
```text
126-
mcp_client.shared.exceptions.MCPDeprecationWarning: The logging capability is deprecated as of 2026-07-28 (SEP-2577).
126+
mcp.shared.exceptions.MCPDeprecationWarning: The logging capability is deprecated as of 2026-07-28 (SEP-2577).
127127
```
128128

129129
One line of pytest configuration, and a deprecated call can never sneak back into your

‎docs/run/legacy-clients.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ Two things about it matter more than what it does.
133133
The whole request fails, as a top-level protocol error:
134134

135135
```text
136-
mcp_client.shared.exceptions.MCPError: Cannot send 'elicitation/create': this transport context has no back-channel for server-initiated requests.
136+
mcp.shared.exceptions.MCPError: Cannot send 'elicitation/create': this transport context has no back-channel for server-initiated requests.
137137
```
138138

139139
`Resolve` did not save you. On a `2025-11-25` connection it *has* to send `elicitation/create`,

‎docs/servers/handling-errors.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ Now swap `ToolError` for `MCPError`.
6464
Same lookup, same miss, but now the call *raises* on the client side instead of returning:
6565

6666
```text
67-
mcp_client.shared.exceptions.MCPError: No book titled 'Nothing' in the catalog.
67+
mcp.shared.exceptions.MCPError: No book titled 'Nothing' in the catalog.
6868
```
6969

7070
The first version handed the model a sentence it could react to. This one hands it nothing.

‎docs/servers/prompts.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ That is the entire life of a prompt: listed by name, rendered on demand, dropped
5959
request itself fails with a JSON-RPC error (code `-32603`):
6060

6161
```text
62-
mcp_client.shared.exceptions.MCPError: Internal server error
62+
mcp.shared.exceptions.MCPError: Internal server error
6363
```
6464

6565
There is no tool-style error result to hand back to a model, because no model is in the loop:

‎docs/troubleshooting.md‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ async def main() -> None:
3939
+-+---------------- 1 ----------------
4040
| Traceback (most recent call last):
4141
| ...
42-
| mcp_client.shared.exceptions.MCPError: No forecast for 'Atlantis'.
42+
| mcp.shared.exceptions.MCPError: No forecast for 'Atlantis'.
4343
+------------------------------------
4444
```
4545

@@ -167,7 +167,7 @@ async with Client("https://mcp.example.com/mcp") as client:
167167
```
168168

169169
```text
170-
mcp_client.shared.exceptions.MCPError: Server returned an error response
170+
mcp.shared.exceptions.MCPError: Server returned an error response
171171
```
172172

173173
The words the server actually sent, `421` and `Invalid Host header`, never reach you: the 421 body has no `Content-Type: application/json`, so the client cannot parse it. They are in the **server's log**, which is where to look next:
@@ -334,7 +334,7 @@ async def test_book_table() -> None:
334334
```
335335

336336
```text
337-
mcp_client.shared.exceptions.MCPError: Cannot send 'elicitation/create': this transport context has no back-channel for server-initiated requests.
337+
mcp.shared.exceptions.MCPError: Cannot send 'elicitation/create': this transport context has no back-channel for server-initiated requests.
338338
```
339339

340340
**A legacy connection on a `stateless_http=True` server.** Statelessness means every request is its own world: no session, no server-to-client stream, and so nowhere to send an `elicitation/create` (or `sampling/createMessage`, or `roots/list`) even for the era that has them:
@@ -375,7 +375,7 @@ async def main() -> None:
375375
```
376376

377377
```text
378-
mcp_client.shared.exceptions.MCPError: Invalid or expired requestState
378+
mcp.shared.exceptions.MCPError: Invalid or expired requestState
379379
```
380380

381381
The message is deliberately frozen: the wire never reveals which check failed. The reason goes to the **server log**, and reading it is the whole diagnosis:

‎scripts/check_client_package.py‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,21 @@
1212

1313
import anyio
1414
import mcp_client
15+
from mcp_client.shared import exceptions
1516
from mcp_client.shared.memory import MessageStream, create_client_server_memory_streams
1617
from mcp_client.shared.message import SessionMessage
1718
from mcp_types import JSONRPCRequest, JSONRPCResponse, ListToolsResult, Tool
1819

1920
get_type_hints(mcp_client.Client.__init__)
2021

22+
for error_type in (
23+
exceptions.MCPError,
24+
exceptions.MCPDeprecationWarning,
25+
exceptions.NoBackChannelError,
26+
exceptions.UrlElicitationRequiredError,
27+
):
28+
assert error_type.__module__ == "mcp_client.shared.exceptions"
29+
2130
for name in ("mcp", "starlette", "uvicorn", "sse_starlette", "multipart"):
2231
assert importlib.util.find_spec(name) is None, name
2332

‎src/mcp-client/mcp_client/client/client.py‎

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from collections.abc import Awaitable, Callable, Mapping, Sequence
99
from contextlib import AbstractAsyncContextManager, AsyncExitStack
1010
from dataclasses import KW_ONLY, dataclass, field
11-
from typing import Any, Literal, TypeVar, cast
11+
from typing import Any, Literal, TypeAlias, TypeVar, cast
1212

1313
import anyio
1414
import anyio.lowlevel
@@ -40,7 +40,7 @@
4040
ServerCapabilities,
4141
)
4242
from mcp_types.version import HANDSHAKE_PROTOCOL_VERSIONS, MODERN_PROTOCOL_VERSIONS
43-
from typing_extensions import Protocol, deprecated
43+
from typing_extensions import Protocol, deprecated, runtime_checkable
4444

4545
from mcp_client.client._input_required import DEFAULT_INPUT_REQUIRED_MAX_ROUNDS, run_input_required_driver
4646
from mcp_client.client._probe import negotiate_auto
@@ -95,12 +95,17 @@ async def connect(exit_stack: AsyncExitStack, _mode: ConnectMode, _raise_excepti
9595
return connect
9696

9797

98-
class _InProcessServer(Protocol):
98+
@runtime_checkable
99+
class _ServerConnector(Protocol):
99100
async def __mcp_client_connect__(
100101
self, exit_stack: AsyncExitStack, mode: str, raise_exceptions: bool
101102
) -> Dispatcher[Any]: ...
102103

103104

105+
# The full SDK rebinds this annotation alias, not the runtime-checkable protocol.
106+
_InProcessServer: TypeAlias = _ServerConnector
107+
108+
104109
def _connected(value: _T | None) -> _T:
105110
"""Narrow a post-handshake session attribute from ``T | None`` to ``T``.
106111
@@ -355,14 +360,14 @@ def __post_init__(self) -> None:
355360
self._folded_extensions = _fold_extensions(self.extensions)
356361

357362
srv = self.server
358-
if isinstance(srv, str):
363+
if isinstance(srv, _ServerConnector):
364+
self._connect = srv.__mcp_client_connect__
365+
elif isinstance(srv, str):
359366
self._connect = _connect_transport(streamable_http_client(srv))
360367
elif isinstance(srv, StdioServerParameters):
361368
self._connect = _connect_transport(stdio_client(srv))
362-
elif isinstance(srv, AbstractAsyncContextManager):
363-
self._connect = _connect_transport(srv)
364369
else:
365-
self._connect = cast(_InProcessServer, srv).__mcp_client_connect__
370+
self._connect = _connect_transport(srv)
366371

367372
if self.cache is not None:
368373
config = self.cache

‎src/mcp/client/__init__.py‎

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,49 @@
5252
from mcp_client.client import (
5353
advertise as advertise,
5454
)
55-
from mcp_client.client import (
55+
56+
from . import (
57+
_input_required as _input_required,
58+
)
59+
from . import (
60+
_memory as _memory,
61+
)
62+
from . import (
63+
_probe as _probe,
64+
)
65+
from . import (
66+
_transport as _transport,
67+
)
68+
from . import (
69+
caching as caching,
70+
)
71+
from . import (
72+
client as client,
73+
)
74+
from . import (
75+
context as context,
76+
)
77+
from . import (
78+
extension as extension,
79+
)
80+
from . import (
81+
session as session,
82+
)
83+
from . import (
84+
session_group as session_group,
85+
)
86+
from . import (
87+
sse as sse,
88+
)
89+
from . import (
5690
stdio as stdio,
5791
)
58-
59-
from . import _memory as _memory
92+
from . import (
93+
streamable_http as streamable_http,
94+
)
95+
from . import (
96+
subscriptions as subscriptions,
97+
)
6098

6199
__all__ = [
62100
"CacheConfig",

‎src/mcp/client/auth/__init__.py‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@
2020
TokenStorage as TokenStorage,
2121
)
2222

23+
from . import exceptions as exceptions
24+
from . import oauth2 as oauth2
25+
from . import utils as utils
26+
2327
__all__ = [
2428
"AuthorizationCodeResult",
2529
"OAuthClientProvider",

0 commit comments

Comments
 (0)