Bug: Singleton McpServer causes 'Already connected to a transport' on HTTP endpoint

Author: jasoncbraatzCreated Mar 28, 2026Updated Aug 5, 2026

Description

When connecting to the HTTP MCP endpoint at http://127.0.0.1:12306/mcp, the first session initializes successfully, but all subsequent sessions fail with:

{"statusCode":500,"error":"Internal Server Error","message":"Already connected to a transport. Call close() before connecting to a new transport, or use a separate Protocol instance per connection."}

This affects both the StreamableHTTP (POST /mcp) and SSE (GET /sse) transports. The stdio transport (mcp-server-stdio.js) is unaffected because Claude Desktop spawns a fresh process per connection.

Environment

  • [email protected] (latest as of March 2026)
  • macOS, Node.js v25.8.2
  • Clients tested: Claude Desktop (Cowork/Opus sessions), curl

Root Cause

In dist/mcp/mcp-server.js, getMcpServer() uses a singleton pattern — it creates one Server instance and returns it for every connection:

javascript
exports.mcpServer = null;
const getMcpServer = () => {
    if (exports.mcpServer) {
        return exports.mcpServer;  // ← returns the already-connected instance
    }
    exports.mcpServer = new index_js_1.Server({ ... });
    setupTools(exports.mcpServer);
    return exports.mcpServer;
};

The MCP SDK's Server.connect(transport) throws if the instance is already connected to a transport. Since the HTTP server runs in a single process, the singleton gets connected on the first request and then rejects all subsequent connections.

Fix

Replace the singleton cache with a factory that creates a new Server instance per connection:

javascript
const getMcpServer = () => {
    const server = new index_js_1.Server({
        name: 'ChromeMcpServer',
        version: '1.0.0',
    }, {
        capabilities: { tools: {} },
    });
    (0, register_tools_1.setupTools)(server);
    exports.mcpServer = server;  // backward compat for stdio path
    return server;
};

This is safe because:

  • Each HTTP transport needs its own Server instance (per MCP SDK design)
  • setupTools() is stateless — it just registers tool handlers
  • The stdio path (mcp-server-stdio.js) still works because it runs in its own process

Related Issues

This is likely the root cause behind #182 and #173, where users report Claude CLI / Claude Code failing to connect to the running HTTP server.

Steps to Reproduce

  1. Start the bridge server (via Chrome extension)
  2. curl -s -X POST http://127.0.0.1:12306/mcp -H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" -d '{"jsonrpc":"2.0","method":"initialize","id":1,"params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"test1","version":"1.0"}}}' → ✅ succeeds
  3. Run the same curl again → ❌ "Already connected to a transport"

After applying the fix above, all three consecutive connections succeed with unique session IDs.