Stateful Streamable HTTP: in-session exceptions are masked as an empty -32603; real cause only in a separate "Session crashed" log
Chưa có ai nhận issue này.
Đánh giá
- Độ khó
- 4/5
- Thời gian dự kiến
- 3-5 ngày
- Mức phù hợp với người mới
- 52/100
- Loại issue
- Lỗi
- Độ rõ ràng
- Khá rõ ràng
- Mức độ hoạt động
- Ít trao đổi
- Công nghệ
- python
- Lĩnh vực
- backend-api-design, networking
Hướng nghiên cứu
Bắt đầu với _handle_stateful_request và run_server trong streamable_http_manager.py, sau đó lần theo _handle_post_request và _create_error_response trong streamable_http.py bằng bản tái hiện lỗi lifespan được cung cấp. Xác nhận cách ngoại lệ của session bị mất khi memory stream đóng. Được xem là hoàn tất khi request hoặc client để lộ ngoại lệ ban đầu và lần gửi tiếp theo không tạo ra ClosedResourceError thứ hai.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
Initial Checks
- I confirm that I'm using the latest version of MCP Python SDK
- I confirm that I searched for my issue in https://github.com/modelcontextprotocol/python-sdk/issues before opening this issue
Description
Summary
In stateful Streamable HTTP (stateless_http=False), when a session's app.run() raises, the client receives JSON-RPC -32603 with the empty message "Error handling POST request: ", and the only prominent server log is anyio.ClosedResourceError. The real exception is logged separately as Session <id> crashed and is never tied to the request — so the actual cause is nearly invisible from the client or a quick log scan.
This is distinct from the DoS crash fixed in 1.10.0 (GHSA-j975-95f5-7wqh): the process stays up. This is the remaining error-propagation / diagnosability gap.
Observed
Repro server is in Example Code below; served via uvicorn repro:app --port 8765 and hit with one initialize POST.
Client response — the real RuntimeError is absent:
HTTP/1.1 500 Internal Server Error
{"jsonrpc":"2.0","id":"server-error","error":{"code":-32603,"message":"Error handling POST request: "}}
Server log — the truth, in a separate task:
Session <id> crashed
RuntimeError: BOOM-distinctive-root-cause
Error handling POST request
anyio.ClosedResourceError
Exception in ASGI application
anyio.ClosedResourceError
(My real-world trigger was OSError: [Errno 24] inotify instance limit reached from a per-session resource; every request then 500'd with an opaque ClosedResourceError, hiding the OSError entirely.)
Root cause
_handle_stateful_request (streamable_http_manager.py) starts run_server (runs app.run() on the session's memory streams), then concurrently calls http_transport.handle_request(). If app.run() raises, run_server logs Session … crashed and connect() tears down the streams. The concurrent _handle_post_request (streamable_http.py) then:
- raises
ClosedResourceErroratwriter.send(session_message)(session read-stream already closed) — it never sees the real exception, only "stream closed"; - builds the 500 via
_create_error_response(f"Error handling POST request: {err}"), buterris now thatClosedResourceError, whosestr()is empty → empty client message; - calls
writer.send(Exception(err))into the already-closed stream, raisingClosedResourceErroragain.
The real exception is decoupled from, and invisible to, the request and the client.
Expected
The client error and/or request-path log should surface the actual exception that crashed the session, not an opaque empty -32603.
Suggested fix
- Capture
run_server's exception on the transport and surface it from_handle_post_request. - Guard the trailing
writer.send(Exception(err))withtry/except (ClosedResourceError, BrokenResourceError). - When
str(err)is empty, fall back torepr(err)/ the exception type.
Related
- GHSA-j975-95f5-7wqh / #967 (1.10.0) fixed the crash, not propagation.
- #1219, #1658 — stateless
ClosedResourceError-on-connect. - #1304 — stateless lifespan-per-request.
This is the stateful error-propagation gap that remains.
Example Code
# Minimal repro — confirmed on mcp 1.27.2 (latest), Python 3.14
# Serve with: uvicorn repro:app --port 8765
from contextlib import asynccontextmanager
from mcp.server.fastmcp import FastMCP
from mcp.server.transport_security import TransportSecuritySettings
@asynccontextmanager
async def lifespan(server):
# FastMCP's server lifespan re-enters PER SESSION in stateful mode.
# Simulate any per-session startup failure:
raise RuntimeError("BOOM-distinctive-root-cause")
yield {}
mcp = FastMCP(
"repro",
stateless_http=False,
json_response=True,
lifespan=lifespan,
transport_security=TransportSecuritySettings(enable_dns_rebinding_protection=False),
)
@mcp.tool()
def ping() -> str:
return "pong"
app = mcp.streamable_http_app()
# Then send one initialize POST (single line):
# curl -s -i -X POST http://127.0.0.1:8765/mcp -H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"c","version":"1"}}}'
Python & MCP Python SDK
Python 3.14 · mcp 1.27.2 (latest) · anyio 4.13 · starlette 1.2.1. (Originally observed on 1.27.1; confirmed identical on 1.27.2.)
- Ngôn ngữ chính
- Python
- Star
- 24.3k
- Fork
- 4k
- Merge trung bình
- 1 ngày 1 giờ
- Pull request đã merge (30 ngày)
- 31
Hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của modelcontextprotocol/python-sdk
-
Streamable HTTP client logs a WARNING for valid 202 Accepted on session termination (DELETE) Đang mởv1 v2
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 85/100
modelcontextprotocol/python-sdk#3546 · 4 bình luận ·
-
v1 v2
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 76/100
modelcontextprotocol/python-sdk#3545 · 1 bình luận ·
-
v1 v2
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 91/100
modelcontextprotocol/python-sdk#3508 · 2 bình luận ·
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 64/100
modelcontextprotocol/python-sdk#3504 ·
-
v1 v2
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 82/100
modelcontextprotocol/python-sdk#3492 · 1 bình luận ·
Tất cả issue của modelcontextprotocol/python-sdk
Issue tương tự
-
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 90/100
-
bug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 86/100
zostera/django-bootstrap4#894 ·
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
use-agent-os/agent-os#3276 ·
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 88/100
zephyrproject-rtos/zephyr#119726 ·
-
area/auth bug comp/agent P3 platform/discord type/security
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 88/100
NousResearch/hermes-agent#117848 ·