Follow-ups to #1610: de-draft 2026-07-28 terminology, renumber error codes, ProtocolVersion floor (#1671)
This commit is contained in:
parent
90c853103d
commit
d6b1615988
1 changed files with 785 additions and 898 deletions
|
|
@ -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' > '2'</c> ordinally); <c>false</c>
|
||||
/// otherwise. A <c>false</c> return signals that the wire emission boundary must apply
|
||||
/// the <c>{"result": <value>}</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": <value>}</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,
|
||||
|
|
|
|||
Loading…
Reference in a new issue