Skip to content

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

@jasoncbraatz

Description

@jasoncbraatz

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:

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:

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions