Skip to content

Commit c7c0e13

Browse files
authored
fix(server): Implement MCP protocol negotiation. (#341)
According to [the MCP spec](https://modelcontextprotocol.io/specification/2025-03-26/basic/lifecycle#version-negotiation): > If the server supports the requested protocol version, it MUST respond with > the same version. Otherwise, the server MUST respond with another protocol > version it supports. This adds `mcp.ValidProtocolVersions`, a slice with all currently specified MCP versions. In the `server` package we check if the client provided a known MCP version and, if so, return that version. Otherwise `LATEST_PROTOCOL_VERSION` is returned as previously.
1 parent 243a292 commit c7c0e13

File tree

3 files changed

+92
-1
lines changed

3 files changed

+92
-1
lines changed

mcp/types.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,12 @@ type JSONRPCMessage any
9898
// LATEST_PROTOCOL_VERSION is the most recent version of the MCP protocol.
9999
const LATEST_PROTOCOL_VERSION = "2025-03-26"
100100

101+
// ValidProtocolVersions lists all known valid MCP protocol versions.
102+
var ValidProtocolVersions = []string{
103+
"2024-11-05",
104+
LATEST_PROTOCOL_VERSION,
105+
}
106+
101107
// JSONRPC_VERSION is the version of JSON-RPC used by MCP.
102108
const JSONRPC_VERSION = "2.0"
103109

server/server.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"encoding/base64"
77
"encoding/json"
88
"fmt"
9+
"slices"
910
"sort"
1011
"sync"
1112

@@ -550,7 +551,7 @@ func (s *MCPServer) handleInitialize(
550551
}
551552

552553
result := mcp.InitializeResult{
553-
ProtocolVersion: mcp.LATEST_PROTOCOL_VERSION,
554+
ProtocolVersion: s.protocolVersion(request.Params.ProtocolVersion),
554555
ServerInfo: mcp.Implementation{
555556
Name: s.name,
556557
Version: s.version,
@@ -570,6 +571,14 @@ func (s *MCPServer) handleInitialize(
570571
return &result, nil
571572
}
572573

574+
func (s *MCPServer) protocolVersion(clientVersion string) string {
575+
if slices.Contains(mcp.ValidProtocolVersions, clientVersion) {
576+
return clientVersion
577+
}
578+
579+
return mcp.LATEST_PROTOCOL_VERSION
580+
}
581+
573582
func (s *MCPServer) handlePing(
574583
_ context.Context,
575584
_ any,

server/server_test.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1946,3 +1946,79 @@ func TestMCPServer_ToolCapabilitiesBehavior(t *testing.T) {
19461946
})
19471947
}
19481948
}
1949+
1950+
func TestMCPServer_ProtocolNegotiation(t *testing.T) {
1951+
tests := []struct {
1952+
name string
1953+
clientVersion string
1954+
expectedVersion string
1955+
}{
1956+
{
1957+
name: "Server supports client version - should respond with same version",
1958+
clientVersion: "2024-11-05",
1959+
expectedVersion: "2024-11-05", // Server must respond with client's version if supported
1960+
},
1961+
{
1962+
name: "Client requests current latest - should respond with same version",
1963+
clientVersion: mcp.LATEST_PROTOCOL_VERSION, // "2025-03-26"
1964+
expectedVersion: mcp.LATEST_PROTOCOL_VERSION,
1965+
},
1966+
{
1967+
name: "Client requests unsupported future version - should respond with server's latest",
1968+
clientVersion: "2026-01-01", // Future unsupported version
1969+
expectedVersion: mcp.LATEST_PROTOCOL_VERSION, // Server responds with its latest supported
1970+
},
1971+
{
1972+
name: "Client requests unsupported old version - should respond with server's latest",
1973+
clientVersion: "2023-01-01", // Very old unsupported version
1974+
expectedVersion: mcp.LATEST_PROTOCOL_VERSION, // Server responds with its latest supported
1975+
},
1976+
}
1977+
1978+
for _, tt := range tests {
1979+
t.Run(tt.name, func(t *testing.T) {
1980+
server := NewMCPServer("test-server", "1.0.0")
1981+
1982+
params := struct {
1983+
ProtocolVersion string `json:"protocolVersion"`
1984+
ClientInfo mcp.Implementation `json:"clientInfo"`
1985+
Capabilities mcp.ClientCapabilities `json:"capabilities"`
1986+
}{
1987+
ProtocolVersion: tt.clientVersion,
1988+
ClientInfo: mcp.Implementation{
1989+
Name: "test-client",
1990+
Version: "1.0.0",
1991+
},
1992+
}
1993+
1994+
// Create initialize request with specific protocol version
1995+
initRequest := mcp.JSONRPCRequest{
1996+
JSONRPC: "2.0",
1997+
ID: mcp.NewRequestId(int64(1)),
1998+
Request: mcp.Request{
1999+
Method: "initialize",
2000+
},
2001+
Params: params,
2002+
}
2003+
2004+
messageBytes, err := json.Marshal(initRequest)
2005+
assert.NoError(t, err)
2006+
2007+
response := server.HandleMessage(context.Background(), messageBytes)
2008+
assert.NotNil(t, response)
2009+
2010+
resp, ok := response.(mcp.JSONRPCResponse)
2011+
assert.True(t, ok)
2012+
2013+
initResult, ok := resp.Result.(mcp.InitializeResult)
2014+
assert.True(t, ok)
2015+
2016+
assert.Equal(
2017+
t,
2018+
tt.expectedVersion,
2019+
initResult.ProtocolVersion,
2020+
"Protocol version should follow MCP spec negotiation rules",
2021+
)
2022+
})
2023+
}
2024+
}

0 commit comments

Comments
 (0)