summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-02-11 20:35:17 +0200
committerPaul Buetow <paul@buetow.org>2026-02-11 20:35:17 +0200
commit97b8887fb3448fd08524111d98425859bec8789f (patch)
tree8ad6235f64ed4a98f5475e32dd502872babc6bc3
parent0a218306f8b3381610d219deca10a21406aa08cf (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.go25
-rw-r--r--internal/mcp/server_test.go22
-rw-r--r--internal/mcp/types.go12
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 {