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
Scope HTTP client redirect following to the request's origin
create_mcp_http_client followed every redirect, so everything configured
on a client (headers, auth, request bodies) was re-sent to whatever host
a Location header named. Clients built by the factory now follow
redirects within the same origin (scheme, host, and port), plus
http-to-https upgrades of the same host on default ports, and raise the
new RedirectError for anything else - before the next request is sent.
- transports resolve a refused redirect in-band: requests get a JSON-RPC
error naming the target and the remedy, notifications are delivered to
the session's message handler; the standalone GET stream stops
retrying an endpoint that keeps redirecting
- caller-supplied clients that follow no redirects get the same clear
error on POST, GET stream, and SSE connect instead of an opaque
content-type error
- OAuth discovery, registration, token, refresh, and the
identity-assertion token exchange fail loudly on redirect responses
instead of silently trying the next URL or abandoning the discovery
chain
- RedirectError and create_mcp_http_client are exported from the
top-level mcp package; migration.md documents the behavior change;
docs and examples configure clients through the factory, and the
general-purpose fetch example uses a browser-like client of its own
Copy file name to clipboardExpand all lines: docs/client/transports.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -29,7 +29,7 @@ Pass a URL string and you get **Streamable HTTP**, the transport you deploy behi
29
29
--8<--"docs_src/client_transports/tutorial002.py"
30
30
```
31
31
32
-
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.
32
+
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: same-origin redirect handling, a 30-second timeout for connect/write/pool, and a 300-second read timeout because the server may hold a response stream open.
33
33
34
34
!!! check
35
35
A `Client` you have constructed is **not** connected. Construction only picks the transport;
Copy file name to clipboardExpand all lines: docs/migration.md
+27-8Lines changed: 27 additions & 8 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1595,14 +1595,15 @@ async with streamablehttp_client(
1595
1595
1596
1596
```python
1597
1597
import httpx
1598
+
from mcp import create_mcp_http_client
1598
1599
from mcp.client.streamable_http import streamable_http_client
1599
1600
1600
-
# Configure headers, timeout, and auth on the httpx.AsyncClient
1601
-
http_client = httpx.AsyncClient(
1601
+
# Configure headers, timeout, and auth on the client. create_mcp_http_client
1602
+
# applies the SDK defaults (timeouts, same-origin redirect handling).
1603
+
http_client = create_mcp_http_client(
1602
1604
headers={"Authorization": "Bearer token"},
1603
1605
timeout=httpx.Timeout(30, read=300),
1604
1606
auth=my_auth,
1605
-
follow_redirects=True,
1606
1607
)
1607
1608
1608
1609
asyncwith http_client:
@@ -1613,7 +1614,26 @@ async with http_client:
1613
1614
...
1614
1615
```
1615
1616
1616
-
v1's internal client set `follow_redirects=True`; set it explicitly when supplying your own `httpx.AsyncClient` to preserve that behavior.
1617
+
Prefer `create_mcp_http_client` when supplying your own client: a plain `httpx.AsyncClient` does not follow redirects at all, and configuring `follow_redirects=True` yourself sends everything on the client (headers, auth, bodies) to whichever server a redirect names.
1618
+
1619
+
### HTTP clients only follow same-origin redirects
1620
+
1621
+
Clients created by the SDK (including `Client("http://...")`, `sse_client`, and the
1622
+
default client inside `streamable_http_client`) now vet redirect targets before
1623
+
following. Redirects that stay on the same origin (scheme, host, and port) and
1624
+
http-to-https upgrades of the same host still work; any other redirect raises
1625
+
`mcp.RedirectError` naming the target instead of being followed. Everything
1626
+
configured on a client - headers, auth, request bodies - is sent to whichever
1627
+
server it talks to, so requests now stay with the server you configured, and a
1628
+
misconfigured URL fails with a clear error instead of silently bouncing.
1629
+
1630
+
To migrate: connect to the final URL the error names. If your deployment
1631
+
genuinely requires following a redirect to a different origin, supply your own
1632
+
`httpx.AsyncClient(follow_redirects=True)` - after verifying you trust every
1633
+
destination in the chain.
1634
+
1635
+
`create_mcp_http_client` (the SDK's client factory) and `RedirectError` are now
1636
+
exported from the top-level `mcp` package.
1617
1637
1618
1638
### `get_session_id` callback removed from `streamable_http_client`
1619
1639
@@ -1638,6 +1658,7 @@ async with streamable_http_client(url) as (read_stream, write_stream, get_sessio
1638
1658
1639
1659
```python
1640
1660
import httpx
1661
+
from mcp import create_mcp_http_client
1641
1662
from mcp.client.streamable_http import streamable_http_client
1642
1663
1643
1664
# Option 1: Simply ignore if you don't need the session ID
Copy file name to clipboardExpand all lines: docs/run/asgi.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -94,7 +94,7 @@ That trailing `/mcp` is `streamable_http_path`. Set it to `"/"` and the mount pr
94
94
--8<--"docs_src/asgi/tutorial004.py"
95
95
```
96
96
97
-
Now clients connect to `/notes`, not `/notes/mcp`.
97
+
Now clients connect to `/notes/`, not `/notes/mcp`. (The trailing slash is the canonical path for this mount shape; the bare `/notes` answers with a same-origin redirect to it.)
0 commit comments