Skip to content

Commit 7605dfd

Browse files
committed
fix: propagate HTTP errors in StreamableHTTP transport instead of silently logging
1 parent 642ca88 commit 7605dfd

1 file changed

Lines changed: 38 additions & 8 deletions

File tree

src/mcp/client/streamable_http.py

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
from mcp.client._transport import TransportStreams
1919
from mcp.shared._httpx_utils import create_mcp_http_client
20+
from mcp.shared.exceptions import HttpError
2021
from mcp.shared.message import ClientMessageMetadata, SessionMessage
2122
from mcp.types import (
2223
INTERNAL_ERROR,
@@ -269,17 +270,41 @@ async def _handle_post_request(self, ctx: RequestContext) -> None:
269270

270271
if response.status_code == 404: # pragma: no branch
271272
if isinstance(message, JSONRPCRequest): # pragma: no branch
272-
error_data = ErrorData(code=INVALID_REQUEST, message="Session terminated")
273+
error_data = ErrorData(
274+
code=INVALID_REQUEST,
275+
message="Session terminated (HTTP 404)",
276+
data={"http_status": 404},
277+
)
273278
session_message = SessionMessage(JSONRPCError(jsonrpc="2.0", id=message.id, error=error_data))
274279
await ctx.read_stream_writer.send(session_message)
280+
else:
281+
raise HttpError(404, "Session terminated (HTTP 404)")
275282
return
276283

284+
if response.status_code in (401, 403):
285+
status_label = "Unauthorized" if response.status_code == 401 else "Forbidden"
286+
error_message = f"HTTP {response.status_code} {status_label}"
287+
if isinstance(message, JSONRPCRequest):
288+
error_data = ErrorData(
289+
code=INTERNAL_ERROR,
290+
message=error_message,
291+
data={"http_status": response.status_code},
292+
)
293+
session_message = SessionMessage(JSONRPCError(jsonrpc="2.0", id=message.id, error=error_data))
294+
await ctx.read_stream_writer.send(session_message)
295+
raise HttpError(response.status_code, error_message)
296+
277297
if response.status_code >= 400:
298+
error_message = f"HTTP {response.status_code}"
278299
if isinstance(message, JSONRPCRequest):
279-
error_data = ErrorData(code=INTERNAL_ERROR, message="Server returned an error response")
300+
error_data = ErrorData(
301+
code=INTERNAL_ERROR,
302+
message=error_message,
303+
data={"http_status": response.status_code},
304+
)
280305
session_message = SessionMessage(JSONRPCError(jsonrpc="2.0", id=message.id, error=error_data))
281306
await ctx.read_stream_writer.send(session_message)
282-
return
307+
raise HttpError(response.status_code, error_message)
283308

284309
if is_initialization:
285310
self._maybe_extract_session_id_from_response(response)
@@ -467,19 +492,24 @@ async def post_writer(
467492
)
468493

469494
async def handle_request_async():
470-
if is_resumption:
471-
await self._handle_resumption_request(ctx)
472-
else:
473-
await self._handle_post_request(ctx)
495+
try:
496+
if is_resumption:
497+
await self._handle_resumption_request(ctx)
498+
else:
499+
await self._handle_post_request(ctx)
500+
except Exception as exc:
501+
logger.exception("Error handling request")
502+
await read_stream_writer.send(exc)
474503

475504
# If this is a request, start a new task to handle it
476505
if isinstance(message, JSONRPCRequest):
477506
tg.start_soon(handle_request_async)
478507
else:
479508
await handle_request_async()
480509

481-
except Exception: # pragma: lax no cover
510+
except Exception as exc: # pragma: lax no cover
482511
logger.exception("Error in post_writer")
512+
await read_stream_writer.send(exc)
483513
finally:
484514
await read_stream_writer.aclose()
485515
await write_stream.aclose()

0 commit comments

Comments
 (0)