can1357/oh-my-pi · error · MCPTransportError
SSE response did not include a body
Error message
SSE response did not include a body
What it means
The Streamable HTTP transport POSTed a JSON-RPC request and got a 2xx response with Content-Type text/event-stream, but the Response object had a null body, so there is no SSE stream to parse. The transport treats this as a malformed server response because a valid SSE reply must carry a readable body stream. It throws immediately from #parseSSEResponse before any events are read.
Source
Thrown at packages/coding-agent/src/mcp/transports/http.ts:543
stage,
failure: "timeout",
message: `Request timeout after ${timeout}ms`,
retryable: false,
traceId,
cause: error,
});
}
if (error instanceof Error && error.name === "AbortError") throw error;
throw normalizeMCPTransportError(error, { transport: "http", stage, traceId });
} finally {
operation.clear();
}
}
#parseSSEResponse<T>(response: Response, expectedId: string | number, options?: MCPRequestOptions): Promise<T> {
const traceId = mcpTraceIdFromHeaders(response.headers);
if (!response.body) {
throw new MCPTransportError({
transport: "http",
stage: "decode",
failure: "malformed_response",
message: "SSE response did not include a body",
retryable: false,
traceId,
});
}
const timeout = resolveMCPTimeoutMs(this.config.timeout);
const operation = createMCPTimeout(timeout, this.#operationSignal(options?.signal));
const signal = operation.signal ?? getNeverAbortSignal();
const { promise, resolve, reject } = Promise.withResolvers<T>();
// The transport owns this promise until the physical stream drain exits.
// Keep a rejection observer attached even when a caller-side timeout
// abandons the request before the drain notices its abort.
void promise.catch(() => {});View on GitHub (pinned to 9690622007)
Solutions
- Inspect the raw server response with curl -N -H 'Accept: text/event-stream' to confirm the server really sends an empty SSE stream; fix or update the MCP server if so.
- Check and fix any proxy/gateway in front of the MCP server that may strip or buffer the response body (disable response buffering, e.g. proxy_buffering off in nginx).
- Update the MCP server to the latest version — returning an SSE content-type with no body violates the Streamable HTTP spec.
- Fall back to a different transport (stdio or legacy SSE) for this server if its HTTP implementation is broken.
- Since the error is marked retryable:false, do not blindly retry; capture mcpTraceIdFromHeaders trace id from server logs when reporting.
Example fix
// server (before): sets header but forgets to stream
res.setHeader('Content-Type', 'text/event-stream');
res.end();
// after: keep the stream open and write events
res.setHeader('Content-Type', 'text/event-stream');
res.write(`event: message\ndata: ${JSON.stringify(reply)}\n\n`); Defensive patterns
Strategy: try-catch
Validate before calling
const res = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', Accept: 'text/event-stream' }, body });
if (res.headers.get('content-type')?.includes('text/event-stream') && !res.body) {
throw new Error('Server returns SSE content-type with no body — server/proxy is broken');
} Type guard
function hasBody(res: Response): res is Response & { body: ReadableStream<Uint8Array> } {
return res.body !== null;
} Try / catch
try {
await client.request('tools/list');
} catch (e) {
if (e instanceof MCPTransportError && e.failure === 'malformed_response' && e.stage === 'decode') {
logger.error('MCP server returned a body-less SSE stream', { traceId: e.traceId });
// do not retry: e.retryable === false; fall back to stdio transport
} else throw e;
} Prevention
- Smoke-test new MCP servers with curl -N before wiring them in.
- Disable response buffering on proxies for SSE routes.
- Prefer the Streamable HTTP transport and current server versions.
- Avoid hosting streaming MCP endpoints on runtimes without streaming response support.
When it happens
Trigger: Server returns 200/202 with Content-Type: text/event-stream but a null body (e.g. a broken proxy or adapter that swallowed the stream); a misconfigured middleware that strips the body while keeping the SSE content-type header.
Common situations: Corporate proxies or API gateways (e.g. nginx buffering misconfig, Cloudflare workers) that return an empty streaming response; buggy MCP server implementations that set the SSE content-type without piping the event stream; serverless wrappers that buffer responses to zero bytes.
Related errors
- HTTP ${response.status} resuming MCP SSE stream: ${text}
- MCP SSE resume returned unsupported Content-Type: ${contentT
- No response received for request ID ${expectedId}
- Legacy SSE response did not include a body
- MCP request failed: ${response.status} ${response.statusText
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/63e4539951bd9d3c.
Report an issue: GitHub.