Streamable HTTP handshake crashes with ERR_HTTP_HEADERS_SENT (Fastify reply lifecycle) — broke OpenCode/Cursor

Author: richard-low-lowCreated Aug 6, 2026Updated Aug 6, 2026

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

bash
# 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/mcp

Server 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-bridge v1.0.31 (latest as of 2026-08-06)
  • @modelcontextprotocol/sdk 1.30.0
  • Reproduced with OpenCode 1.17.x and 1.18.14 (both fail the same way)