Symptom
Image client (ghcr.io/contextforge-org/mcp-reverse-proxy:latest, 2026-09-14 digest sha256:5e94395…) fails gateway discovery against a fast Streamable-HTTP MCP server:
- client logs
Error handling MCP message: Input is a zero-length, empty document x4
- initialize response never relayed -> gateway
reverse-proxy initialize timed out after 60.0s
register_complete: error -> empty catalog, empty tool list; downstream redis-outage/client-stop/restart checks fail for the same reason (5 total failures)
The August run with a clone client parsed the byte-identical server response (265 bytes) fine.
Root cause
StreamableHttpAdapter.send extracted the FIRST data: line of an SSE body and stopped. The server opens the stream with a keepalive preamble before the JSON event:
data:
id: 0
retry: 3000
data: {json}
The first data: line has an empty payload, so the handler received "" and downstream orjson.loads("") raised exactly the reported error. The 404-retry path duplicated the same parser. Secondary defects in the same code: only the first event of a multi-event body was relayed, and multi-line data: frames were not joined per the SSE spec.
Fix
- New shared parser
mcp_reverse_proxy/transports/sse_events.py (SSE line protocol: data lines join with newlines, blank line dispatches, empty keepalive events skipped, trailing unterminated event flushed).
streamablehttp_adapter relays EVERY dispatched event in order (both primary and 404-retry paths); plain-JSON bodies fall back to the raw body as before.
sse_adapter now uses the same parser (behavior preserved: typed events only).
Symptom
Image client (
ghcr.io/contextforge-org/mcp-reverse-proxy:latest, 2026-09-14 digestsha256:5e94395…) fails gateway discovery against a fast Streamable-HTTP MCP server:Error handling MCP message: Input is a zero-length, empty documentx4reverse-proxy initialize timed out after 60.0sregister_complete: error-> empty catalog, empty tool list; downstream redis-outage/client-stop/restart checks fail for the same reason (5 total failures)The August run with a clone client parsed the byte-identical server response (265 bytes) fine.
Root cause
StreamableHttpAdapter.sendextracted the FIRSTdata:line of an SSE body and stopped. The server opens the stream with a keepalive preamble before the JSON event:The first
data:line has an empty payload, so the handler received""and downstreamorjson.loads("")raised exactly the reported error. The 404-retry path duplicated the same parser. Secondary defects in the same code: only the first event of a multi-event body was relayed, and multi-linedata:frames were not joined per the SSE spec.Fix
mcp_reverse_proxy/transports/sse_events.py(SSE line protocol: data lines join with newlines, blank line dispatches, empty keepalive events skipped, trailing unterminated event flushed).streamablehttp_adapterrelays EVERY dispatched event in order (both primary and 404-retry paths); plain-JSON bodies fall back to the raw body as before.sse_adapternow uses the same parser (behavior preserved: typed events only).