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
Client picks its transport from the type of its argument: a server
object connects in-process, a str is a Streamable HTTP URL, and anything
else is entered as a Transport. stdio was the odd one out, needing
Client(stdio_client(StdioServerParameters(...))). Add the missing arm so
Client(StdioServerParameters(...)) launches the command via stdio_client;
wrapping it yourself remains the way to redirect the child's stderr.
Docs: the transports page's stdio section and recap, the "what you can
pass" list, and the two other places that enumerate the connection
forms. The stories harness drops the TODO that anticipated this.
Copy file name to clipboardExpand all lines: docs/client/index.md
+3-2Lines changed: 3 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -22,9 +22,10 @@ The server at the top is only there so you have something to connect to. The cli
22
22
23
23
* An `MCPServer` (or low-level `Server`) instance: connected **in-process**.
24
24
* A URL string (`Client("http://localhost:8000/mcp")`): Streamable HTTP, the production path.
25
-
* A **transport**: anything you can `async with ... as (read, write)`, such as `stdio_client(...)` wrapping a subprocess.
25
+
* A `StdioServerParameters`: the command to launch as a **subprocess**, spoken to over its stdin and stdout.
26
+
* A **transport**: anything you can `async with ... as (read, write)`, such as `streamable_http_client(url, http_client=...)` around your own HTTP client.
26
27
27
-
Everything else on this page is identical across all three. Headers, subprocesses, timeouts, and the `Transport` protocol get their own page: **[Client transports](transports.md)**.
28
+
Everything else on this page is identical across all four. Headers, subprocesses, timeouts, and the `Transport` protocol get their own page: **[Client transports](transports.md)**.
Copy file name to clipboardExpand all lines: docs/client/transports.md
+7-7Lines changed: 7 additions & 7 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -82,15 +82,15 @@ environment variables or pass an explicit `verify=ssl_context` to your `httpx2.A
82
82
83
83
A **stdio** server is a subprocess. The client launches it, writes JSON-RPC to its stdin and reads JSON-RPC from its stdout. It is how a desktop host runs a server on your machine: a host *is* this code plus a UI, and **[Connect to a real host](../get-started/real-host.md)** is the same relationship seen from the host's side, as a config file.
84
84
85
-
Describe the process with `StdioServerParameters`, turn it into a transport with `stdio_client`, and hand *that* to `Client`:
85
+
Describe the process with `StdioServerParameters`and hand it to `Client`:
86
86
87
-
```python title="client.py" hl_lines="4-8 12"
87
+
```python title="client.py" hl_lines="3-7 11"
88
88
--8<--"docs_src/client_transports/tutorial004.py"
89
89
```
90
90
91
-
`Client` does not accept the parameters object on its own. `StdioServerParameters` is configuration; `stdio_client(server)` is the transport that knows how to spawn a process from it. Always wrap.
91
+
Entering the block spawns the process; leaving it shuts the subprocess down: close stdin, wait, kill if it lingers. You never clean it up yourself.
92
92
93
-
Leaving the `async with` block also shuts the subprocess down: close stdin, wait, kill if it lingers. You never clean it up yourself.
93
+
The child's stderr goes to yours. To send it somewhere else, build the transport yourself with `stdio_client` (from `mcp`) and pass that instead: `Client(stdio_client(server, errlog=log_file))`.
94
94
95
95
!!! warning
96
96
The child does **not** inherit your environment. It gets a minimal allow-list (`HOME`, `LOGNAME`,
@@ -108,16 +108,16 @@ Leaving the `async with` block also shuts the subprocess down: close stdin, wait
108
108
109
109
To `Client`, all of the above are the same thing.
110
110
111
-
A **transport** is any async context manager that yields a `(read, write)` pair of message streams: formally, the `Transport` protocol in `mcp.client`. `Client` resolves its argument by type: a server object connects in-process, a `str` becomes `streamable_http_client(url)`, and anything else is entered as a transport directly. That last rule is why `stdio_client(...)`, `streamable_http_client(...)` and `sse_client(...)` all drop into the same slot, and why you can write your own.
111
+
A **transport** is any async context manager that yields a `(read, write)` pair of message streams: formally, the `Transport` protocol in `mcp.client`. `Client` resolves its argument by type: a server object connects in-process, a `str` becomes `streamable_http_client(url)`, a `StdioServerParameters` becomes `stdio_client(params)`, and anything else is entered as a transport directly. That last rule is why `stdio_client(...)`, `streamable_http_client(...)` and `sse_client(...)` all drop into the same slot, and why you can write your own.
112
112
113
113
## Recap
114
114
115
115
*`Client(mcp)` (the server object) connects in memory. Use it for tests and for embedding.
116
116
*`Client("http://.../mcp")` (a URL) connects over Streamable HTTP, the production transport.
117
117
* Headers, auth, proxies and timeouts belong on an `httpx2.AsyncClient` you pass to `streamable_http_client(url, http_client=...)`. There is no `headers=` keyword.
118
-
* stdio is `Client(stdio_client(StdioServerParameters(...)))`, never the parameters object alone.
118
+
* stdio is `Client(StdioServerParameters(...))`; wrap it in `stdio_client(...)` yourself only to redirect the child's stderr.
119
119
* The subprocess gets an allow-listed environment, not yours; `env=` adds to it.
120
-
* A transport is anything you can `async with x as (read, write)`. `Client` hands anything that isn't a server object or a URL straight to that protocol.
120
+
* A transport is anything you can `async with x as (read, write)`. `Client` hands anything that isn't a server object, a URL or `StdioServerParameters` straight to that protocol.
121
121
* Constructing a `Client` picks the transport. `async with` opens it.
122
122
123
123
Once the transport is open the two sides have to agree on a protocol version. You normally never think about it; when you do, **[Protocol versions](../protocol-versions.md)** is the page.
Copy file name to clipboardExpand all lines: docs/whats-new.md
+2-2Lines changed: 2 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -41,9 +41,9 @@ v1 handed you three nested layers: a transport context manager yielding raw stre
41
41
--8<--"docs_src/client/tutorial001.py"
42
42
```
43
43
44
-
`Client` takes a server object (in memory, no transport: the testing story), a URL (Streamable HTTP), or any transport context manager such as `stdio_client(...)`. Entering `async with` connects and negotiates the protocol version, whichever era the server speaks; `client.server_capabilities` and `client.protocol_version` are simply there afterwards, and `client.server_info` is too when the server identifies itself (it is `Implementation | None` now, since 2026-era identity is optional). The sampling and elicitation callbacks you registered in v1 still work (their bodies see the same snake_case attribute rename as everything else on this page), they now also answer the 2026-style requests-inside-results (below), and they run concurrently instead of one at a time. `ClientSession` is still underneath for anyone who wants the low-level surface, and `client.session` hands it to you; it moved too (it runs on the new dispatcher engine, and some of its own signatures changed), so read the **[Migration Guide](migration.md#clientsession-now-runs-on-jsonrpcdispatcher-basesession-removed)** before you drop down.
44
+
`Client` takes a server object (in memory, no transport: the testing story), a URL (Streamable HTTP), a `StdioServerParameters` (a stdio subprocess), or any other transport context manager such as `sse_client(...)`. Entering `async with` connects and negotiates the protocol version, whichever era the server speaks; `client.server_capabilities` and `client.protocol_version` are simply there afterwards, and `client.server_info` is too when the server identifies itself (it is `Implementation | None` now, since 2026-era identity is optional). The sampling and elicitation callbacks you registered in v1 still work (their bodies see the same snake_case attribute rename as everything else on this page), they now also answer the 2026-style requests-inside-results (below), and they run concurrently instead of one at a time. `ClientSession` is still underneath for anyone who wants the low-level surface, and `client.session` hands it to you; it moved too (it runs on the new dispatcher engine, and some of its own signatures changed), so read the **[Migration Guide](migration.md#clientsession-now-runs-on-jsonrpcdispatcher-basesession-removed)** before you drop down.
45
45
46
-
**[The Client](client/index.md)** introduces it, **[Client transports](client/transports.md)** covers the three connection forms, **[Client callbacks](client/callbacks.md)** covers the callbacks themselves, and **[Testing](get-started/testing.md)** shows the in-memory pattern that replaces v1's `create_connected_server_and_client_session()` helper.
46
+
**[The Client](client/index.md)** introduces it, **[Client transports](client/transports.md)** covers the four connection forms, **[Client callbacks](client/callbacks.md)** covers the callbacks themselves, and **[Testing](get-started/testing.md)** shows the in-memory pattern that replaces v1's `create_connected_server_and_client_session()` helper.
47
47
48
48
### The low-level `Server` was rebuilt, not renamed
0 commit comments