Improve protocol version handling (#468)

Right now by default, the client and server hardcode the initial spec version, and if there's a mismatch, the request fails.

With this change, by default both client and server are allowed to float to any supported version.
This commit is contained in:
Stephen Toub 2025-06-02 23:03:57 -04:00 • committed by GitHub
commit e0b058f260
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 45 additions and 12 deletions

View file

@ -129,11 +129,12 @@ internal sealed partial class McpClient : McpEndpoint, IMcpClient
try try
{ {
// Send initialize request // Send initialize request
string requestProtocol = _options.ProtocolVersion ?? McpSession.LatestProtocolVersion;
var initializeResponse = await this.SendRequestAsync( var initializeResponse = await this.SendRequestAsync(
RequestMethods.Initialize, RequestMethods.Initialize,
new InitializeRequestParams new InitializeRequestParams
{ {
ProtocolVersion = _options.ProtocolVersion, ProtocolVersion = requestProtocol,
Capabilities = _options.Capabilities ?? new ClientCapabilities(), Capabilities = _options.Capabilities ?? new ClientCapabilities(),
ClientInfo = _options.ClientInfo ?? DefaultImplementation, ClientInfo = _options.ClientInfo ?? DefaultImplementation,
}, },
@ -154,10 +155,13 @@ internal sealed partial class McpClient : McpEndpoint, IMcpClient
_serverInstructions = initializeResponse.Instructions; _serverInstructions = initializeResponse.Instructions;
// Validate protocol version // Validate protocol version
if (initializeResponse.ProtocolVersion != _options.ProtocolVersion) bool isResponseProtocolValid =
_options.ProtocolVersion is { } optionsProtocol ? optionsProtocol == initializeResponse.ProtocolVersion :
McpSession.SupportedProtocolVersions.Contains(initializeResponse.ProtocolVersion);
if (!isResponseProtocolValid)
{ {
LogServerProtocolVersionMismatch(EndpointName, _options.ProtocolVersion, initializeResponse.ProtocolVersion); LogServerProtocolVersionMismatch(EndpointName, requestProtocol, initializeResponse.ProtocolVersion);
throw new McpException($"Server protocol version mismatch. Expected {_options.ProtocolVersion}, got {initializeResponse.ProtocolVersion}"); throw new McpException($"Server protocol version mismatch. Expected {requestProtocol}, got {initializeResponse.ProtocolVersion}");
} }
// Send initialized notification // Send initialized notification

View file

@ -34,11 +34,18 @@ public class McpClientOptions
/// Gets or sets the protocol version to request from the server, using a date-based versioning scheme. /// Gets or sets the protocol version to request from the server, using a date-based versioning scheme.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// <para>
/// The protocol version is a key part of the initialization handshake. The client and server must /// The protocol version is a key part of the initialization handshake. The client and server must
/// agree on a compatible protocol version to communicate successfully. If the server doesn't support /// agree on a compatible protocol version to communicate successfully.
/// the requested version, it will respond with a version mismatch error. /// </para>
/// <para>
/// If non-<see langword="null"/>, this version will be sent to the server, and the handshake
/// will fail if the version in the server's response does not match this version.
/// If <see langword="null"/>, the client will request the latest version supported by the server
/// but will allow any supported version that the server advertizes in its response.
/// </para>
/// </remarks> /// </remarks>
public string ProtocolVersion { get; set; } = "2024-11-05"; public string? ProtocolVersion { get; set; }
/// <summary> /// <summary>
/// Gets or sets a timeout for the client-server initialization handshake sequence. /// Gets or sets a timeout for the client-server initialization handshake sequence.

View file

@ -28,6 +28,16 @@ internal sealed partial class McpSession : IDisposable
private static readonly Histogram<double> s_serverOperationDuration = Diagnostics.CreateDurationHistogram( private static readonly Histogram<double> s_serverOperationDuration = Diagnostics.CreateDurationHistogram(
"mcp.server.operation.duration", "Measures the duration of inbound message processing.", longBuckets: false); "mcp.server.operation.duration", "Measures the duration of inbound message processing.", longBuckets: false);
/// <summary>The latest version of the protocol supported by this implementation.</summary>
internal const string LatestProtocolVersion = "2025-03-26";
/// <summary>All protocol versions supported by this implementation.</summary>
internal static readonly string[] SupportedProtocolVersions =
[
"2024-11-05",
LatestProtocolVersion,
];
private readonly bool _isServer; private readonly bool _isServer;
private readonly string _transportKind; private readonly string _transportKind;
private readonly ITransport _transport; private readonly ITransport _transport;

View file

@ -6,7 +6,7 @@ namespace ModelContextProtocol.Protocol;
/// Provides a base class for paginated requests. /// Provides a base class for paginated requests.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/2024-11-05/schema.json">See the schema for details</see> /// <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">See the schema for details</see>
/// </remarks> /// </remarks>
public class PaginatedRequestParams : RequestParams public class PaginatedRequestParams : RequestParams
{ {

View file

@ -161,9 +161,20 @@ internal sealed class McpServer : McpEndpoint, IMcpServer
UpdateEndpointNameWithClientInfo(); UpdateEndpointNameWithClientInfo();
GetSessionOrThrow().EndpointName = EndpointName; GetSessionOrThrow().EndpointName = EndpointName;
// Negotiate a protocol version. If the server options provide one, use that.
// Otherwise, try to use whatever the client requested as long as it's supported.
// If it's not supported, fall back to the latest supported version.
string? protocolVersion = options.ProtocolVersion;
if (protocolVersion is null)
{
protocolVersion = request?.ProtocolVersion is string clientProtocolVersion && McpSession.SupportedProtocolVersions.Contains(clientProtocolVersion) ?
clientProtocolVersion :
McpSession.LatestProtocolVersion;
}
return new InitializeResult return new InitializeResult
{ {
ProtocolVersion = options.ProtocolVersion, ProtocolVersion = protocolVersion,
Instructions = options.ServerInstructions, Instructions = options.ServerInstructions,
ServerInfo = options.ServerInfo ?? DefaultImplementation, ServerInfo = options.ServerInfo ?? DefaultImplementation,
Capabilities = ServerCapabilities ?? new(), Capabilities = ServerCapabilities ?? new(),

View file

@ -32,8 +32,11 @@ public class McpServerOptions
/// <remarks> /// <remarks>
/// The protocol version defines which features and message formats this server supports. /// The protocol version defines which features and message formats this server supports.
/// This uses a date-based versioning scheme in the format "YYYY-MM-DD". /// This uses a date-based versioning scheme in the format "YYYY-MM-DD".
/// If <see langword="null"/>, the server will advertize to the client the version requested
/// by the client if that version is known to be supported, and otherwise will advertize the latest
/// version supported by the server.
/// </remarks> /// </remarks>
public string ProtocolVersion { get; set; } = "2024-11-05"; public string? ProtocolVersion { get; set; }
/// <summary> /// <summary>
/// Gets or sets a timeout used for the client-server initialization handshake sequence. /// Gets or sets a timeout used for the client-server initialization handshake sequence.

View file

@ -46,7 +46,6 @@ internal static class Program
Logging = ConfigureLogging(), Logging = ConfigureLogging(),
Completions = ConfigureCompletions(), Completions = ConfigureCompletions(),
}, },
ProtocolVersion = "2024-11-05",
ServerInstructions = "This is a test server with only stub functionality", ServerInstructions = "This is a test server with only stub functionality",
}; };

View file

@ -34,7 +34,6 @@ public class Program
Resources = new(), Resources = new(),
Prompts = new(), Prompts = new(),
}; };
options.ProtocolVersion = "2024-11-05";
options.ServerInstructions = "This is a test server with only stub functionality"; options.ServerInstructions = "This is a test server with only stub functionality";
Console.WriteLine("Registering handlers."); Console.WriteLine("Registering handlers.");