Streamable HTTP handshake crashes with ERR_HTTP_HEADERS_SENT (Fastify reply lifecycle) — broke OpenCode/Cursor
Summary
Any MCP client using the Streamable HTTP transport fails to complete the handshake against mcp-chrome-bridge v1.0.31. Single-shot curl initialize succeeds, but follow-up requests (notifications/initialized, SSE reconnection) cause the server to throw ERR_HTTP_HEADERS_SENT in a tight loop.
Repro
# 1. Start bridge (Chrome extension auto-spawns it)
# 2. Run a real MCP client that uses Streamable HTTP (e.g. OpenCode)
opencode mcp list | grep chrome
# ● ✗ chrome failed
# SSE error: Non-200 status code (400)
# http://127.0.0.1:12306/mcpServer stderr (~/Library/Logs/mcp-chrome-bridge/native_host_stderr_*.log on macOS) shows:
Error [ERR_HTTP_HEADERS_SENT]: Cannot write headers after they are sent to the client
at responseViaResponseObject (.../@hono/node-server/dist/index.cjs:838:12)
at async StreamableHTTPServerTransport.handleRequest (.../streamableHttp.js:146:9)
at async Object.<anonymous> (.../mcp-chrome-bridge/dist/server/index.js:226:17)Root cause (one-liner)
The POST /mcp, GET /mcp, DELETE /mcp handlers in app/native-server/src/server/index.ts invoke StreamableHTTPServerTransport.handleRequest(...) which writes directly to reply.raw via @hono/node-server, but Fastify's reply lifecycle then tries to manage the same reply.raw afterward and crashes. The fix is to call reply.hijack() before handing reply.raw to the SDK on all three handlers.
Fix
PR: https://github.com/hangwin/mcp-chrome/pull/377
Why doctor doesn't catch it
mcp-chrome-bridge doctor only hits /ping, not the Streamable HTTP flow. Adding a doctor probe that runs a full initialize → tools/call → delete against /mcp would prevent regressions like this.
Environment
mcp-chrome-bridgev1.0.31 (latest as of 2026-08-06)@modelcontextprotocol/sdk1.30.0- Reproduced with OpenCode 1.17.x and 1.18.14 (both fail the same way)
Source: hangwin/mcp-chrome