Skip to content

Commit 526d79d

Browse files
authored
test(mcp): add regression test for nil ClientInfo panic (#848)
Add a test that sends a raw initialize request without clientInfo to verify the server doesn't panic. The go-sdk client always sets clientInfo, so we bypass it with raw HTTP to reproduce the scenario from non-compliant clients. Ref: #842, #844 Signed-off-by: Marc Nuri <marc@marcnuri.com>
1 parent 7b75311 commit 526d79d

File tree

1 file changed

+50
-0
lines changed

1 file changed

+50
-0
lines changed

pkg/mcp/mcp_test.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ package mcp
22

33
import (
44
"fmt"
5+
"io"
56
"net/http"
7+
"net/http/httptest"
68
"runtime"
79
"sync"
810
"testing"
@@ -262,6 +264,54 @@ func (s *UserAgentPropagationSuite) TestFallsBackToServerPrefixWhenNoClientInfo(
262264
})
263265
}
264266

267+
func (s *UserAgentPropagationSuite) TestDoesNotPanicWhenClientInfoIsNil() {
268+
// Regression test for https://github.com/containers/kubernetes-mcp-server/issues/842
269+
// Fixed in https://github.com/containers/kubernetes-mcp-server/pull/844
270+
//
271+
// The MCP spec mandates that clientInfo is sent during initialization:
272+
// https://modelcontextprotocol.io/specification/2025-11-25/basic/lifecycle#initialization
273+
// However, some non-compliant clients omit it, which caused a nil pointer panic
274+
// in the user-agent middleware. This test verifies that the server handles
275+
// non-compliant clients gracefully by sending a raw initialize request without clientInfo.
276+
provider, err := internalk8s.NewProvider(s.Cfg)
277+
s.Require().NoError(err)
278+
s.mcpServer, err = NewServer(Configuration{StaticConfig: s.Cfg}, provider)
279+
s.Require().NoError(err)
280+
handler := s.mcpServer.ServeHTTP()
281+
strippedHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
282+
r.Header.Del("User-Agent")
283+
handler.ServeHTTP(w, r)
284+
})
285+
httpServer := httptest.NewServer(strippedHandler)
286+
defer httpServer.Close()
287+
288+
// Send raw initialize request without clientInfo (non-compliant client)
289+
endpoint := httpServer.URL + "/mcp"
290+
initResp := test.McpRawPost(s.T(), endpoint, "",
291+
`{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"protocolVersion":"2025-03-26"}}`)
292+
defer func() { _ = initResp.Body.Close() }()
293+
_, _ = io.ReadAll(initResp.Body)
294+
sessionID := initResp.Header.Get("Mcp-Session-Id")
295+
s.Require().NotEmpty(sessionID, "Expected session ID in response")
296+
297+
// Send tool call - this would panic before the fix when ClientInfo was nil
298+
toolResp := test.McpRawPost(s.T(), endpoint, sessionID,
299+
`{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"pods_list","arguments":{}}}`)
300+
defer func() { _ = toolResp.Body.Close() }()
301+
302+
s.pathHeadersMux.Lock()
303+
podsHeaders := s.pathHeaders["/api/v1/namespaces/default/pods"]
304+
s.pathHeadersMux.Unlock()
305+
306+
s.Require().NotNil(podsHeaders, "No requests were made to /api/v1/namespaces/default/pods")
307+
s.Run("User-Agent uses server prefix only when clientInfo is nil", func() {
308+
s.Equal(
309+
fmt.Sprintf("kubernetes-mcp-server/0.0.0 (%s/%s)", runtime.GOOS, runtime.GOARCH),
310+
podsHeaders.Get("User-Agent"),
311+
)
312+
})
313+
}
314+
265315
func TestUserAgentPropagation(t *testing.T) {
266316
suite.Run(t, new(UserAgentPropagationSuite))
267317
}

0 commit comments

Comments
 (0)