Follow-ups to #1610: de-draft 2026-07-28 terminology, renumber error codes, ProtocolVersion floor (#1671)

This commit is contained in:
Stephen Halter 2026-06-25 14:07:52 -07:00 • committed by GitHub
commit d6b1615988
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -28,22 +28,9 @@ internal sealed partial class McpSessionHandler : IAsyncDisposable
private static readonly Histogram<double> s_serverOperationDuration = Diagnostics.CreateDurationHistogram(
"mcp.server.operation.duration", "MCP request or notification duration as observed on the receiver from the time it was received until the result or ack is sent.");
/// <summary>The latest version of the protocol supported by this implementation.</summary>
internal const string LatestProtocolVersion = "2025-11-25";
/// <summary>
/// The draft protocol version (SEP-2575 + SEP-2567) that removes the <c>initialize</c> handshake
/// and <c>Mcp-Session-Id</c> and enables MRTR (Multi Round-Trip Requests) per SEP-2322.
/// Clients prefer this revision by default and fall back to the legacy <c>initialize</c> handshake
/// when the server does not support it; pin <see cref="McpClientOptions.ProtocolVersion"/> to a
/// legacy version to opt out. Servers remain reactive: with
/// <see cref="McpServerOptions.ProtocolVersion"/> left <see langword="null"/> they honor whichever
/// supported revision each peer requests, so a single server serves both draft and legacy clients.
/// </summary>
internal const string DraftProtocolVersion = "2026-07-28";
/// <summary>
/// All protocol versions supported by this implementation.
/// All protocol versions supported by this implementation. The version constants live on
/// <see cref="McpHttpHeaders"/> so the shared source file is the single source of truth.
/// Keep in sync with s_supportedProtocolVersions in StreamableHttpHandler.
/// </summary>
internal static readonly string[] SupportedProtocolVersions =
@ -51,8 +38,8 @@ internal sealed partial class McpSessionHandler : IAsyncDisposable
"2024-11-05",
"2025-03-26",
"2025-06-18",
LatestProtocolVersion,
DraftProtocolVersion,
McpHttpHeaders.November2025ProtocolVersion,
McpHttpHeaders.July2026ProtocolVersion,
];
/// <summary>
@ -66,7 +53,7 @@ internal sealed partial class McpSessionHandler : IAsyncDisposable
/// </remarks>
internal static bool SupportsPrimingEvent(string? protocolVersion)
{
const string MinResumabilityProtocolVersion = "2025-11-25";
const string MinResumabilityProtocolVersion = McpHttpHeaders.November2025ProtocolVersion;
if (protocolVersion is null)
{
@ -82,22 +69,19 @@ internal sealed partial class McpSessionHandler : IAsyncDisposable
/// </summary>
/// <param name="protocolVersion">The negotiated protocol version, or <c>null</c> if
/// negotiation has not completed.</param>
/// <returns><c>true</c> if the version is <c>"2026-06-30"</c> or later (including the
/// in-flight <c>"DRAFT-2026-06-v1"</c>, since <c>'D' &gt; '2'</c> ordinally); <c>false</c>
/// otherwise. A <c>false</c> return signals that the wire emission boundary must apply
/// the <c>{"result": &lt;value&gt;}</c> envelope expected by clients on protocol versions
/// that pre-date SEP-2106's widening of <c>outputSchema</c> to any JSON Schema 2020-12
/// document.</returns>
/// <returns><c>true</c> if the version is the <c>2026-07-28</c> revision or later, which is where
/// SEP-2106 widened <c>outputSchema</c> to any JSON Schema 2020-12 document; <c>false</c> otherwise.
/// A <c>false</c> return signals that the wire emission boundary must apply the
/// <c>{"result": &lt;value&gt;}</c> envelope expected by clients on protocol versions that pre-date
/// SEP-2106.</returns>
internal static bool SupportsNaturalOutputSchemas(string? protocolVersion)
{
const string MinNaturalOutputSchemasProtocolVersion = "2026-06-30";
if (protocolVersion is null)
{
return false;
}
return string.Compare(protocolVersion, MinNaturalOutputSchemasProtocolVersion, StringComparison.Ordinal) >= 0;
return string.Compare(protocolVersion, McpHttpHeaders.July2026ProtocolVersion, StringComparison.Ordinal) >= 0;
}
private readonly bool _isServer;
@ -169,17 +153,16 @@ internal sealed partial class McpSessionHandler : IAsyncDisposable
_outgoingMessageFilter = outgoingMessageFilter ?? (next => next);
_logger = logger;
// ping was removed in the draft protocol revision (SEP-2575). Under draft, return
// MethodNotFound; under legacy, the per-spec behavior is to always answer with PingResult.
// Liveness on draft sessions belongs to transport- and request-level timeouts, not a
// dedicated MCP RPC.
// ping was removed in the 2026-07-28 protocol revision (SEP-2575). On the 2026-07-28 or later version,
// return MethodNotFound; on an older version, the per-spec behavior is to always answer
// with PingResult. Liveness on those requests belongs to transport- and request-level
// timeouts, not a dedicated MCP RPC.
_requestHandlers.Set(
RequestMethods.Ping,
(request, jsonRpcRequest, cancellationToken) =>
{
string? perRequestVersion = jsonRpcRequest?.Context?.ProtocolVersion ?? NegotiatedProtocolVersion;
if (perRequestVersion is not null &&
StringComparer.Ordinal.Compare(perRequestVersion, DraftProtocolVersion) >= 0)
if (McpHttpHeaders.IsJuly2026OrLaterProtocolVersion(perRequestVersion))
{
throw new McpProtocolException(
$"Method '{RequestMethods.Ping}' is not available on protocol version '{perRequestVersion}'.",
@ -426,7 +409,7 @@ internal sealed partial class McpSessionHandler : IAsyncDisposable
private async Task HandleMessageAsync(JsonRpcMessage message, CancellationToken cancellationToken)
{
// Project the draft-protocol per-request _meta fields onto the message context before any
// Project the 2026-07-28 protocol's per-request _meta fields onto the message context before any
// filters run so they (and downstream handlers) can read client info / capabilities /
// protocol version / log level without re-parsing.
if (_isServer && message is JsonRpcRequest incomingRequest)
@ -570,7 +553,7 @@ internal sealed partial class McpSessionHandler : IAsyncDisposable
}
/// <summary>
/// Reads the draft-protocol per-request <c>_meta</c> fields off the request and projects them onto
/// Reads the 2026-07-28 protocol's per-request <c>_meta</c> fields off the request and projects them onto
/// <see cref="JsonRpcMessage.Context"/> so they're available without re-parsing throughout the pipeline.
/// </summary>
/// <remarks>
@ -599,8 +582,8 @@ internal sealed partial class McpSessionHandler : IAsyncDisposable
{
// If a transport-level header (e.g., the Streamable HTTP MCP-Protocol-Version header) already
// populated this, validate the body _meta matches per SEP-2575. A disagreement is reported with
// -32001 HeaderMismatch (the same code used for the Mcp-Method/Mcp-Name header-vs-body checks),
// which conformant draft clients recognize as a modern-server signal and surface as-is rather
// -32020 HeaderMismatch (the same code used for the Mcp-Method/Mcp-Name header-vs-body checks),
// which conformant 2026-07-28 clients recognize as a modern-server signal and surface as-is rather
// than mistaking it for a legacy server and falling back to the initialize handshake.
if (context.ProtocolVersion is { } existing && !string.Equals(existing, protocolVersionValue, StringComparison.Ordinal))
{
@ -629,16 +612,16 @@ internal sealed partial class McpSessionHandler : IAsyncDisposable
}
/// <summary>
/// Injects the draft-protocol per-request <c>_meta</c> fields into an outgoing request.
/// Injects the 2026-07-28 protocol's per-request <c>_meta</c> fields into an outgoing request.
/// Protocol version and client info overwrite any existing values; client capabilities are merged
/// so per-request capability opt-ins already present in the envelope are preserved.
/// </summary>
/// <remarks>
/// Used by <see cref="Client.McpClient"/> in draft mode to carry protocol version, client info, and
/// client capabilities on every outgoing request (replacing what the legacy <c>initialize</c> handshake
/// previously negotiated once).
/// Used by <see cref="Client.McpClient"/> on a 2026-07-28 or later session to carry protocol version, client
/// info, and client capabilities on every outgoing request (replacing what the legacy
/// <c>initialize</c> handshake previously negotiated once).
/// </remarks>
internal static void InjectDraftMeta(
internal static void InjectRequestMeta(
JsonRpcRequest request,
string protocolVersion,
Implementation clientInfo,