Skip to content

Commit 076ec0c

Browse files
authored
Adopt streamable_http_client API from MCP SDK (#2620)
* Adopt streamable_http_client API from MCP SDK - Update import to use new streamable_http_client function - Convert httpx_client_factory to httpx.AsyncClient before passing to new API - Maintain backward compatibility by continuing to accept factories - Add deprecation warning for sse_read_timeout parameter The new API accepts httpx.AsyncClient directly instead of factories. We continue accepting factories for OAuth compatibility, converting them to clients at the boundary with the MCP SDK. * Fix timeout type conversion for streamable_http_client Convert read_timeout_seconds from timedelta to float before passing to httpx, matching the pattern used in the SSE transport. * Enable redirect following in httpx client * Fix httpx client resource leak
1 parent b8ae95a commit 076ec0c

File tree

1 file changed

+34
-16
lines changed

1 file changed

+34
-16
lines changed

src/fastmcp/client/transports.py

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
)
2424
from mcp.client.sse import sse_client
2525
from mcp.client.stdio import stdio_client
26-
from mcp.client.streamable_http import streamablehttp_client
26+
from mcp.client.streamable_http import streamable_http_client
2727
from mcp.server.fastmcp import FastMCP as FastMCP1Server
2828
from mcp.shared._httpx_utils import McpHttpClientFactory
2929
from mcp.shared.memory import create_client_server_memory_streams
@@ -252,6 +252,16 @@ def __init__(
252252
self.httpx_client_factory = httpx_client_factory
253253
self._set_auth(auth)
254254

255+
if sse_read_timeout is not None:
256+
if fastmcp.settings.deprecation_warnings:
257+
warnings.warn(
258+
"The `sse_read_timeout` parameter is deprecated and no longer used. "
259+
"The new streamable_http_client API does not support this parameter. "
260+
"Use `read_timeout_seconds` in session_kwargs or configure timeout on "
261+
"the httpx client via `httpx_client_factory` instead.",
262+
DeprecationWarning,
263+
stacklevel=2,
264+
)
255265
if isinstance(sse_read_timeout, int | float):
256266
sse_read_timeout = datetime.timedelta(seconds=float(sse_read_timeout))
257267
self.sse_read_timeout = sse_read_timeout
@@ -269,28 +279,36 @@ def _set_auth(self, auth: httpx.Auth | Literal["oauth"] | str | None):
269279
async def connect_session(
270280
self, **session_kwargs: Unpack[SessionKwargs]
271281
) -> AsyncIterator[ClientSession]:
272-
client_kwargs: dict[str, Any] = {}
273-
274-
# load headers from an active HTTP request, if available. This will only be true
282+
# Load headers from an active HTTP request, if available. This will only be true
275283
# if the client is used in a FastMCP Proxy, in which case the MCP client headers
276284
# need to be forwarded to the remote server.
277-
client_kwargs["headers"] = get_http_headers() | self.headers
285+
headers = get_http_headers() | self.headers
278286

279-
# sse_read_timeout has a default value set, so we can't pass None without overriding it
280-
# instead we simply leave the kwarg out if it's not provided
281-
if self.sse_read_timeout is not None:
282-
client_kwargs["sse_read_timeout"] = self.sse_read_timeout
287+
# Build httpx client configuration
288+
httpx_client_kwargs: dict[str, Any] = {
289+
"headers": headers,
290+
"auth": self.auth,
291+
"follow_redirects": True,
292+
}
293+
294+
# Configure timeout if provided (convert timedelta to seconds for httpx)
283295
if session_kwargs.get("read_timeout_seconds") is not None:
284-
client_kwargs["timeout"] = session_kwargs.get("read_timeout_seconds")
296+
read_timeout_seconds = cast(
297+
datetime.timedelta, session_kwargs.get("read_timeout_seconds")
298+
)
299+
httpx_client_kwargs["timeout"] = read_timeout_seconds.total_seconds()
285300

301+
# Create httpx client from factory or use default
286302
if self.httpx_client_factory is not None:
287-
client_kwargs["httpx_client_factory"] = self.httpx_client_factory
303+
http_client = self.httpx_client_factory(**httpx_client_kwargs)
304+
else:
305+
http_client = httpx.AsyncClient(**httpx_client_kwargs)
288306

289-
async with streamablehttp_client(
290-
self.url,
291-
auth=self.auth,
292-
**client_kwargs,
293-
) as transport:
307+
# Ensure httpx client is closed after use
308+
async with (
309+
http_client,
310+
streamable_http_client(self.url, http_client=http_client) as transport,
311+
):
294312
read_stream, write_stream, get_session_id = transport
295313
self._get_session_id_cb = get_session_id
296314
async with ClientSession(

0 commit comments

Comments
 (0)