From 3733664ed3cf4a12b59c141b442327615ccb417f Mon Sep 17 00:00:00 2001 From: Pham Khanh Hoa Date: Wed, 15 Apr 2026 21:49:35 +0700 Subject: [PATCH] fix: resolve ERR_HTTP_HEADERS_SENT and transport reuse errors - Remove redundant response send in DELETE /mcp handler (StreamableHTTPServerTransport handles headers internally) - Remove manual flushHeaders/hijack in GET /mcp handler (transport manages headers) - Convert mcp-server singleton to factory pattern (creates fresh Server instance per transport connection) This fixes two recurring Chrome MCP bridge errors: 1. ERR_HTTP_HEADERS_SENT - double header send from transport + reply.send() 2. "Already connected to a transport" - singleton Server instance reuse across connections Co-Authored-By: Claude Opus 4.6 --- dist/mcp/mcp-server.js | 19 ++++++++++--------- dist/server/index.js | 16 ++-------------- 2 files changed, 12 insertions(+), 23 deletions(-) diff --git a/dist/mcp/mcp-server.js b/dist/mcp/mcp-server.js index 1e646b8..c9e2633 100644 --- a/dist/mcp/mcp-server.js +++ b/dist/mcp/mcp-server.js @@ -1,14 +1,12 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.getMcpServer = exports.mcpServer = void 0; +exports.createMcpServer = void 0; const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js"); const register_tools_1 = require("./register-tools"); -exports.mcpServer = null; -const getMcpServer = () => { - if (exports.mcpServer) { - return exports.mcpServer; - } - exports.mcpServer = new index_js_1.Server({ +// Factory function - creates a new MCP Server instance per transport connection +// This fixes "Already connected to a transport" error from singleton reuse +const createMcpServer = () => { + const server = new index_js_1.Server({ name: 'ChromeMcpServer', version: '1.0.0', }, { @@ -16,8 +14,11 @@ const getMcpServer = () => { tools: {}, }, }); - (0, register_tools_1.setupTools)(exports.mcpServer); - return exports.mcpServer; + (0, register_tools_1.setupTools)(server); + return server; }; +exports.createMcpServer = createMcpServer; +// Backwards compatibility - still works but creates new instance each time +const getMcpServer = () => createMcpServer(); exports.getMcpServer = getMcpServer; //# sourceMappingURL=mcp-server.js.map \ No newline at end of file diff --git a/dist/server/index.js b/dist/server/index.js index 5cc6f07..7e1972b 100644 --- a/dist/server/index.js +++ b/dist/server/index.js @@ -157,17 +157,9 @@ class Server { reply.code(constant_1.HTTP_STATUS.BAD_REQUEST).send({ error: constant_1.ERROR_MESSAGES.INVALID_SSE_SESSION }); return; } - reply.raw.setHeader('Content-Type', 'text/event-stream'); - reply.raw.setHeader('Cache-Control', 'no-cache'); - reply.raw.setHeader('Connection', 'keep-alive'); - reply.raw.flushHeaders(); // Ensure headers are sent immediately try { - // transport.handleRequest will take over the response stream + // transport.handleRequest will take over the response stream and send headers await transport.handleRequest(request.raw, reply.raw); - if (!reply.sent) { - // If transport didn't send anything (unlikely for SSE initial handshake) - reply.hijack(); // Prevent Fastify from automatically sending response - } } catch (error) { if (!reply.raw.writableEnded) { @@ -176,7 +168,6 @@ class Server { } request.socket.on('close', () => { request.log.info(`SSE client disconnected for session: ${sessionId}`); - // transport's onclose should handle its own cleanup }); }); this.fastify.delete('/mcp', async (request, reply) => { @@ -189,11 +180,8 @@ class Server { return; } try { + // Let transport handleRequest close the session - do NOT send additional response await transport.handleRequest(request.raw, reply.raw); - // Assume transport.handleRequest will send response or transport.onclose will cleanup - if (!reply.sent) { - reply.code(constant_1.HTTP_STATUS.NO_CONTENT).send(); - } } catch (error) { if (!reply.sent) {