Skip to content

Commit 422d7b0

Browse files
committed
Convert httpx usage added on main since the last rebase to httpx2
New test files and comments picked up httpx imports on main; convert them to httpx2 and re-apply the dependency swap to uv.lock (httpx, httpx-sse, and httpcore out; httpx2, httpcore2, and truststore in; idna raised to the 3.18 floor httpx2 requires). No-Verification-Needed: test-only conversions plus lockfile, no runtime surface
1 parent 684dfff commit 422d7b0

7 files changed

Lines changed: 72 additions & 73 deletions

File tree

tests/client/test_client_caching.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
import anyio
1414
import anyio.lowlevel
15-
import httpx
15+
import httpx2
1616
import mcp_types as types
1717
import pytest
1818
from inline_snapshot import snapshot
@@ -836,9 +836,9 @@ async def call_tool(ctx: ServerRequestContext, params: types.CallToolRequestPara
836836

837837
server = Server("headers", on_list_tools=list_tools, on_call_tool=call_tool)
838838

839-
posts: list[httpx.Request] = []
839+
posts: list[httpx2.Request] = []
840840

841-
async def on_request(request: httpx.Request) -> None:
841+
async def on_request(request: httpx2.Request) -> None:
842842
posts.append(request)
843843

844844
config = CacheConfig(store=InMemoryResponseCacheStore(), partition="p", target_id="svc")

tests/client/test_streamable_http.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ class _ParkedSSEStream(httpx2.AsyncByteStream):
189189
"""An SSE response body that emits one comment line, then parks until closed.
190190
191191
`opened` fires once the transport is iterating the body (the POST is truly in
192-
flight); `closed` fires when httpx tears the body down — the observable proof
192+
flight); `closed` fires when httpx2 tears the body down — the observable proof
193193
that an abort, not a response, ended the stream.
194194
"""
195195

tests/docs_src/test_deploy.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""`docs/run/deploy.md`: every claim the page makes, proved against the real SDK."""
22

33
import anyio
4-
import httpx
4+
import httpx2
55
import pytest
66
from mcp_types import (
77
INVALID_PARAMS,
@@ -44,18 +44,18 @@ async def test_the_default_app_rejects_a_real_hostname_before_mcp_runs() -> None
4444
bare = MCPServer("Notes")
4545
app = bare.streamable_http_app()
4646
async with bare.session_manager.run():
47-
async with httpx.AsyncClient(transport=httpx.ASGITransport(app=app), base_url="https://api.example.com") as h:
47+
async with httpx2.AsyncClient(transport=httpx2.ASGITransport(app=app), base_url="https://api.example.com") as h:
4848
response = await h.post("/mcp", json=INITIALIZE, headers=MCP_HEADERS)
4949
assert (response.status_code, response.text) == (421, "Invalid Host header")
5050

5151

5252
async def test_the_allowlisted_app_serves_its_hostname_and_still_rejects_others() -> None:
5353
"""tutorial001: `allowed_hosts=` opens exactly the hostname you named, and nothing else."""
54-
transport = httpx.ASGITransport(app=tutorial001.app)
54+
transport = httpx2.ASGITransport(app=tutorial001.app)
5555
async with tutorial001.mcp.session_manager.run():
56-
async with httpx.AsyncClient(transport=transport, base_url="https://mcp.example.com") as http:
56+
async with httpx2.AsyncClient(transport=transport, base_url="https://mcp.example.com") as http:
5757
allowed = await http.post("/mcp", json=INITIALIZE, headers=MCP_HEADERS)
58-
async with httpx.AsyncClient(transport=transport, base_url="https://api.example.com") as http:
58+
async with httpx2.AsyncClient(transport=transport, base_url="https://api.example.com") as http:
5959
rejected = await http.post("/mcp", json=INITIALIZE, headers=MCP_HEADERS)
6060
assert allowed.status_code == 200
6161
assert allowed.headers["mcp-session-id"]

tests/docs_src/test_legacy_clients.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import inspect
44

5-
import httpx
5+
import httpx2
66
import pytest
77
from mcp_types import INVALID_REQUEST, ResourceUpdatedNotification, TextContent
88

@@ -64,8 +64,8 @@ async def test_a_legacy_session_is_minted_in_process_and_a_stray_session_id_is_a
6464
app = MCPServer("Bookshop").streamable_http_app()
6565
async with (
6666
app.router.lifespan_context(app),
67-
httpx.ASGITransport(app) as transport,
68-
httpx.AsyncClient(transport=transport, base_url="http://localhost:8000") as http,
67+
httpx2.ASGITransport(app) as transport,
68+
httpx2.AsyncClient(transport=transport, base_url="http://localhost:8000") as http,
6969
):
7070
opened = await http.post("/mcp", json=INITIALIZE, headers=MCP_HEADERS)
7171
assert opened.status_code == 200
@@ -80,8 +80,8 @@ async def test_stateless_http_never_mints_a_session() -> None:
8080
app = MCPServer("Bookshop").streamable_http_app(stateless_http=True)
8181
async with (
8282
app.router.lifespan_context(app),
83-
httpx.ASGITransport(app) as transport,
84-
httpx.AsyncClient(transport=transport, base_url="http://localhost:8000") as http,
83+
httpx2.ASGITransport(app) as transport,
84+
httpx2.AsyncClient(transport=transport, base_url="http://localhost:8000") as http,
8585
):
8686
opened = await http.post("/mcp", json=INITIALIZE, headers=MCP_HEADERS)
8787
assert opened.status_code == 200
@@ -93,8 +93,8 @@ async def test_stateless_http_kills_the_legacy_back_channel_and_only_the_legacy_
9393
the legacy client's call fails as the top-level `MCPError` the `!!! check` quotes."""
9494
async with (
9595
tutorial002.app.router.lifespan_context(tutorial002.app),
96-
httpx.ASGITransport(tutorial002.app) as transport,
97-
httpx.AsyncClient(transport=transport) as http,
96+
httpx2.ASGITransport(tutorial002.app) as transport,
97+
httpx2.AsyncClient(transport=transport) as http,
9898
):
9999
modern_target = streamable_http_client(URL, http_client=http)
100100
async with Client(modern_target, elicitation_callback=tutorial001.answer) as modern:

tests/docs_src/test_troubleshooting.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import logging
44
from typing import Any
55

6-
import httpx
6+
import httpx2
77
import pytest
88
from mcp_types import (
99
INVALID_PARAMS,
@@ -125,10 +125,10 @@ async def test_the_default_streamable_http_app_answers_a_real_hostname_with_421(
125125
caplog: pytest.LogCaptureFixture,
126126
) -> None:
127127
"""tutorial003: one 421, three spellings. The page presents all three as the same event."""
128-
transport = httpx.ASGITransport(app=tutorial003.app)
128+
transport = httpx2.ASGITransport(app=tutorial003.app)
129129
async with tutorial003.mcp.session_manager.run():
130130
# What curl (or the reverse proxy's access log) shows: the status and the plain-text body.
131-
async with httpx.AsyncClient(transport=transport, base_url="http://mcp.example.com") as raw:
131+
async with httpx2.AsyncClient(transport=transport, base_url="http://mcp.example.com") as raw:
132132
with caplog.at_level(logging.WARNING, logger="mcp.server.transport_security"):
133133
response = await raw.post("/mcp", json=INITIALIZE, headers=MCP_HEADERS)
134134
assert (response.status_code, response.text) == (421, "Invalid Host header")
@@ -137,7 +137,7 @@ async def test_the_default_streamable_http_app_answers_a_real_hostname_with_421(
137137
# What the server operator finds by grepping the server log.
138138
assert "Invalid Host header: mcp.example.com" in caplog.messages
139139
# What the python `Client` raises instead: the generic stand-in, wrapped by the task group.
140-
async with httpx.AsyncClient(transport=transport) as http_client:
140+
async with httpx2.AsyncClient(transport=transport) as http_client:
141141
client = Client(streamable_http_client("http://mcp.example.com/mcp", http_client=http_client))
142142
with pytest.raises(Exception) as exc_info: # pragma: no branch
143143
await client.__aenter__() # the connection attempt itself is what fails
@@ -147,9 +147,9 @@ async def test_the_default_streamable_http_app_answers_a_real_hostname_with_421(
147147

148148
async def test_an_allowlisted_hostname_connects_and_calls_a_tool() -> None:
149149
"""tutorial004: `transport_security=` names the deployed hostname, and the same client connects."""
150-
transport = httpx.ASGITransport(app=tutorial004.app)
150+
transport = httpx2.ASGITransport(app=tutorial004.app)
151151
async with tutorial004.mcp.session_manager.run():
152-
async with httpx.AsyncClient(transport=transport) as http_client:
152+
async with httpx2.AsyncClient(transport=transport) as http_client:
153153
allowed = streamable_http_client("http://mcp.example.com/mcp", http_client=http_client)
154154
async with Client(allowed) as c: # pragma: no branch
155155
assert c.protocol_version == "2026-07-28"
@@ -159,8 +159,8 @@ async def test_an_allowlisted_hostname_connects_and_calls_a_tool() -> None:
159159

160160
async def test_a_mounted_app_without_a_lifespan_fails_on_the_first_request() -> None:
161161
"""tutorial005: Starlette never runs a mounted sub-app's lifespan, so nothing starts the manager."""
162-
transport = httpx.ASGITransport(app=tutorial005.app)
163-
async with httpx.AsyncClient(transport=transport, base_url="http://127.0.0.1:8000") as http:
162+
transport = httpx2.ASGITransport(app=tutorial005.app)
163+
async with httpx2.AsyncClient(transport=transport, base_url="http://127.0.0.1:8000") as http:
164164
with pytest.raises(RuntimeError, match=r"Task group is not initialized\. Make sure to use run\(\)\."):
165165
await http.post("/mcp")
166166

@@ -170,7 +170,7 @@ async def test_a_session_id_the_server_never_issued_gets_a_404_session_not_found
170170
mcp = MCPServer("Weather")
171171
app = mcp.streamable_http_app()
172172
async with mcp.session_manager.run():
173-
async with httpx.AsyncClient(transport=httpx.ASGITransport(app=app), base_url="http://127.0.0.1:8000") as h:
173+
async with httpx2.AsyncClient(transport=httpx2.ASGITransport(app=app), base_url="http://127.0.0.1:8000") as h:
174174
response = await h.post(
175175
"/mcp",
176176
json={"jsonrpc": "2.0", "id": 1, "method": "tools/list", "params": {}},
@@ -243,9 +243,9 @@ async def test_a_legacy_ctx_elicit_without_a_callback_says_elicitation_not_suppo
243243

244244
async def test_ctx_elicit_over_stateless_http_has_no_back_channel() -> None:
245245
"""tutorial008: `stateless_http=True` leaves the server no channel to send `elicitation/create`."""
246-
transport = httpx.ASGITransport(app=tutorial008.app)
246+
transport = httpx2.ASGITransport(app=tutorial008.app)
247247
async with tutorial008.mcp.session_manager.run():
248-
async with httpx.AsyncClient(transport=transport) as http_client:
248+
async with httpx2.AsyncClient(transport=transport) as http_client:
249249
stateless = streamable_http_client("http://127.0.0.1:8000/mcp", http_client=http_client)
250250
async with Client(stateless) as c: # pragma: no branch
251251
with pytest.raises(MCPError) as exc_info: # pragma: no branch

tests/server/test_streamable_http_modern.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -894,7 +894,7 @@ async def test_modern_tools_call_leaves_mis_shaped_name_and_arguments_to_dispatc
894894

895895
async def test_modern_tools_call_rejects_a_duplicated_mcp_param_header() -> None:
896896
"""A duplicated recognized header is rejected even if one copy matches: readers may disagree on which wins."""
897-
# An httpx header list with a repeated name reaches the ASGI scope as two raw header lines.
897+
# An httpx2 header list with a repeated name reaches the ASGI scope as two raw header lines.
898898
duplicated = httpx2.Headers(
899899
[*_TOOL_CALL_HEADERS.items(), ("mcp-param-region", "spoofed"), ("mcp-param-region", "eu")]
900900
)

0 commit comments

Comments
 (0)