diff options
| author | Paul Buetow <paul@buetow.org> | 2026-02-11 20:35:17 +0200 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-02-11 20:35:17 +0200 |
| commit | 97b8887fb3448fd08524111d98425859bec8789f (patch) | |
| tree | 8ad6235f64ed4a98f5475e32dd502872babc6bc3 | |
| parent | 0a218306f8b3381610d219deca10a21406aa08cf (diff) | |
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 <noreply@anthropic.com>
| -rw-r--r-- | internal/mcp/server.go | 25 | ||||
| -rw-r--r-- | internal/mcp/server_test.go | 22 | ||||
| -rw-r--r-- | 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 { |
