How MCP Protocol Version Negotiation Works Between Client and Server
MCP protocol version negotiation uses a JSON-RPC handshake where the client sends supported_versions in an mcp/initialize request, and the server selects the highest common version from its implemented list, storing the result in the session context.
The codebase-memory-mcp repository implements a lightweight MCP (Memory Codebase Protocol) that requires explicit version agreement before processing tool calls. This negotiation ensures both client and server interpret subsequent messages consistently, preventing protocol mismatches during codebase memory operations.
The Initialization Handshake
The negotiation begins immediately upon connection establishment. Whether communicating via HTTP or stdin/stdout, the client initiates the handshake by sending a JSON-RPC request with the method mcp/initialize.
The request payload must contain a supported_versions array listing all protocol versions the client understands:
{
"jsonrpc": "2.0",
"id": 1,
"method": "mcp/initialize",
"params": {
"supported_versions": ["1.0", "2.0"]
}
}
Server-Side Negotiation Logic
Upon receiving the request, the server parses the JSON payload and extracts the supported_versions array. According to the reference implementation in tests/test_mcp.c (lines 203-218), the server compares the client's supported versions against its own implemented versions list, which currently includes "2.0".
The negotiation algorithm selects the highest common version shared between both lists. If multiple versions match, the server chooses the highest supported version to ensure clients benefit from the latest protocol features.
When a match exists, the server responds with a success payload containing the agreed_version:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"agreed_version": "2.0"
}
}
Connection State Management
After successful negotiation, the server stores the agreed version in the per-connection context. This context persists for the duration of the session, ensuring all subsequent MCP tool calls are validated against the negotiated version. The HTTP server implementation in src/ui/http_server.c handles this state management, routing incoming mcp/initialize requests to the version negotiation helper before processing further commands.
Error Handling for Version Mismatches
If the client and server share no overlapping versions, the server returns a JSON-RPC error response indicating an unsupported protocol version. Upon receiving this error, the client must abort the session immediately, as no compatible communication channel can be established.
Version Bump Handling
When maintainers introduce new protocol versions, they add them to the server's internal implemented list. Older clients automatically negotiate downward to the highest mutually supported version, preserving backward compatibility without requiring client updates.
Implementation Examples
Client-Side Implementation
The following Go example demonstrates constructing and sending the initialization request:
payload := map[string]any{
"jsonrpc": "2.0",
"id": 1,
"method": "mcp/initialize",
"params": map[string]any{
"supported_versions": []string{"1.0", "2.0"},
},
}
data, _ := json.Marshal(payload)
resp, _ := http.Post("http://localhost:8080/mcp", "application/json", bytes.NewReader(data))
var result struct {
Result struct{ AgreedVersion string `json:"agreed_version"` } `json:"result"`
}
json.NewDecoder(resp.Body).Decode(&result)
fmt.Println("Negotiated version:", result.Result.AgreedVersion)
Server-Side Negotiation Logic
The C implementation excerpt below shows the core version selection algorithm:
// From the MCP server implementation
static const char *implemented_versions[] = { "2.0" };
static const char *negotiate_version(json_t *params) {
json_t *client_versions = json_object_get(params, "supported_versions");
for (size_t i = 0; i < json_array_size(client_versions); ++i) {
const char *v = json_string_value(json_array_get(client_versions, i));
for (size_t j = 0; j < sizeof(implemented_versions)/sizeof(*implemented_versions); ++j) {
if (strcmp(v, implemented_versions[j]) == 0) {
return v; // Return first (highest) match
}
}
}
return NULL; // No match → error
}
Key Source Files
The MCP protocol version negotiation relies on these specific components:
-
tests/test_mcp.c(lines 203-218): Contains the "MCP PROTOCOL HELPERS" section that mirrors the production implementation, showing how the server evaluates supported versions against implemented versions. -
src/ui/http_server.c: Implements the HTTP-based MCP server request router that receivesmcp/initializemethod calls and invokes the version negotiation helpers. -
src/ui/httpd.c: Provides the client-side HTTP dispatcher utilities that construct and transmit initialization requests. -
docs/CONFIGURATION.md: Documents the supported protocol versions and specifies the expected JSON-RPC handshake format.
Summary
-
MCP protocol version negotiation begins with a client sending
supported_versionsin anmcp/initializeJSON-RPC request immediately after connection establishment. -
The server, as implemented in
tests/test_mcp.c, selects the highest common version between the client's supported list and its own implemented versions (currently"2.0"). -
The agreed version is stored in per-connection context by the HTTP server implementation in
src/ui/http_server.cand validated against all subsequent tool calls. -
When no version overlap exists, the server returns an error and the client aborts the session, ensuring protocol compatibility.
-
The design maintains backward compatibility by allowing older clients to fall back to the highest mutually supported version.
Frequently Asked Questions
What happens if the client and server share no common protocol versions?
The server returns a JSON-RPC error response indicating an unsupported protocol version. The client must abort the session immediately, as communication cannot proceed without a mutually understood protocol version.
How does the server determine which version to use when multiple versions match?
The server selects the highest common version from the intersection of the client's supported_versions array and the server's internal implemented_versions list. This prioritizes the most recent protocol features available to both parties.
Where is the negotiated version stored during an MCP session?
After successful negotiation, the server stores the agreed version in the per-connection context, which persists for the duration of the session. The src/ui/http_server.c implementation manages this state to validate subsequent tool calls against the negotiated protocol version.
How does the MCP server maintain backward compatibility with older clients?
When new protocol versions are introduced, they are added to the server's implemented versions list. Older clients that do not support the newest version automatically negotiate downward to the highest version present in both the client's supported_versions and the server's implemented list, ensuring seamless interoperability.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →