Skip to content

MCP proxy: initialize 500s when the backend SSE response starts with a non-response event (empty/keep-alive) — "MCP message is not a response: <nil>" #2219

Description

@stephane-segning

Summary (corrected)

The MCP proxy (internal/mcpproxy, v0.7.0) fails initialize session creation with HTTP 500 failed to create MCP session to any backend when the backend's initialize SSE response begins with a non-response event (e.g. an empty/keep-alive data: event) before the JSON-RPC initialize result. The proxy reads only the first SSE event, and if it isn't the response, errors:

component=mcp-proxy backend=<name> error="MCP message is not a response: <nil>"

(My original report guessed a protocol-version downgrade check — that was wrong. The proxy does not compare echoed vs requested versions; the real trigger is the SSE framing, which for some servers varies by requested protocol version.)

Reproduction

Backend: https://mcp.firecrawl.dev/v2/mcp (Firecrawl hosted MCP, FastMCP).

firecrawl frames its initialize SSE response differently depending on the requested protocolVersion:

  • protocolVersion: 2025-06-18 → first event is the response:
    event: message
    id: …
    data: {"result":{...}}
    
  • protocolVersion: 2025-11-25 (the current MCP SDK default) → an empty leading event precedes it:
    id: …_0000_…
    data:
                        <- empty data
    
    event: message
    id: …_0001_…
    data: {"result":{...}}
    

Through the AIEG MCP gateway:

  • client initialize with 2025-06-18200 (session created).
  • client initialize with 2025-11-25500, log MCP message is not a response: <nil> for that backend.

The backend's actual initialize result is identical in both cases (it negotiates down to 2025-06-18); only the leading empty SSE event differs. Backends that don't emit a leading non-response event work regardless of the requested version.

Expected

The proxy's SSE reader should skip non-response events (empty data:, keep-alives/pings, notifications) and consume events until it finds the JSON-RPC initialize response, rather than treating the first event as the response.

Environment

  • envoyproxy/ai-gateway v0.7.0 (latest), Envoy Gateway v1.8.0. Client = @modelcontextprotocol/sdk (protocolVersion 2025-11-25).

Workaround

We front the backend with an openresty (nginx+Lua) reverse proxy that rewrites the request body's protocolVersion down to 2025-06-18 so the upstream emits clean framing. Works, but obviously a per-backend hack.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/mcpMCP proxy, MCPRoute, and MCP spec conformancebugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions