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:
parent
703b0fe50b
commit
e0b058f260
8 changed files with 45 additions and 12 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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(),
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.");
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue