Replace httpx and httpx-sse with httpx2 (#2972)
Some checks failed
CI / checks (push) Has been cancelled
CI / all-green (push) Has been cancelled

Co-authored-by: Max Isbey <224885523+maxisbey@users.noreply.github.com>
This commit is contained in:
Marcelo Trylesinski 2026-07-14 18:05:08 +02:00 • committed by GitHub
commit 2713b53b12
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -29,7 +29,7 @@ Pass a URL string and you get **Streamable HTTP**, the transport you deploy behi
--8<-- "docs_src/client_transports/tutorial002.py"
```
That is the whole production client. `Client` wraps the URL in `streamable_http_client(...)` for you, on top of an `httpx.AsyncClient` configured the way MCP needs: `follow_redirects=True`, a 30-second timeout for connect/write/pool, and a 300-second read timeout because the server may hold a response stream open.
That is the whole production client. `Client` wraps the URL in `streamable_http_client(...)` for you, on top of an `httpx2.AsyncClient` configured the way MCP needs: `follow_redirects=True`, a 30-second timeout for connect/write/pool, and a 300-second read timeout because the server may hold a response stream open.
!!! check
A `Client` you have constructed is **not** connected. Construction only picks the transport;
@ -41,9 +41,9 @@ That is the whole production client. `Client` wraps the URL in `streamable_http_
Nothing was resolved, fetched or spawned when you wrote `Client("http://...")`. That line is free.
### Bring your own `httpx.AsyncClient`
### Bring your own `httpx2.AsyncClient`
The moment you need an `Authorization` header, a cookie, a proxy, mTLS, or a different timeout, build the `httpx.AsyncClient` yourself and hand it to `streamable_http_client`:
The moment you need an `Authorization` header, a cookie, a proxy, mTLS, or a different timeout, build the `httpx2.AsyncClient` yourself and hand it to `streamable_http_client`:
```python title="client.py" hl_lines="8-14"
--8<-- "docs_src/client_transports/tutorial003.py"
@ -51,9 +51,16 @@ The moment you need an `Authorization` header, a cookie, a proxy, mTLS, or a dif
Two things to notice:
* You own the `httpx.AsyncClient`, so **you** enter and exit it. The SDK never closes a client it didn't create.
* You own the `httpx2.AsyncClient`, so **you** enter and exit it. The SDK never closes a client it didn't create.
* `streamable_http_client(url, http_client=...)` returns a transport, and `Client(transport)` accepts it like anything else.
One TLS note: `httpx2` verifies certificates against the operating system trust store (via
[`truststore`](https://pypi.org/project/truststore/)), not a bundled CA list. In an environment with
no usable system CA store (some minimal containers), set the standard `SSL_CERT_FILE`/`SSL_CERT_DIR`
environment variables or pass an explicit `verify=ssl_context` to your `httpx2.AsyncClient`
(background in
[`httpx` and `httpx-sse` replaced by `httpx2`](../migration.md#httpx-and-httpx-sse-replaced-by-httpx2)).
!!! warning
`streamable_http_client` used to take `headers=` and `timeout=` directly. It does not any more:
its only parameters are `url`, `http_client` and `terminate_on_close`. Reach for `headers=` out
@ -63,12 +70,13 @@ Two things to notice:
TypeError: streamable_http_client() got an unexpected keyword argument 'headers'
```
Everything HTTP-shaped now lives on the one `httpx.AsyncClient` you pass in.
Everything HTTP-shaped now lives on the one `httpx2.AsyncClient` you pass in.
!!! info
If you know `httpx`, you already know how to do auth, proxies, event hooks, retries and connection
limits here. The SDK adds nothing on top and takes nothing away. It is also where OAuth plugs in:
`httpx.AsyncClient(auth=OAuthClientProvider(...))`. That whole flow is **[OAuth clients](oauth-clients.md)**.
`httpx2` keeps the familiar `httpx` API, so if you know `httpx` you already know how to do auth,
proxies, event hooks, retries and connection limits here. The SDK adds nothing on top and takes
nothing away. It is also where OAuth plugs in:
`httpx2.AsyncClient(auth=OAuthClientProvider(...))`. That whole flow is **[OAuth clients](oauth-clients.md)**.
## stdio
@ -106,7 +114,7 @@ A **transport** is any async context manager that yields a `(read, write)` pair
* `Client(mcp)` (the server object) connects in memory. Use it for tests and for embedding.
* `Client("http://.../mcp")` (a URL) connects over Streamable HTTP, the production transport.
* Headers, auth, proxies and timeouts belong on an `httpx.AsyncClient` you pass to `streamable_http_client(url, http_client=...)`. There is no `headers=` keyword.
* Headers, auth, proxies and timeouts belong on an `httpx2.AsyncClient` you pass to `streamable_http_client(url, http_client=...)`. There is no `headers=` keyword.
* stdio is `Client(stdio_client(StdioServerParameters(...)))`, never the parameters object alone.
* The subprocess gets an allow-listed environment, not yours; `env=` adds to it.
* 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.