You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The default Client(...) now probes server/discover and falls back to
initialize() (via negotiate_auto's denylist). For an in-process Server,
the default path is now DirectDispatcher per-request rather than the
legacy InMemoryTransport stream loop.
DirectDispatcher gains raise_handler_exceptions (matching
JSONRPCDispatcher's knob): True chains the original exception via
__cause__; False sanitizes to MCPError(INTERNAL_ERROR). modern_on_request
collapses to a pure envelope-builder (no exception ladder of its own),
and Client threads raise_exceptions into create_direct_dispatcher_pair.
Tests that exercise legacy-specific semantics — server-initiated
sampling/elicitation push, message_handler delivery, ping,
InMemoryTransport mechanics, JSON-RPC wire-shape recording — are pinned
to mode='legacy' explicitly (~64 sites across 26 test files plus the
client_via_http / connect_over_sse / auth-harness helpers). These are
census-driven, not failure-driven: ~23 sites would have passed under
'auto' but silently stopped testing their subject.
Client.send_ping() is deprecated (ping is removed from 2026-07-28); it
only works under mode='legacy'.
docs/migration.md gains a section explaining the default change and when
to pin mode='legacy'; docs/testing.md notes the same for test authors.
Copy file name to clipboardExpand all lines: docs/migration.md
+10-2Lines changed: 10 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -352,7 +352,15 @@ version = session.protocol_version
352
352
353
353
The raw handshake result is also retained: `session.initialize_result` is set after `initialize()` (≤2025-11-25 servers — including `stateless_http=True` servers, which still answer `initialize`); `session.discover_result` is set after `discover()` (2026-07-28+ servers). At most one is non-`None`.
354
354
355
-
On the high-level `Client`, `client.server_capabilities`, `client.server_info`, and `client.protocol_version` are non-nullable inside the context manager. `client.instructions` remains `str | None` since the server may omit it. (The lowlevel `ClientSession` still lets you call methods before any handshake, as in v1; `Client` always handshakes on enter.)
355
+
On the high-level `Client`, `client.server_capabilities`, `client.server_info`, and `client.protocol_version` are non-nullable inside the context manager. `client.instructions` remains `str | None` since the server may omit it. (The lowlevel `ClientSession` still lets you call methods before any handshake, as in v1; `Client` always connects on enter — by default it probes `server/discover` and falls back to the initialize handshake.)
356
+
357
+
### `Client` defaults to `mode='auto'`
358
+
359
+
In v1, connecting to a server always performed the `initialize` handshake. In v2, `Client` defaults to `mode='auto'`: on enter it probes `server/discover` and, if the server doesn't support it, falls back to the `initialize` handshake. Pass `mode='legacy'` to force the initialize handshake and reproduce v1's byte-identical pre-2026 behavior, or pass a modern protocol-version string (e.g. `mode='2026-07-28'`) to pin a version without probing.
360
+
361
+
For an in-process `Client(server)` (where `server` is a `Server` or `MCPServer` instance), `mode='auto'` dispatches calls directly through `DirectDispatcher` with no JSON-RPC framing. Pass `mode='legacy'` if you need the in-memory JSON-RPC transport that v1 used.
362
+
363
+
`Client.send_ping()` is deprecated (ping is removed in 2026-07-28); pin `mode='legacy'` if you need it.
356
364
357
365
### `McpError` renamed to `MCPError`
358
366
@@ -832,7 +840,7 @@ async with Client(server) as client:
832
840
result =await client.call_tool("my_tool", {"x": 1})
833
841
```
834
842
835
-
`Client` accepts the same callback parameters the old helper did (`sampling_callback`, `list_roots_callback`, `logging_callback`, `message_handler`, `elicitation_callback`, `client_info`) plus `raise_exceptions` to surface server-side errors.
843
+
`Client` accepts the same callback parameters the old helper did (`sampling_callback`, `list_roots_callback`, `logging_callback`, `message_handler`, `elicitation_callback`, `client_info`) plus `raise_exceptions` to surface server-side errors and `mode` to control version negotiation (`'auto'` by default; `'legacy'` reproduces v1's initialize-only handshake).
836
844
837
845
If you need direct access to the underlying `ClientSession` and memory streams (e.g., for low-level transport testing), `create_client_server_memory_streams` is still available in `mcp.shared.memory`:
1. If you are using `trio`, you should set `"trio"` as the `anyio_backend`. Check more information in the [anyio documentation](https://anyio.readthedocs.io/en/stable/testing.html#specifying-the-backends-to-run-on).
75
75
2. The `client` fixture creates a connected client that can be reused across multiple tests.
76
76
77
+
!!! note
78
+
`Client(app)` connects in-process and is era-neutral by default — it probes the server and picks the
79
+
appropriate protocol path. Pin `mode='legacy'` if your test exercises legacy-specific semantics
80
+
(sampling/elicitation push, `message_handler`).
81
+
77
82
There you go! You can now extend your tests to cover more scenarios.
0 commit comments