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-18 → 200 (session created).
- client
initialize with 2025-11-25 → 500, 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.
Summary (corrected)
The MCP proxy (
internal/mcpproxy, v0.7.0) failsinitializesession creation with HTTP 500failed to create MCP session to any backendwhen the backend'sinitializeSSE response begins with a non-response event (e.g. an empty/keep-alivedata:event) before the JSON-RPCinitializeresult. The proxy reads only the first SSE event, and if it isn't the response, errors:(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
initializeSSE response differently depending on the requestedprotocolVersion:protocolVersion: 2025-06-18→ first event is the response:protocolVersion: 2025-11-25(the current MCP SDK default) → an empty leading event precedes it:Through the AIEG MCP gateway:
initializewith2025-06-18→ 200 (session created).initializewith2025-11-25→ 500, logMCP message is not a response: <nil>for that backend.The backend's actual
initializeresult is identical in both cases (it negotiates down to2025-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-RPCinitializeresponse, rather than treating the first event as the response.Environment
@modelcontextprotocol/sdk(protocolVersion 2025-11-25).Workaround
We front the backend with an openresty (nginx+Lua) reverse proxy that rewrites the request body's
protocolVersiondown to2025-06-18so the upstream emits clean framing. Works, but obviously a per-backend hack.