Skip to content

Commit ac69e24

Browse files
Kludexmaxisbey
authored andcommitted
Replace httpx and httpx-sse with httpx2
httpx2 (2.5.0) is the next-generation httpx fork with server-sent events support built in, so the separate httpx-sse dependency is no longer needed. - Swap the httpx/httpx-sse dependencies for httpx2>=2.5.0 in the SDK and the example projects. - Rewrite the SSE transports against httpx2's API: aconnect_sse(...) -> client.stream(...)/client.sse(...) wrapped in EventSource, and iterate the EventSource directly instead of .aiter_sse(). - Document the swap as a v2 breaking change in docs/migration.md and update docs/installation.md, README.v2.md, and the example sources. Verified: ruff, pyright, and the full test suite pass at 100% coverage.
1 parent 1216c53 commit ac69e24

68 files changed

Lines changed: 667 additions & 654 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/get-started/installation.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ You don't need to know any of this to use the SDK, but if you're wondering what
3535
* [`anyio`](https://anyio.readthedocs.io/): the async runtime. The whole SDK is written against anyio, so it runs on either `asyncio` or `trio`.
3636
* [`pydantic`](https://docs.pydantic.dev/): what every `mcp_types` model is built on, plus all schema generation and validation.
3737
* [`pydantic-settings`](https://docs.pydantic.dev/latest/concepts/pydantic_settings/): server configuration via `MCP_*` environment variables and `.env` files.
38-
* [`httpx`](https://www.python-httpx.org/) and [`httpx-sse`](https://pypi.org/project/httpx-sse/): the HTTP client behind the Streamable HTTP and SSE *client* transports.
38+
* [`httpx2`](https://pypi.org/project/httpx2/): the HTTP client behind the Streamable HTTP and SSE *client* transports, with server-sent events support built in.
3939
* [`starlette`](https://www.starlette.io/), [`uvicorn`](https://www.uvicorn.org/), [`sse-starlette`](https://pypi.org/project/sse-starlette/), and [`python-multipart`](https://pypi.org/project/python-multipart/): the HTTP *server* transports.
4040
* [`jsonschema`](https://pypi.org/project/jsonschema/): validates a tool's structured output against its declared output schema.
4141
* [`pyjwt[crypto]`](https://pyjwt.readthedocs.io/): OAuth token handling for authorization.

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
from typing import Any
1818
from urllib.parse import parse_qs, urlparse
1919

20-
import httpx
20+
import httpx2
2121
from mcp.client._transport import ReadStream, WriteStream
2222
from mcp.client.auth import AuthorizationCodeResult, OAuthClientProvider, TokenStorage
2323
from mcp.client.session import ClientSession
@@ -233,7 +233,7 @@ async def _default_redirect_handler(authorization_url: str) -> None:
233233
await self._run_session(read_stream, write_stream)
234234
else:
235235
print("📡 Opening StreamableHTTP transport connection with auth...")
236-
async with httpx.AsyncClient(auth=oauth_auth, follow_redirects=True) as custom_client:
236+
async with httpx2.AsyncClient(auth=oauth_auth, follow_redirects=True) as custom_client:
237237
async with streamable_http_client(url=self.server_url, http_client=custom_client) as (
238238
read_stream,
239239
write_stream,

examples/clients/simple-chatbot/mcp_simple_chatbot/main.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from contextlib import AsyncExitStack
99
from typing import Any
1010

11-
import httpx
11+
import httpx2
1212
from dotenv import load_dotenv
1313
from mcp import ClientSession, StdioServerParameters
1414
from mcp.client.stdio import stdio_client
@@ -230,7 +230,7 @@ def get_response(self, messages: list[dict[str, str]]) -> str:
230230
The LLM's response as a string.
231231
232232
Raises:
233-
httpx.RequestError: If the request to the LLM fails.
233+
httpx2.RequestError: If the request to the LLM fails.
234234
"""
235235
url = "https://api.groq.com/openai/v1/chat/completions"
236236

@@ -249,17 +249,17 @@ def get_response(self, messages: list[dict[str, str]]) -> str:
249249
}
250250

251251
try:
252-
with httpx.Client() as client:
252+
with httpx2.Client() as client:
253253
response = client.post(url, headers=headers, json=payload)
254254
response.raise_for_status()
255255
data = response.json()
256256
return data["choices"][0]["message"]["content"]
257257

258-
except httpx.RequestError as e:
258+
except httpx2.RequestError as e:
259259
error_message = f"Error getting LLM response: {str(e)}"
260260
logging.error(error_message)
261261

262-
if isinstance(e, httpx.HTTPStatusError):
262+
if isinstance(e, httpx2.HTTPStatusError):
263263
status_code = e.response.status_code
264264
logging.error(f"Status code: {status_code}")
265265
logging.error(f"Response details: {e.response.text}")

examples/clients/sse-polling-client/mcp_sse_polling_client/main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ def main(url: str, items: int, checkpoint_every: int, log_level: str) -> None:
9292
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
9393
)
9494
# Suppress noisy HTTP client logging
95-
logging.getLogger("httpx").setLevel(logging.WARNING)
95+
logging.getLogger("httpx2").setLevel(logging.WARNING)
9696
logging.getLogger("httpcore").setLevel(logging.WARNING)
9797

9898
asyncio.run(run_demo(url, items, checkpoint_every))

examples/mcpserver/text_me.py

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

2020
from typing import Annotated
2121

22-
import httpx
22+
import httpx2
2323
from pydantic import BeforeValidator
2424
from pydantic_settings import BaseSettings, SettingsConfigDict
2525

@@ -44,7 +44,7 @@ class SurgeSettings(BaseSettings):
4444
@mcp.tool(name="textme", description="Send a text message to me")
4545
def text_me(text_content: str) -> str:
4646
"""Send a text message to a phone number via https://surgemsg.com/"""
47-
with httpx.Client() as client:
47+
with httpx2.Client() as client:
4848
response = client.post(
4949
"https://api.surgemsg.com/messages",
5050
headers={

examples/servers/everything-server/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ requires-python = ">=3.10"
77
authors = [{ name = "Model Context Protocol a Series of LF Projects, LLC." }]
88
keywords = ["mcp", "llm", "automation", "conformance", "testing"]
99
license = { text = "MIT" }
10-
dependencies = ["anyio>=4.5", "click>=8.2.0", "httpx>=0.27", "mcp", "starlette", "uvicorn"]
10+
dependencies = ["anyio>=4.5", "click>=8.2.0", "httpx2>=2.5.0", "mcp", "starlette", "uvicorn"]
1111

1212
[project.scripts]
1313
mcp-everything-server = "mcp_everything_server.server:main"

examples/servers/simple-auth/mcp_simple_auth/token_verifier.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,18 +33,18 @@ def __init__(
3333

3434
async def verify_token(self, token: str) -> AccessToken | None:
3535
"""Verify token via introspection endpoint."""
36-
import httpx
36+
import httpx2
3737

3838
# Validate URL to prevent SSRF attacks
3939
if not self.introspection_endpoint.startswith(("https://", "http://localhost", "http://127.0.0.1")):
4040
logger.warning(f"Rejecting introspection endpoint with unsafe scheme: {self.introspection_endpoint}")
4141
return None
4242

4343
# Configure secure HTTP client
44-
timeout = httpx.Timeout(10.0, connect=5.0)
45-
limits = httpx.Limits(max_connections=10, max_keepalive_connections=5)
44+
timeout = httpx2.Timeout(10.0, connect=5.0)
45+
limits = httpx2.Limits(max_connections=10, max_keepalive_connections=5)
4646

47-
async with httpx.AsyncClient(
47+
async with httpx2.AsyncClient(
4848
timeout=timeout,
4949
limits=limits,
5050
verify=True, # Enforce SSL verification

examples/servers/simple-auth/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ license = { text = "MIT" }
99
dependencies = [
1010
"anyio>=4.5",
1111
"click>=8.2.0",
12-
"httpx>=0.27",
12+
"httpx2>=2.5.0",
1313
"mcp",
1414
"pydantic>=2.0",
1515
"pydantic-settings>=2.5.2",

examples/servers/simple-pagination/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ classifiers = [
1414
"Programming Language :: Python :: 3",
1515
"Programming Language :: Python :: 3.10",
1616
]
17-
dependencies = ["anyio>=4.5", "click>=8.2.0", "httpx>=0.27", "mcp"]
17+
dependencies = ["anyio>=4.5", "click>=8.2.0", "httpx2>=2.5.0", "mcp"]
1818

1919
[project.scripts]
2020
mcp-simple-pagination = "mcp_simple_pagination.server:main"

examples/servers/simple-prompt/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ classifiers = [
1414
"Programming Language :: Python :: 3",
1515
"Programming Language :: Python :: 3.10",
1616
]
17-
dependencies = ["anyio>=4.5", "click>=8.2.0", "httpx>=0.27", "mcp"]
17+
dependencies = ["anyio>=4.5", "click>=8.2.0", "httpx2>=2.5.0", "mcp"]
1818

1919
[project.scripts]
2020
mcp-simple-prompt = "mcp_simple_prompt.server:main"

0 commit comments

Comments
 (0)