Skip to content

Commit e7c7446

Browse files
committed
fix(mcpproxy): return clear error when initialize SSE ends before a response
Address review feedback: the initialize reader broke on EOF/deadline with rawMsg still nil, producing the misleading "MCP message is not a response: <nil>". Track the read error and surface a dedicated error instead, and add a regression test for an initialize stream that closes before any JSON-RPC response arrives. Signed-off-by: pjdurden <prajjwalchittori1@gmail.com>
1 parent 768c3d2 commit e7c7446

2 files changed

Lines changed: 44 additions & 3 deletions

File tree

internal/mcpproxy/mcpproxy.go

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,7 @@ func (m *mcpRequestContext) initializeSession(ctx context.Context, routeName fil
352352
}
353353
if rawMsg == nil {
354354
parser := newSSEEventParser(sseReader, backend.Name)
355+
var readErr error
355356
for rawMsg == nil {
356357
event, parseErr := parser.next()
357358
// TODO: handle reconnect. We need to re-arrange the event ID so that it will also contain the backend name and the original session ID.
@@ -366,14 +367,27 @@ func (m *mcpRequestContext) initializeSession(ctx context.Context, routeName fil
366367
}
367368
}
368369
}
370+
if rawMsg != nil {
371+
// Found the response; a trailing EOF on this same event is not a failure.
372+
break
373+
}
369374
if parseErr != nil {
370-
if errors.Is(parseErr, io.EOF) || strings.Contains(parseErr.Error(), "context deadline exceeded") {
371-
break
375+
readErr = parseErr
376+
if !errors.Is(parseErr, io.EOF) && !strings.Contains(parseErr.Error(), "context deadline exceeded") {
377+
m.l.Error("failed to read MCP GET response body", slog.String("error", parseErr.Error()))
372378
}
373-
m.l.Error("failed to read MCP GET response body", slog.String("error", parseErr.Error()))
374379
break
375380
}
376381
}
382+
if rawMsg == nil {
383+
// The SSE stream ended (EOF/deadline) or errored before any JSON-RPC
384+
// response arrived. Surface a clear error instead of falling through to
385+
// the misleading "MCP message is not a response: <nil>".
386+
if readErr != nil && !errors.Is(readErr, io.EOF) && !strings.Contains(readErr.Error(), "context deadline exceeded") {
387+
return nil, fmt.Errorf("failed to read MCP initialize response from backend %q: %w", backend.Name, readErr)
388+
}
389+
return nil, fmt.Errorf("MCP initialize stream from backend %q ended before a JSON-RPC response was received", backend.Name)
390+
}
377391
}
378392

379393
msg, ok := rawMsg.(*jsonrpc.Response)

internal/mcpproxy/mcpproxy_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,33 @@ func TestInitializeSession_InitializeFailure(t *testing.T) {
399399
require.Contains(t, err.Error(), "failed with status code")
400400
}
401401

402+
func TestInitializeSession_SSEEndsBeforeResponse(t *testing.T) {
403+
// Backend returns a 200 text/event-stream whose initialize response contains
404+
// only non-response events (a keep-alive) and then closes before ever sending
405+
// the JSON-RPC response. This must produce a clear error rather than the
406+
// misleading "MCP message is not a response: <nil>".
407+
backendServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
408+
w.Header().Set(sessionIDHeader, "test-session-123")
409+
w.Header().Set("Content-Type", "text/event-stream")
410+
w.WriteHeader(http.StatusOK)
411+
_, _ = w.Write([]byte(`id: keepalive_0000
412+
data:
413+
414+
`))
415+
}))
416+
defer backendServer.Close()
417+
418+
proxy := newTestMCPProxy()
419+
proxy.backendListenerAddr = backendServer.URL
420+
421+
sessionID, err := proxy.initializeSession(t.Context(), "route1", filterapi.MCPBackend{Name: "test-backend"}, &mcp.InitializeParams{}, time.Now())
422+
423+
require.Error(t, err)
424+
require.Empty(t, sessionID)
425+
require.Contains(t, err.Error(), "ended before a JSON-RPC response was received")
426+
require.NotContains(t, err.Error(), "is not a response")
427+
}
428+
402429
func TestInitializeSession_NotificationsInitializedFailure(t *testing.T) {
403430
// Mock backend server.
404431
var callCount perBackendCallCount

0 commit comments

Comments
 (0)