-
Notifications
You must be signed in to change notification settings - Fork 3.8k
Follow redirects only within the MCP endpoint's origin #3397
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,7 +12,12 @@ | |
|
|
||
| from mcp.shared._compat import resync_tracer | ||
| from mcp.shared._context_streams import create_context_streams | ||
| from mcp.shared._httpx_utils import McpHttpClientFactory, create_mcp_http_client | ||
| from mcp.shared._httpx_utils import ( | ||
| McpHttpClientFactory, | ||
| create_mcp_http_client, | ||
| request_within_origin, | ||
| sse_within_origin, | ||
| ) | ||
| from mcp.shared.message import SessionMessage | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
@@ -47,15 +52,21 @@ async def sse_client( | |
| headers: Optional headers to include in requests. | ||
| timeout: HTTP timeout for regular operations (in seconds). | ||
| sse_read_timeout: Timeout for SSE read operations (in seconds). | ||
| httpx_client_factory: Factory function for creating the httpx2 client. | ||
| httpx_client_factory: Factory function for creating the httpx2 client. Whichever client it | ||
| returns, MCP requests follow a redirect only when it stays on the endpoint's origin | ||
| (same scheme, host and port, or http to https on the same host with default ports) and | ||
| keeps the request method; any other redirect is not followed, so connecting fails with | ||
| `httpx2.HTTPStatusError` for the redirect response. The client's `follow_redirects` | ||
| setting is not consulted, and requests `auth` makes during an MCP request do not follow | ||
| redirects. | ||
| auth: Optional httpx2 authentication handler. | ||
| on_session_created: Optional callback invoked with the session ID when received. | ||
| """ | ||
| logger.debug(f"Connecting to SSE endpoint: {remove_request_params(url)}") | ||
| async with httpx_client_factory( | ||
| headers=headers, auth=auth, timeout=httpx2.Timeout(timeout, read=sse_read_timeout) | ||
| ) as client: | ||
| async with client.sse(url) as event_source: | ||
| async with sse_within_origin(client, url) as event_source: | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P1: When an SSE endpoint upgrades from HTTP to HTTPS, this wrapper connects the stream over HTTPS but Prompt for AI agents |
||
| event_source.response.raise_for_status() | ||
| logger.debug("SSE connection established") | ||
|
|
||
|
|
@@ -121,13 +132,11 @@ async def post_writer(endpoint_url: str): | |
|
|
||
| async def _send_message(session_message: SessionMessage) -> None: | ||
| logger.debug(f"Sending client message: {session_message}") | ||
| response = await client.post( | ||
| response = await request_within_origin( | ||
| client, | ||
| "POST", | ||
| endpoint_url, | ||
| json=session_message.message.model_dump( | ||
| by_alias=True, | ||
| mode="json", | ||
| exclude_unset=True, | ||
| ), | ||
| json=session_message.message.model_dump(by_alias=True, mode="json", exclude_unset=True), | ||
| ) | ||
| response.raise_for_status() | ||
|
maxisbey marked this conversation as resolved.
|
||
| logger.debug(f"Client message sent successfully: {response.status_code}") | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P3: This line says the transport follows any redirect that stays on the endpoint's origin, but stream_within_origin also requires the method to be unchanged. A same-origin 301/302/303 that httpx2 turns into a GET (common for a POST) is treated as unfollowed, not followed. Qualify the wording to mention that only method-preserving redirects (e.g. 307/308) are followed.
Prompt for AI agents
-v1's internal client set
follow_redirects=True; set it explicitly when supplying your ownhttpx2.AsyncClientto preserve that behavior.+v1's internal client set
follow_redirects=True. You don't need it on your own client: the transport follows a redirect within the endpoint's origin (a trailing-slash redirect, say) itself, and does not follow one anywhere else, whatever the client is configured to do.streamable_http_clientitself keeps a small signature —streamable_http_client(url, *, http_client=None, terminate_on_close=True)— and now yields a 2-tuple (next section). The removed function's other parameters map onto the client you build:</file context>