{"record":{"id":"2d64e1eaa6cdc52c","repo":"can1357/oh-my-pi","slug":"legacy-sse-response-did-not-include-a-body","errorCode":null,"errorMessage":"Legacy SSE response did not include a body","messagePattern":"Legacy SSE response did not include a body","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/mcp/transports/sse.ts","lineNumber":90,"sourceCode":"\t\tconst connection = new AbortController();\n\t\tconst timeout = resolveMCPTimeoutMs(this.#config.timeout);\n\t\tconst operation = createMCPTimeout(timeout, connection.signal);\n\t\tconst endpointReady = Promise.withResolvers<void>();\n\t\tthis.#sseConnection = connection;\n\n\t\ttry {\n\t\t\tconst response = await this.#fetch(\n\t\t\t\tthis.#config.url,\n\t\t\t\t{ method: \"GET\", signal: operation.signal },\n\t\t\t\t{ Accept: \"text/event-stream\" },\n\t\t\t);\n\n\t\t\tif (!response.ok) {\n\t\t\t\tconst text = await response.text();\n\t\t\t\tthrow new Error(`HTTP ${response.status}: ${text}`);\n\t\t\t}\n\t\t\tif (!response.body) {\n\t\t\t\tthrow new Error(\"Legacy SSE response did not include a body\");\n\t\t\t}\n\n\t\t\tvoid this.#readSSEStream(response.body, operation, endpointReady).finally(() => {\n\t\t\t\tconst wasConnected = this.#connected;\n\t\t\t\tif (this.#sseConnection === connection) this.#sseConnection = null;\n\t\t\t\tif (wasConnected) this.onClose?.();\n\t\t\t});\n\t\t\tawait endpointReady.promise;\n\t\t} catch (error) {\n\t\t\toperation.clear();\n\t\t\tif (this.#sseConnection === connection) this.#sseConnection = null;\n\t\t\tconnection.abort();\n\t\t\tif (operation.isTimeoutAbort(error)) {\n\t\t\t\tthrow new Error(`Legacy SSE endpoint timeout after ${timeout}ms`);\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t}","sourceCodeStart":72,"sourceCodeEnd":108,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/mcp/transports/sse.ts#L72-L108","documentation":"The legacy SSE transport's connect() received a 2xx response but with a null body, so there is no event stream to read the 'endpoint' event from. A valid legacy SSE handshake requires a readable stream whose first event names the POST message endpoint; without a body the transport cannot complete initialization and throws. This runs after the !response.ok check, so the server did return success — just with nothing in it.","triggerScenarios":"Server (or intermediary) returns 200 with Content-Type: text/event-stream but an empty/immediately-closed body; a proxy that buffers and truncates the stream; server handler that sets SSE headers then ends the response.","commonSituations":"Misconfigured nginx/gateway buffering an SSE stream into an empty body; buggy or outdated MCP server SSE implementations; serverless platforms that don't support streaming responses hosting the MCP server.","solutions":["Confirm with curl -N -H 'Accept: text/event-stream' <url> whether the server actually streams an 'endpoint' event or closes immediately.","Disable proxy response buffering for the SSE route (e.g. proxy_buffering off in nginx).","If the server runs on a platform without streaming support (some serverless runtimes), move it to a streaming-capable host.","Update or fix the MCP server so a 200 SSE response always carries the stream.","Migrate to the Streamable HTTP transport, which this client prefers and most current servers support."],"exampleFix":"// server (before)\nres.writeHead(200, { 'Content-Type': 'text/event-stream' });\nres.end();\n// after: send the required endpoint event and keep alive\nres.writeHead(200, { 'Content-Type': 'text/event-stream' });\nres.write(`event: endpoint\\ndata: ${messageEndpointUrl}\\n\\n`);","handlingStrategy":"validation","validationCode":"const res = await fetch(sseUrl, { headers: { Accept: 'text/event-stream' } });\nif (res.ok && !res.body) throw new Error('Server returns 200 SSE with empty body — cannot run legacy SSE here');\nawait res.body?.cancel();","typeGuard":"function hasStreamingBody(res: Response): res is Response & { body: ReadableStream<Uint8Array> } {\n  return res.body !== null;\n}","tryCatchPattern":"try {\n  const transport = await createSseTransport(config);\n} catch (e) {\n  if (e instanceof Error && e.message.includes('did not include a body')) {\n    logger.error('Legacy SSE server/proxy returns empty streams; switch transport');\n    return useStreamableHttp(config);\n  }\n  throw e;\n}","preventionTips":["Verify with curl -N that the endpoint streams an endpoint event before configuring it.","Disable proxy buffering on the SSE route.","Host streaming MCP servers on runtimes that support streaming responses.","Prefer the Streamable HTTP transport on modern servers."],"tags":["mcp","sse","legacy-transport","malformed-response"],"backgroundTag":"empty-response-body","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}