From 97b8887fb3448fd08524111d98425859bec8789f Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Wed, 11 Feb 2026 20:35:17 +0200 Subject: Fix MCP protocol version negotiation and null prompts array Two issues prevented Claude Code CLI from discovering MCP prompts: 1. Protocol version mismatch: Server always returned "2025-06-18" but Claude Code sends "2025-11-25". Per MCP spec, server must echo back the client's version if supported. Now supports all known versions (2024-11-05 through 2025-11-25). 2. Null prompts array: Go nil slices marshal as JSON null, but Claude Code expects an empty array []. Initialize prompts slice with make() to ensure [] in JSON output. Co-Authored-By: Claude Opus 4.6 --- internal/mcp/server.go | 25 ++++++++++++++++++------- internal/mcp/server_test.go | 22 ++++++++++++++++++++++ internal/mcp/types.go | 12 ++++++++++++ 3 files changed, 52 insertions(+), 7 deletions(-) diff --git a/internal/mcp/server.go b/internal/mcp/server.go index 4bc66dd..6ac7537 100644 --- a/internal/mcp/server.go +++ b/internal/mcp/server.go @@ -107,13 +107,13 @@ func (s *Server) handleInitialize(req Request) { s.logger.Printf("initialize from client: %s %s (protocol: %s)", params.ClientInfo.Name, params.ClientInfo.Version, params.ProtocolVersion) - // Validate protocol version (accept both old and new versions for compatibility) - if params.ProtocolVersion != "2024-11-05" && params.ProtocolVersion != "2025-06-18" { - s.logger.Printf("warning: unsupported protocol version: %s", params.ProtocolVersion) - } + // Negotiate protocol version: echo client's version if valid, otherwise use latest. + // This follows the MCP spec where the server responds with a version it supports. + negotiatedVersion := negotiateProtocolVersion(params.ProtocolVersion) + s.logger.Printf("negotiated protocol version: %s", negotiatedVersion) result := InitializeResult{ - ProtocolVersion: "2025-06-18", + ProtocolVersion: negotiatedVersion, Capabilities: ServerCapabilities{ Prompts: &PromptsCapability{ ListChanged: false, @@ -133,6 +133,17 @@ func (s *Server) handleInitialize(req Request) { s.sendResponse(req.ID, result) } +// negotiateProtocolVersion returns the client's version if supported, +// otherwise returns the latest version this server supports. +func negotiateProtocolVersion(clientVersion string) string { + for _, v := range ValidProtocolVersions { + if v == clientVersion { + return clientVersion + } + } + return LatestProtocolVersion +} + // handleInitialized processes the initialized notification. // This is sent by the client after receiving initialize response. func (s *Server) handleInitialized(_ Request) { @@ -166,8 +177,8 @@ func (s *Server) handlePromptsList(req Request) { return } - // Convert to PromptInfo - var infos []PromptInfo + // Convert to PromptInfo (initialize as empty slice so JSON marshals as [] not null) + infos := make([]PromptInfo, 0, len(prompts)) for _, p := range prompts { args := make([]PromptArgument, len(p.Arguments)) for i, a := range p.Arguments { diff --git a/internal/mcp/server_test.go b/internal/mcp/server_test.go index 0c767f2..0944f35 100644 --- a/internal/mcp/server_test.go +++ b/internal/mcp/server_test.go @@ -500,6 +500,28 @@ func TestServer_ReadMessage(t *testing.T) { }) } +func TestNegotiateProtocolVersion(t *testing.T) { + tests := []struct { + name string + client string + expected string + }{ + {"echoes supported version", "2025-11-25", "2025-11-25"}, + {"echoes older version", "2025-06-18", "2025-06-18"}, + {"echoes oldest version", "2024-11-05", "2024-11-05"}, + {"returns latest for unknown", "9999-01-01", LatestProtocolVersion}, + {"returns latest for empty", "", LatestProtocolVersion}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := negotiateProtocolVersion(tt.client) + if got != tt.expected { + t.Errorf("negotiateProtocolVersion(%q) = %q, want %q", tt.client, got, tt.expected) + } + }) + } +} + func TestServer_HandleInitialized(t *testing.T) { store := &mockPromptStore{prompts: make(map[string]*promptstore.Prompt)} server, _, _ := createTestServer(t, store) diff --git a/internal/mcp/types.go b/internal/mcp/types.go index e165972..0cd843a 100644 --- a/internal/mcp/types.go +++ b/internal/mcp/types.go @@ -38,6 +38,18 @@ const ( ErrCodeInternalError = -32603 // Server internal error ) +// LatestProtocolVersion is the newest MCP protocol version this server supports. +const LatestProtocolVersion = "2025-11-25" + +// ValidProtocolVersions lists all MCP protocol versions this server accepts. +// Server echoes back the client's version if valid; otherwise responds with latest. +var ValidProtocolVersions = []string{ + "2025-11-25", + "2025-06-18", + "2025-03-26", + "2024-11-05", +} + // InitializeRequest is the first message from client to establish connection. // Client sends capabilities and protocol version; server responds with its capabilities. type InitializeRequest struct { -- cgit v1.2.3