Skip to content

Commit 08e24ae

Browse files
committed
Custom httpx client for streamablehttp
1 parent 6353dd1 commit 08e24ae

File tree

2 files changed

+42
-6
lines changed

2 files changed

+42
-6
lines changed

src/mcp/client/streamable_http.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,7 @@ async def streamablehttp_client(
427427
timeout: timedelta = timedelta(seconds=30),
428428
sse_read_timeout: timedelta = timedelta(seconds=60 * 5),
429429
terminate_on_close: bool = True,
430+
http_client: httpx.AsyncClient | None = None,
430431
) -> AsyncGenerator[
431432
tuple[
432433
MemoryObjectReceiveStream[SessionMessage | Exception],
@@ -448,6 +449,12 @@ async def streamablehttp_client(
448449
- get_session_id_callback: Function to retrieve the current session ID
449450
"""
450451
transport = StreamableHTTPTransport(url, headers, timeout, sse_read_timeout)
452+
client = http_client or create_mcp_http_client(
453+
headers=transport.request_headers,
454+
timeout=httpx.Timeout(
455+
transport.timeout.seconds, read=transport.sse_read_timeout.seconds
456+
),
457+
)
451458

452459
read_stream_writer, read_stream = anyio.create_memory_object_stream[
453460
SessionMessage | Exception
@@ -460,12 +467,7 @@ async def streamablehttp_client(
460467
try:
461468
logger.info(f"Connecting to StreamableHTTP endpoint: {url}")
462469

463-
async with create_mcp_http_client(
464-
headers=transport.request_headers,
465-
timeout=httpx.Timeout(
466-
transport.timeout.seconds, read=transport.sse_read_timeout.seconds
467-
),
468-
) as client:
470+
async with client:
469471
# Define callbacks that need access to tg
470472
def start_get_stream() -> None:
471473
tg.start_soon(

tests/server/fastmcp/test_integration.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import time
1111
from collections.abc import Generator
1212

13+
import httpx
1314
import pytest
1415
import uvicorn
1516
from pydantic import AnyUrl
@@ -460,6 +461,39 @@ async def test_fastmcp_streamable_http(
460461
assert tool_result.content[0].text == "Echo: hello"
461462

462463

464+
@pytest.mark.anyio
465+
async def test_fastmcp_streamable_http_with_custom_client(
466+
streamable_http_server: None, http_server_url: str
467+
) -> None:
468+
"""Test that FastMCP works with StreamableHTTP transport using custom HttpClient."""
469+
http_client = httpx.AsyncClient(
470+
base_url=http_server_url,
471+
follow_redirects=True,
472+
headers={
473+
"Content-Type": "application/json",
474+
"Accept": "application/json,text/event-stream",
475+
},
476+
)
477+
# Connect to the server using StreamableHTTP
478+
async with streamablehttp_client("/mcp", http_client=http_client) as (
479+
read_stream,
480+
write_stream,
481+
_,
482+
):
483+
# Create a session using the client streams
484+
async with ClientSession(read_stream, write_stream) as session:
485+
# Test initialization
486+
result = await session.initialize()
487+
assert isinstance(result, InitializeResult)
488+
assert result.serverInfo.name == "NoAuthServer"
489+
490+
# Test that we can call tools without authentication
491+
tool_result = await session.call_tool("echo", {"message": "hello"})
492+
assert len(tool_result.content) == 1
493+
assert isinstance(tool_result.content[0], TextContent)
494+
assert tool_result.content[0].text == "Echo: hello"
495+
496+
463497
@pytest.mark.anyio
464498
async def test_fastmcp_stateless_streamable_http(
465499
stateless_http_server: None, stateless_http_server_url: str

0 commit comments

Comments
 (0)