Skip to content

Session ID management gated behind enableSSE breaks MCP Streamable HTTP compatibility #123

@niksmac

Description

@niksmac

The @platformatic/mcp package only generates and returns Mcp-Session-Id headers when enableSSE: true is configured. This breaks compatibility with MCP clients that use the Streamable HTTP transport without SSE, as session ID negotiation is a core requirement of the MCP Streamable HTTP specification independent of SSE streaming.

Reproduction

// Register the plugin with enableSSE: false:
await app.register(mcpPlugin, {
  enableSSE: false,
  serverInfo: { name: 'test-server', version: '1.0.0' },
  capabilities: { tools: {}, resources: {}, prompts: {} }
})
Send an initialize request:
curl -X POST http://localhost:3000/mcp \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "initialize",
    "params": {
      "protocolVersion": "2024-11-05",
      "capabilities": {},
      "clientInfo": { "name": "test", "version": "1.0" }
    }
  }'

Expected: Response includes Mcp-Session-Id header per MCP Streamable HTTP spec.
Actual: No Mcp-Session-Id header is returned.

Root Cause
In src/routes/mcp.js, session creation is entirely gated behind the enableSSE flag:

// Line 108-125
if (enableSSE) {
    let session;
    if (sessionId) {
        const existingSession = await sessionStore.get(sessionId);
        if (existingSession) {
            session = existingSession;
        } else {
            session = await createSSESession();
            reply.header('Mcp-Session-Id', session.id);
        }
    } else {
        session = await createSSESession();
        reply.header('Mcp-Session-Id', session.id);
    }
    sessionId = session.id;
}

When enableSSE: false, the entire session lifecycle is skipped; no sessions are created, no Mcp-Session-Id headers are returned, and subsequent requests cannot include session context.

Impact
This breaks compatibility with MCP clients that implement the full Streamable HTTP transport specification, including:

  • ElevenLabs MCP client (uses Python MCP SDK); throws ExceptionGroup errors when session ID is missing
  • Any client using StreamableHttpTransport from the official MCP SDKs that expects session negotiation
  • Clients that require session persistence without needing SSE streaming

The MCP specification treats Streamable HTTP session management and SSE streaming as separate concerns. A server should be able to support session ID negotiation without implementing SSE.

Proposed Fix
Decouple session lifecycle from SSE support. The enableSSE flag should only control the GET /mcp SSE endpoint, not the session management on POST /mcp:

  1. Always create sessions and return Mcp-Session-Id on initialize requests to POST /mcp
  2. Only enable the GET /mcp SSE stream endpoint when enableSSE: true
  3. Optionally, add a separate config flag like enableSessionManagement: true (default) for fine-grained control

Workaround
Currently requires implementing a custom route wrapper that handles session ID generation and validation before delegating to the plugin, which defeats the purpose of using a managed MCP adapter.

Environment

  • @platformatic/mcp version: 1.2.2
  • Node.js version: 18+
  • Affected clients: ElevenLabs, Python MCP SDK StreamableHttpTransport, any client expecting MCP Streamable HTTP session negotiation

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions