Skip to content

Add Streamable HTTP request body limits - #3095

Merged
Kludex merged 2 commits into
mainfrom
agent/add-streamable-http-request-limit
Jul 16, 2026
Merged

Add Streamable HTTP request body limits#3095
Kludex merged 2 commits into
mainfrom
agent/add-streamable-http-request-limit

Add Streamable HTTP request body limits

37e41a2
Select commit
Loading
Failed to load commit list.
Claude / Claude Code Review completed Jul 14, 2026 in 12m 52s

Code review found 2 important issues

Found 5 candidates, confirmed 2. See review comments for details.

Details

Severity Count
🔴 Important 2
🟡 Nit 0
🟣 Pre-existing 0
Severity File:Line Issue
🔴 Important src/mcp/server/streamable_http_manager.py:99-106 Public handle_request() bypasses max_request_body_size, contradicting migration-doc claim
🔴 Important src/mcp/server/streamable_http_manager.py:395-409 Body-limit middleware caches one dict per ASGI message, allowing large memory amplification via tiny chunks

Annotations

Check failure on line 106 in src/mcp/server/streamable_http_manager.py

See this annotation in the file changed.

@claude claude / Claude Code Review

Public handle_request() bypasses max_request_body_size, contradicting migration-doc claim

The new `max_request_body_size` limit is enforced only through the new `manager.asgi_app` wrapper — `StreamableHTTPSessionManager.handle_request` remains a public, documented ASGI entry point with no size check, so existing code that mounts `handle_request` directly (as the SDK's own tests and, until this PR, `examples/stories/legacy_routing/README.md` do) silently gets no limit. That contradicts docs/migration.md's unconditional claim that "V2 applies a 4 MiB default limit" and the class docstr

Check failure on line 409 in src/mcp/server/streamable_http_manager.py

See this annotation in the file changed.

@claude claude / Claude Code Review

Body-limit middleware caches one dict per ASGI message, allowing large memory amplification via tiny chunks

RequestBodyLimitMiddleware caps only the sum of body bytes but caches one ASGI message dict (~220 bytes of object overhead) per `http.request` message, so a client trickling a chunked POST in 1-byte segments can force ~4M cached dicts (~1 GB resident) per request before the default 4 MiB limit trips — a ~200x amplification of exactly the memory bound this PR adds. Coalescing the cached body chunks into a single `bytearray` and replaying one synthesized message would bound memory at `max_body_siz