Skip to content

Commit a3a4b8d

Browse files
Add streamable_http_client which accepts httpx.AsyncClient instead of httpx_client_factory (#1177)
Co-authored-by: Felix Weinberger <[email protected]>
1 parent cc8382c commit a3a4b8d

File tree

12 files changed

+488
-221
lines changed

12 files changed

+488
-221
lines changed

README.md

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2241,12 +2241,12 @@ Run from the repository root:
22412241
import asyncio
22422242

22432243
from mcp import ClientSession
2244-
from mcp.client.streamable_http import streamablehttp_client
2244+
from mcp.client.streamable_http import streamable_http_client
22452245

22462246

22472247
async def main():
22482248
# Connect to a streamable HTTP server
2249-
async with streamablehttp_client("http://localhost:8000/mcp") as (
2249+
async with streamable_http_client("http://localhost:8000/mcp") as (
22502250
read_stream,
22512251
write_stream,
22522252
_,
@@ -2370,11 +2370,12 @@ cd to the `examples/snippets` directory and run:
23702370
import asyncio
23712371
from urllib.parse import parse_qs, urlparse
23722372

2373+
import httpx
23732374
from pydantic import AnyUrl
23742375

23752376
from mcp import ClientSession
23762377
from mcp.client.auth import OAuthClientProvider, TokenStorage
2377-
from mcp.client.streamable_http import streamablehttp_client
2378+
from mcp.client.streamable_http import streamable_http_client
23782379
from mcp.shared.auth import OAuthClientInformationFull, OAuthClientMetadata, OAuthToken
23792380

23802381

@@ -2428,15 +2429,16 @@ async def main():
24282429
callback_handler=handle_callback,
24292430
)
24302431

2431-
async with streamablehttp_client("http://localhost:8001/mcp", auth=oauth_auth) as (read, write, _):
2432-
async with ClientSession(read, write) as session:
2433-
await session.initialize()
2432+
async with httpx.AsyncClient(auth=oauth_auth, follow_redirects=True) as custom_client:
2433+
async with streamable_http_client("http://localhost:8001/mcp", http_client=custom_client) as (read, write, _):
2434+
async with ClientSession(read, write) as session:
2435+
await session.initialize()
24342436

2435-
tools = await session.list_tools()
2436-
print(f"Available tools: {[tool.name for tool in tools.tools]}")
2437+
tools = await session.list_tools()
2438+
print(f"Available tools: {[tool.name for tool in tools.tools]}")
24372439

2438-
resources = await session.list_resources()
2439-
print(f"Available resources: {[r.uri for r in resources.resources]}")
2440+
resources = await session.list_resources()
2441+
print(f"Available resources: {[r.uri for r in resources.resources]}")
24402442

24412443

24422444
def run():

examples/clients/simple-auth-client/mcp_simple_auth_client/main.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,15 @@
1111
import threading
1212
import time
1313
import webbrowser
14-
from datetime import timedelta
1514
from http.server import BaseHTTPRequestHandler, HTTPServer
1615
from typing import Any
1716
from urllib.parse import parse_qs, urlparse
1817

18+
import httpx
1919
from mcp.client.auth import OAuthClientProvider, TokenStorage
2020
from mcp.client.session import ClientSession
2121
from mcp.client.sse import sse_client
22-
from mcp.client.streamable_http import streamablehttp_client
22+
from mcp.client.streamable_http import streamable_http_client
2323
from mcp.shared.auth import OAuthClientInformationFull, OAuthClientMetadata, OAuthToken
2424

2525

@@ -193,7 +193,7 @@ async def _default_redirect_handler(authorization_url: str) -> None:
193193
# Create OAuth authentication handler using the new interface
194194
# Use client_metadata_url to enable CIMD when the server supports it
195195
oauth_auth = OAuthClientProvider(
196-
server_url=self.server_url,
196+
server_url=self.server_url.replace("/mcp", ""),
197197
client_metadata=OAuthClientMetadata.model_validate(client_metadata_dict),
198198
storage=InMemoryTokenStorage(),
199199
redirect_handler=_default_redirect_handler,
@@ -212,12 +212,12 @@ async def _default_redirect_handler(authorization_url: str) -> None:
212212
await self._run_session(read_stream, write_stream, None)
213213
else:
214214
print("📡 Opening StreamableHTTP transport connection with auth...")
215-
async with streamablehttp_client(
216-
url=self.server_url,
217-
auth=oauth_auth,
218-
timeout=timedelta(seconds=60),
219-
) as (read_stream, write_stream, get_session_id):
220-
await self._run_session(read_stream, write_stream, get_session_id)
215+
async with httpx.AsyncClient(auth=oauth_auth, follow_redirects=True) as custom_client:
216+
async with streamable_http_client(
217+
url=self.server_url,
218+
http_client=custom_client,
219+
) as (read_stream, write_stream, get_session_id):
220+
await self._run_session(read_stream, write_stream, get_session_id)
221221

222222
except Exception as e:
223223
print(f"❌ Failed to connect: {e}")

examples/snippets/clients/oauth_client.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,12 @@
1010
import asyncio
1111
from urllib.parse import parse_qs, urlparse
1212

13+
import httpx
1314
from pydantic import AnyUrl
1415

1516
from mcp import ClientSession
1617
from mcp.client.auth import OAuthClientProvider, TokenStorage
17-
from mcp.client.streamable_http import streamablehttp_client
18+
from mcp.client.streamable_http import streamable_http_client
1819
from mcp.shared.auth import OAuthClientInformationFull, OAuthClientMetadata, OAuthToken
1920

2021

@@ -68,15 +69,16 @@ async def main():
6869
callback_handler=handle_callback,
6970
)
7071

71-
async with streamablehttp_client("http://localhost:8001/mcp", auth=oauth_auth) as (read, write, _):
72-
async with ClientSession(read, write) as session:
73-
await session.initialize()
72+
async with httpx.AsyncClient(auth=oauth_auth, follow_redirects=True) as custom_client:
73+
async with streamable_http_client("http://localhost:8001/mcp", http_client=custom_client) as (read, write, _):
74+
async with ClientSession(read, write) as session:
75+
await session.initialize()
7476

75-
tools = await session.list_tools()
76-
print(f"Available tools: {[tool.name for tool in tools.tools]}")
77+
tools = await session.list_tools()
78+
print(f"Available tools: {[tool.name for tool in tools.tools]}")
7779

78-
resources = await session.list_resources()
79-
print(f"Available resources: {[r.uri for r in resources.resources]}")
80+
resources = await session.list_resources()
81+
print(f"Available resources: {[r.uri for r in resources.resources]}")
8082

8183

8284
def run():

examples/snippets/clients/streamable_basic.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@
66
import asyncio
77

88
from mcp import ClientSession
9-
from mcp.client.streamable_http import streamablehttp_client
9+
from mcp.client.streamable_http import streamable_http_client
1010

1111

1212
async def main():
1313
# Connect to a streamable HTTP server
14-
async with streamablehttp_client("http://localhost:8000/mcp") as (
14+
async with streamable_http_client("http://localhost:8000/mcp") as (
1515
read_stream,
1616
write_stream,
1717
_,

src/mcp/client/session_group.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from typing import Any, TypeAlias, overload
1818

1919
import anyio
20+
import httpx
2021
from pydantic import BaseModel
2122
from typing_extensions import Self, deprecated
2223

@@ -25,7 +26,8 @@
2526
from mcp.client.session import ElicitationFnT, ListRootsFnT, LoggingFnT, MessageHandlerFnT, SamplingFnT
2627
from mcp.client.sse import sse_client
2728
from mcp.client.stdio import StdioServerParameters
28-
from mcp.client.streamable_http import streamablehttp_client
29+
from mcp.client.streamable_http import streamable_http_client
30+
from mcp.shared._httpx_utils import create_mcp_http_client
2931
from mcp.shared.exceptions import McpError
3032
from mcp.shared.session import ProgressFnT
3133

@@ -47,7 +49,7 @@ class SseServerParameters(BaseModel):
4749

4850

4951
class StreamableHttpParameters(BaseModel):
50-
"""Parameters for intializing a streamablehttp_client."""
52+
"""Parameters for intializing a streamable_http_client."""
5153

5254
# The endpoint URL.
5355
url: str
@@ -309,11 +311,18 @@ async def _establish_session(
309311
)
310312
read, write = await session_stack.enter_async_context(client)
311313
else:
312-
client = streamablehttp_client(
313-
url=server_params.url,
314+
httpx_client = create_mcp_http_client(
314315
headers=server_params.headers,
315-
timeout=server_params.timeout,
316-
sse_read_timeout=server_params.sse_read_timeout,
316+
timeout=httpx.Timeout(
317+
server_params.timeout.total_seconds(),
318+
read=server_params.sse_read_timeout.total_seconds(),
319+
),
320+
)
321+
await session_stack.enter_async_context(httpx_client)
322+
323+
client = streamable_http_client(
324+
url=server_params.url,
325+
http_client=httpx_client,
317326
terminate_on_close=server_params.terminate_on_close,
318327
)
319328
read, write, _ = await session_stack.enter_async_context(client)

0 commit comments

Comments
 (0)