From 7f7524c8593b213992ea6918c02dd56a3044e6f6 Mon Sep 17 00:00:00 2001 From: Mathias Bergqvist Date: Wed, 29 Apr 2026 08:39:20 +0200 Subject: [PATCH] fix(mcp): do not respond to JSON-RPC notifications The supervisor's MCP HTTP handler was answering every parsed request, including notifications (messages with no id field). Per JSON-RPC 2.0, notifications must not receive a response. The Apr-29 incident saw Claude Code's MCP client receive a -32601 error for the standard notifications/initialized handshake step and disconnect immediately after a successful initialize. Skip writing the response when req.ID == nil. Cover both the known-method (notifications/initialized) and unknown-method paths with tests. Co-Authored-By: Claude Opus 4.7 (1M context) --- internal/mcp/server.go | 5 +++++ internal/mcp/server_test.go | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/internal/mcp/server.go b/internal/mcp/server.go index 1da4ee4..271a2dd 100644 --- a/internal/mcp/server.go +++ b/internal/mcp/server.go @@ -43,6 +43,11 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } + // JSON-RPC 2.0 notifications (no id) must not receive a response. + if req.ID == nil { + return + } + var result any var rpcErr *rpcError diff --git a/internal/mcp/server_test.go b/internal/mcp/server_test.go index 2a8c65c..166893f 100644 --- a/internal/mcp/server_test.go +++ b/internal/mcp/server_test.go @@ -5,6 +5,7 @@ import ( "encoding/json" "net/http" "net/http/httptest" + "strings" "testing" "github.com/mathiasbq/supervisor/internal/mcp" @@ -76,3 +77,39 @@ func TestMCPUnknownMethod(t *testing.T) { require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp)) assert.NotNil(t, resp["error"]) } + +func TestMCPNotificationKnownMethodGetsNoResponseBody(t *testing.T) { + reg := registry.New() + srv := mcp.NewServer(reg) + + // JSON-RPC 2.0 notification: "id" field absent. Per spec, server MUST NOT + // reply. notifications/initialized is part of the standard MCP handshake. + req := httptest.NewRequest(http.MethodPost, "/mcp", jsonBody(t, map[string]any{ + "jsonrpc": "2.0", + "method": "notifications/initialized", + })) + req.Header.Set("Content-Type", "application/json") + rr := httptest.NewRecorder() + srv.ServeHTTP(rr, req) + + assert.Equal(t, http.StatusOK, rr.Code) + assert.Empty(t, strings.TrimSpace(rr.Body.String()), + "notifications must not receive a response body") +} + +func TestMCPNotificationUnknownMethodGetsNoResponseBody(t *testing.T) { + reg := registry.New() + srv := mcp.NewServer(reg) + + req := httptest.NewRequest(http.MethodPost, "/mcp", jsonBody(t, map[string]any{ + "jsonrpc": "2.0", + "method": "notifications/totally-unknown", + })) + req.Header.Set("Content-Type", "application/json") + rr := httptest.NewRecorder() + srv.ServeHTTP(rr, req) + + assert.Equal(t, http.StatusOK, rr.Code) + assert.Empty(t, strings.TrimSpace(rr.Body.String()), + "unknown notifications must also receive no response body") +}