docs: stop presenting the in-memory client as the way to connect (#3443)

This commit is contained in:
Max 2026-09-05 04:15:58 +01:00 • committed by GitHub
commit 7bb486a10f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 803 additions and 598 deletions

View file

@ -159,7 +159,7 @@ The low-level `Server` is the no-batteries tier: unlike `MCPServer`, nothing is
## A 2026-07-28 result
`InputRequiredResult` only exists at protocol version **2026-07-28**. The in-memory `Client(server)` negotiates it for you; over the wire, `mode="auto"` discovers it. After connecting, `client.protocol_version` tells you what you got.
`InputRequiredResult` only exists at protocol version **2026-07-28**. `Client`'s default `mode="auto"` discovers it on any connection. After connecting, `client.protocol_version` tells you what you got.
!!! warning
A pre-2026 session has nowhere to put an `InputRequiredResult`. Return one from your handler on a

View file

@ -24,19 +24,17 @@ Three arguments, and you decide what they mean:
The client opts in **per call**, by passing `progress_callback=` to `call_tool`:
```python title="client.py" hl_lines="7 16"
```python title="client.py" hl_lines="5 14"
import anyio
from mcp import Client
from server import mcp
async def show(progress: float, total: float | None, message: str | None) -> None:
print(f"{message} ({progress}/{total})")
async def main() -> None:
async with Client(mcp) as client:
async with Client("http://localhost:8000/mcp") as client:
result = await client.call_tool(
"import_catalog",
{"urls": ["https://example.com/a.json", "https://example.com/b.json"]},
@ -51,28 +49,31 @@ anyio.run(main)
The callback is an `async` function taking exactly what the server reported: `progress`, `total`, `message`.
!!! info
`Client(mcp)` connects straight to the server object, in memory, the same client the **[Testing](../get-started/testing.md)**
page is built on. `progress_callback` is the same parameter whatever transport the `Client`
uses; the *timing* you are about to see is the in-memory connection's. It runs your callback
inline, so every report lands before `call_tool` returns. Over a real transport the
notifications race the result, and a slow callback can still be running after `call_tool` has
returned.
`progress_callback` is the same parameter whatever you handed `Client`: a URL as here, a
`StdioServerParameters`, or the server object in a test. Mind the timing over a real
transport, though. Each notification is delivered on its own, beside the response, so a slow
callback can still be running after `call_tool` has returned. Only the in-process test
connection runs the callback inline and guarantees every report lands first.
### Try it
Put `client.py` next to `server.py` and run it:
Serve `server.py` over HTTP, then run the client from a second terminal:
```console
uv run mcp run server.py --transport streamable-http
```
```console
python client.py
```
```text
Imported https://example.com/a.json (1/2)
Imported https://example.com/b.json (2/2)
Imported https://example.com/a.json (1.0/2.0)
Imported https://example.com/b.json (2.0/2.0)
{'result': 'Imported 2 records.'}
```
Every `await ctx.report_progress(...)` on the server became one call to `show` on the client, in order, and both lines printed **before** `call_tool` returned. Progress is not bundled into the result; it streams while the tool is still working.
Every `await ctx.report_progress(...)` on the server became one call to `show` on the client, in order. Progress is not bundled into the result. It streams while the tool is still working.
!!! warning
`progress_callback` belongs to the **call**, not the `Client`. There is no constructor argument