diff --git a/docs/migration.md b/docs/migration.md index b094d79f84..69d1dc8b71 100644 --- a/docs/migration.md +++ b/docs/migration.md @@ -2109,6 +2109,18 @@ v1's internal client set `follow_redirects=True`; set it explicitly when supplyi - `httpx_client_factory`: gone with no replacement — call your factory yourself and pass the result as `http_client`. - `terminate_on_close`: unchanged (default `True`). +If you'd rather not hand-build the `httpx2.AsyncClient`, the standardized factory and its defaults are re-exported from the public `mcp.client.streamable_http` module, so you don't need to touch the private `mcp.shared._httpx_utils`: + +```python +from mcp.client.streamable_http import create_mcp_http_client, streamable_http_client + +async with create_mcp_http_client(headers={"Authorization": "Bearer token"}) as http_client: + async with streamable_http_client(url="http://localhost:8000/mcp", http_client=http_client) as (read_stream, write_stream): + ... +``` + +`create_mcp_http_client(headers=..., timeout=..., auth=...)` applies the MCP defaults (`follow_redirects=True` and `Timeout(30, read=300)`) for you; the `McpHttpClientFactory` protocol and the `MCP_DEFAULT_TIMEOUT` / `MCP_DEFAULT_SSE_READ_TIMEOUT` constants are available from the same module. + Client-side stream resumption is also unchanged: the transport reconnects a dropped GET stream with `Last-Event-ID` on its own, and `session.send_request(..., metadata=ClientMessageMetadata(resumption_token=..., on_resumption_token_update=...))` (from `mcp.shared.message`) works as in v1. ### `get_session_id` callback removed from `streamable_http_client` diff --git a/src/mcp/client/streamable_http.py b/src/mcp/client/streamable_http.py index 226b0fecf9..438af18310 100644 --- a/src/mcp/client/streamable_http.py +++ b/src/mcp/client/streamable_http.py @@ -33,6 +33,9 @@ from mcp.client._transport import TransportStreams from mcp.shared._compat import resync_tracer from mcp.shared._context_streams import ContextReceiveStream, ContextSendStream, create_context_streams +from mcp.shared._httpx_utils import MCP_DEFAULT_SSE_READ_TIMEOUT as MCP_DEFAULT_SSE_READ_TIMEOUT +from mcp.shared._httpx_utils import MCP_DEFAULT_TIMEOUT as MCP_DEFAULT_TIMEOUT +from mcp.shared._httpx_utils import McpHttpClientFactory as McpHttpClientFactory from mcp.shared._httpx_utils import create_mcp_http_client from mcp.shared.inbound import MCP_PROTOCOL_VERSION_HEADER from mcp.shared.jsonrpc_dispatcher import cancelled_request_id_from_params diff --git a/tests/shared/test_httpx_utils.py b/tests/shared/test_httpx_utils.py index a94d7c9299..4120fdc306 100644 --- a/tests/shared/test_httpx_utils.py +++ b/tests/shared/test_httpx_utils.py @@ -22,3 +22,16 @@ def test_custom_parameters(): assert client.headers["Authorization"] == "Bearer token" assert client.timeout.connect == 60.0 + + +def test_public_reexport_from_streamable_http(): + """The factory, protocol, and timeout defaults are importable from the + public ``mcp.client.streamable_http`` module, not only the private + ``mcp.shared._httpx_utils``.""" + import mcp.shared._httpx_utils as private_module + from mcp.client import streamable_http + + assert streamable_http.create_mcp_http_client is private_module.create_mcp_http_client + assert streamable_http.McpHttpClientFactory is private_module.McpHttpClientFactory + assert streamable_http.MCP_DEFAULT_TIMEOUT == private_module.MCP_DEFAULT_TIMEOUT + assert streamable_http.MCP_DEFAULT_SSE_READ_TIMEOUT == private_module.MCP_DEFAULT_SSE_READ_TIMEOUT