Tighten draft protocol negotiation boundaries (#1692)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Co-authored-by: Tarek Mahmoud Sayed <tarekms@ntdev.microsoft.com>
This commit is contained in:
parent
a8d374b280
commit
fd38f51e27
2 changed files with 1755 additions and 589 deletions
|
|
@ -27,11 +27,22 @@ internal sealed partial class McpServerImpl : McpServer
|
|||
private readonly NotificationHandlers _notificationHandlers;
|
||||
private readonly RequestHandlers _requestHandlers;
|
||||
private readonly McpSessionHandler _sessionHandler;
|
||||
private readonly string[] _supportedProtocolVersions;
|
||||
private readonly string[] _initializeHandshakeProtocolVersions;
|
||||
private readonly string[] _perRequestMetadataProtocolVersions;
|
||||
private readonly SemaphoreSlim _disposeLock = new(1, 1);
|
||||
private readonly ConcurrentDictionary<string, CancellationTokenSource> _taskCancellationSources = new();
|
||||
private readonly ConcurrentDictionary<string, MrtrContinuation> _mrtrContinuations = new();
|
||||
private readonly ConcurrentDictionary<RequestId, MrtrContext> _mrtrContextsByRequestId = new();
|
||||
|
||||
private static readonly string[] s_perRequestMetadataKeys =
|
||||
[
|
||||
MetaKeys.ProtocolVersion,
|
||||
MetaKeys.ClientInfo,
|
||||
MetaKeys.ClientCapabilities,
|
||||
MetaKeys.LogLevel,
|
||||
];
|
||||
|
||||
// Track MRTR handler tasks using the same inFlightCount + TCS pattern as
|
||||
// McpSessionHandler.ProcessMessagesCoreAsync. Starts at 1 for DisposeAsync itself.
|
||||
private int _mrtrInFlightCount = 1;
|
||||
|
|
@ -72,6 +83,9 @@ internal sealed partial class McpServerImpl : McpServer
|
|||
_sessionTransport = transport;
|
||||
ServerOptions = options;
|
||||
Services = serviceProvider;
|
||||
_supportedProtocolVersions = GetConfiguredSupportedProtocolVersions(options.ProtocolVersion);
|
||||
_initializeHandshakeProtocolVersions = [.. _supportedProtocolVersions.Where(McpProtocolVersions.SupportsInitializeHandshake)];
|
||||
_perRequestMetadataProtocolVersions = [.. _supportedProtocolVersions.Where(McpProtocolVersions.RequiresPerRequestMetadata)];
|
||||
_serverOnlyEndpointName = $"Server ({options.ServerInfo?.Name ?? DefaultImplementation.Name} {options.ServerInfo?.Version ?? DefaultImplementation.Version})";
|
||||
_endpointName = _serverOnlyEndpointName;
|
||||
_servicesScopePerRequest = options.ScopeRequests;
|
||||
|
|
@ -161,33 +175,93 @@ internal sealed partial class McpServerImpl : McpServer
|
|||
/// MUST be populated per-request. Per-request client capabilities and client info are consumed request-scoped
|
||||
/// by <see cref="DestinationBoundMcpServer"/> and are not read from server-wide state by request handlers. The
|
||||
/// shared <see cref="_clientInfo"/> write below is best-effort and used only to derive the session endpoint
|
||||
/// name for logging/telemetry. For legacy clients the per-request values are absent and the built-in filter is
|
||||
/// a no-op (the values were captured during the initialize handler).
|
||||
/// name for logging/telemetry. For initialize-handshake clients the per-request values are absent and the built-in
|
||||
/// filter is a no-op (the values were captured during the initialize handler).
|
||||
/// </remarks>
|
||||
private JsonRpcMessageFilter PrependMetaReadingFilter(JsonRpcMessageFilter inner)
|
||||
{
|
||||
JsonRpcMessageFilter metaReadingFilter = next => async (message, cancellationToken) =>
|
||||
{
|
||||
if (message is JsonRpcRequest { Method: not RequestMethods.Initialize } request && request.Context is { } context)
|
||||
if (message is JsonRpcRequest { Method: RequestMethods.Initialize } initializeRequest)
|
||||
{
|
||||
ValidateInitializeRequestBoundary(initializeRequest);
|
||||
}
|
||||
else if (message is JsonRpcRequest request)
|
||||
{
|
||||
var context = request.Context;
|
||||
bool endpointNameNeedsRefresh = false;
|
||||
bool hasProtocolVersionMeta = HasMetaKey(request, MetaKeys.ProtocolVersion);
|
||||
bool hasReservedPerRequestMeta = TryGetPerRequestMetadataKey(request, out var reservedPerRequestMetaKey);
|
||||
|
||||
if (context.ProtocolVersion is { } protocolVersion)
|
||||
if (context?.ProtocolVersion is { } protocolVersion)
|
||||
{
|
||||
bool protocolVersionAlreadyEstablished = _negotiatedProtocolVersion is not null;
|
||||
if (protocolVersionAlreadyEstablished)
|
||||
{
|
||||
SetNegotiatedProtocolVersion(protocolVersion);
|
||||
}
|
||||
|
||||
// Per SEP-2575, the server MUST reject any request whose per-request
|
||||
// _meta/io.modelcontextprotocol/protocolVersion is not one of its supported versions
|
||||
// with an UnsupportedProtocolVersionError (-32022) carrying the supported list.
|
||||
if (!McpSessionHandler.SupportedProtocolVersions.Contains(protocolVersion))
|
||||
if (!_supportedProtocolVersions.Contains(protocolVersion))
|
||||
{
|
||||
throw new UnsupportedProtocolVersionException(
|
||||
requested: protocolVersion,
|
||||
supported: McpSessionHandler.SupportedProtocolVersions);
|
||||
supported: _supportedProtocolVersions);
|
||||
}
|
||||
|
||||
SetNegotiatedProtocolVersion(protocolVersion);
|
||||
if (McpProtocolVersions.RequiresPerRequestMetadata(protocolVersion))
|
||||
{
|
||||
ValidateRequiredPerRequestMetadata(
|
||||
protocolVersion,
|
||||
hasProtocolVersionMeta,
|
||||
context.ClientInfo is not null,
|
||||
context.ClientCapabilities is not null);
|
||||
}
|
||||
else if (McpProtocolVersions.SupportsInitializeHandshake(protocolVersion))
|
||||
{
|
||||
if (_negotiatedProtocolVersion is null && hasProtocolVersionMeta)
|
||||
{
|
||||
throw new UnsupportedProtocolVersionException(
|
||||
requested: protocolVersion,
|
||||
supported: _perRequestMetadataProtocolVersions,
|
||||
message: $"Protocol version '{protocolVersion}' requires the initialize handshake and cannot be selected through per-request metadata.");
|
||||
}
|
||||
|
||||
if (hasReservedPerRequestMeta)
|
||||
{
|
||||
ThrowReservedPerRequestMetadata(requestedProtocolVersion: protocolVersion, reservedPerRequestMetaKey);
|
||||
}
|
||||
}
|
||||
|
||||
if (!protocolVersionAlreadyEstablished)
|
||||
{
|
||||
SetNegotiatedProtocolVersion(protocolVersion);
|
||||
}
|
||||
}
|
||||
else if (_negotiatedProtocolVersion is null)
|
||||
{
|
||||
if (request.Method == RequestMethods.ServerDiscover)
|
||||
{
|
||||
throw new McpProtocolException(
|
||||
$"The '{RequestMethods.ServerDiscover}' request requires per-request metadata declaring a supported protocol version.",
|
||||
McpErrorCode.InvalidParams);
|
||||
}
|
||||
|
||||
if (hasReservedPerRequestMeta)
|
||||
{
|
||||
ThrowReservedPerRequestMetadata(requestedProtocolVersion: null, reservedPerRequestMetaKey);
|
||||
}
|
||||
}
|
||||
else if (McpProtocolVersions.SupportsInitializeHandshake(_negotiatedProtocolVersion) && hasReservedPerRequestMeta)
|
||||
{
|
||||
ThrowReservedPerRequestMetadata(_negotiatedProtocolVersion, reservedPerRequestMetaKey);
|
||||
}
|
||||
|
||||
if (context.ClientInfo is { } clientInfo &&
|
||||
ValidateRequestMethodBoundary(request);
|
||||
|
||||
if (context?.ClientInfo is { } clientInfo &&
|
||||
(_clientInfo is null || !string.Equals(_clientInfo.Name, clientInfo.Name, StringComparison.Ordinal) ||
|
||||
!string.Equals(_clientInfo.Version, clientInfo.Version, StringComparison.Ordinal)))
|
||||
{
|
||||
|
|
@ -207,6 +281,10 @@ internal sealed partial class McpServerImpl : McpServer
|
|||
_sessionHandler.EndpointName = _endpointName;
|
||||
}
|
||||
}
|
||||
else if (message is JsonRpcNotification notification)
|
||||
{
|
||||
ValidateNotificationBoundary(notification);
|
||||
}
|
||||
|
||||
await next(message, cancellationToken).ConfigureAwait(false);
|
||||
};
|
||||
|
|
@ -214,6 +292,142 @@ internal sealed partial class McpServerImpl : McpServer
|
|||
return next => metaReadingFilter(inner(next));
|
||||
}
|
||||
|
||||
private static void ValidateRequiredPerRequestMetadata(
|
||||
string protocolVersion,
|
||||
bool hasProtocolVersionMeta,
|
||||
bool hasClientInfoMeta,
|
||||
bool hasClientCapabilitiesMeta)
|
||||
{
|
||||
if (!hasProtocolVersionMeta)
|
||||
{
|
||||
ThrowMissingPerRequestMetadata(protocolVersion, MetaKeys.ProtocolVersion);
|
||||
}
|
||||
|
||||
if (!hasClientInfoMeta)
|
||||
{
|
||||
ThrowMissingPerRequestMetadata(protocolVersion, MetaKeys.ClientInfo);
|
||||
}
|
||||
|
||||
if (!hasClientCapabilitiesMeta)
|
||||
{
|
||||
ThrowMissingPerRequestMetadata(protocolVersion, MetaKeys.ClientCapabilities);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ThrowMissingPerRequestMetadata(string protocolVersion, string key) =>
|
||||
throw new McpProtocolException(
|
||||
$"Requests using protocol version '{protocolVersion}' must include '_meta/{key}'.",
|
||||
McpErrorCode.InvalidParams);
|
||||
|
||||
private static void ThrowReservedPerRequestMetadata(string? requestedProtocolVersion, string key) =>
|
||||
throw new McpProtocolException(
|
||||
requestedProtocolVersion is null
|
||||
? $"The reserved per-request metadata key '_meta/{key}' requires a protocol version that uses per-request metadata."
|
||||
: $"The reserved per-request metadata key '_meta/{key}' is not valid with protocol version '{requestedProtocolVersion}'.",
|
||||
McpErrorCode.InvalidRequest);
|
||||
|
||||
private static bool TryGetPerRequestMetadataKey(JsonRpcRequest request, out string key)
|
||||
{
|
||||
foreach (var candidate in s_perRequestMetadataKeys)
|
||||
{
|
||||
if (HasMetaKey(request, candidate))
|
||||
{
|
||||
key = candidate;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
key = "";
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool HasMetaKey(JsonRpcRequest request, string key) =>
|
||||
request.Params is JsonObject paramsObj &&
|
||||
paramsObj["_meta"] is JsonObject metaObj &&
|
||||
metaObj.ContainsKey(key);
|
||||
|
||||
private void ValidateInitializeRequestBoundary(JsonRpcRequest request)
|
||||
{
|
||||
if (request.Context?.ProtocolVersion is { } protocolVersion &&
|
||||
!McpProtocolVersions.SupportsInitializeHandshake(protocolVersion))
|
||||
{
|
||||
throw new UnsupportedProtocolVersionException(
|
||||
requested: protocolVersion,
|
||||
supported: _initializeHandshakeProtocolVersions,
|
||||
message: $"Protocol version '{protocolVersion}' is not available through the initialize handshake.");
|
||||
}
|
||||
|
||||
if (TryGetPerRequestMetadataKey(request, out var key))
|
||||
{
|
||||
ThrowReservedPerRequestMetadata(TryGetStringParam(request, "protocolVersion"), key);
|
||||
}
|
||||
}
|
||||
|
||||
private static string? TryGetStringParam(JsonRpcRequest request, string propertyName)
|
||||
{
|
||||
if (request.Params is JsonObject paramsObj &&
|
||||
paramsObj[propertyName] is JsonValue value &&
|
||||
value.TryGetValue(out string? result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string[] GetConfiguredSupportedProtocolVersions(string? protocolVersion)
|
||||
{
|
||||
if (protocolVersion is null)
|
||||
{
|
||||
return McpProtocolVersions.SupportedProtocolVersions;
|
||||
}
|
||||
|
||||
if (!McpProtocolVersions.IsSupportedProtocolVersion(protocolVersion))
|
||||
{
|
||||
throw new McpException(
|
||||
$"Unsupported server protocol version '{protocolVersion}'. Supported protocol versions: " +
|
||||
string.Join(", ", McpProtocolVersions.SupportedProtocolVersions) + ".");
|
||||
}
|
||||
|
||||
return [protocolVersion];
|
||||
}
|
||||
|
||||
private void ValidateNotificationBoundary(JsonRpcNotification notification)
|
||||
{
|
||||
if (notification.Method == NotificationMethods.InitializedNotification &&
|
||||
McpProtocolVersions.RequiresPerRequestMetadata(notification.Context?.ProtocolVersion ?? _negotiatedProtocolVersion))
|
||||
{
|
||||
throw new McpProtocolException(
|
||||
$"The notification '{NotificationMethods.InitializedNotification}' is only valid after the initialize handshake.",
|
||||
McpErrorCode.InvalidRequest);
|
||||
}
|
||||
}
|
||||
|
||||
private void ValidateRequestMethodBoundary(JsonRpcRequest request)
|
||||
{
|
||||
bool usesPerRequestMetadata = IsJuly2026OrLaterProtocolRequest(request);
|
||||
|
||||
if (!usesPerRequestMetadata &&
|
||||
request.Method is RequestMethods.SubscriptionsListen
|
||||
or RequestMethods.TasksGet
|
||||
or RequestMethods.TasksUpdate
|
||||
or RequestMethods.TasksCancel
|
||||
or RequestMethods.ServerDiscover)
|
||||
{
|
||||
throw new McpProtocolException(
|
||||
$"The method '{request.Method}' requires a newer protocol revision that supports per-request metadata; " +
|
||||
$"the negotiated protocol version is '{NegotiatedProtocolVersion ?? "(none)"}'.",
|
||||
McpErrorCode.MethodNotFound);
|
||||
}
|
||||
|
||||
if (usesPerRequestMetadata && request.Method == RequestMethods.LoggingSetLevel)
|
||||
{
|
||||
throw new McpProtocolException(
|
||||
$"The method '{RequestMethods.LoggingSetLevel}' is not available on protocol version '{request.Context?.ProtocolVersion ?? NegotiatedProtocolVersion}'. Use per-request _meta/{MetaKeys.LogLevel} instead.",
|
||||
McpErrorCode.MethodNotFound);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string? SessionId => _sessionTransport.SessionId;
|
||||
|
||||
|
|
@ -356,26 +570,54 @@ internal sealed partial class McpServerImpl : McpServer
|
|||
UpdateEndpointNameWithClientInfo();
|
||||
_sessionHandler.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.
|
||||
// Negotiate an initialize-handshake protocol version. initialize is not available in the 2026-07-28
|
||||
// and later protocol revisions, so those versions must use server/discover with
|
||||
// per-request _meta instead.
|
||||
string? protocolVersion = options.ProtocolVersion;
|
||||
protocolVersion ??= request?.ProtocolVersion is string clientProtocolVersion &&
|
||||
McpSessionHandler.SupportedProtocolVersions.Contains(clientProtocolVersion) ?
|
||||
clientProtocolVersion :
|
||||
McpHttpHeaders.November2025ProtocolVersion;
|
||||
if (protocolVersion is { } configuredProtocolVersion &&
|
||||
McpProtocolVersions.IsJuly2026OrLaterProtocolVersion(configuredProtocolVersion))
|
||||
{
|
||||
throw new UnsupportedProtocolVersionException(
|
||||
configuredProtocolVersion,
|
||||
_initializeHandshakeProtocolVersions,
|
||||
$"Protocol version '{configuredProtocolVersion}' is not available through the initialize handshake.");
|
||||
}
|
||||
|
||||
// The legacy initialize handshake is authoritative: it may supersede a protocol version
|
||||
// a prior server/discover probe established on the same connection (the dual-era
|
||||
if (protocolVersion is null)
|
||||
{
|
||||
if (request?.ProtocolVersion is string clientProtocolVersion)
|
||||
{
|
||||
if (McpProtocolVersions.IsJuly2026OrLaterProtocolVersion(clientProtocolVersion))
|
||||
{
|
||||
throw new UnsupportedProtocolVersionException(
|
||||
clientProtocolVersion,
|
||||
_initializeHandshakeProtocolVersions,
|
||||
$"Protocol version '{clientProtocolVersion}' is not available through the initialize handshake.");
|
||||
}
|
||||
|
||||
protocolVersion = McpProtocolVersions.SupportsInitializeHandshake(clientProtocolVersion) ?
|
||||
clientProtocolVersion :
|
||||
McpProtocolVersions.November2025ProtocolVersion;
|
||||
}
|
||||
else
|
||||
{
|
||||
protocolVersion = McpProtocolVersions.November2025ProtocolVersion;
|
||||
}
|
||||
}
|
||||
|
||||
string negotiatedProtocolVersion = protocolVersion ?? McpProtocolVersions.November2025ProtocolVersion;
|
||||
|
||||
// The initialize handshake is authoritative: it may supersede a protocol version
|
||||
// a prior server/discover probe established on the same connection (the dual-path
|
||||
// fallback path a permissive client takes against an unknown server). Unlike the
|
||||
// per-request 2026-07-28 version - which SetNegotiatedProtocolVersion locks once negotiated -
|
||||
// initialize force-sets the version.
|
||||
_negotiatedProtocolVersion = protocolVersion;
|
||||
_sessionHandler.NegotiatedProtocolVersion = protocolVersion;
|
||||
_negotiatedProtocolVersion = negotiatedProtocolVersion;
|
||||
_sessionHandler.NegotiatedProtocolVersion = negotiatedProtocolVersion;
|
||||
|
||||
return new InitializeResult
|
||||
{
|
||||
ProtocolVersion = protocolVersion,
|
||||
ProtocolVersion = negotiatedProtocolVersion,
|
||||
Instructions = options.ServerInstructions,
|
||||
ServerInfo = options.ServerInfo ?? DefaultImplementation,
|
||||
Capabilities = ServerCapabilities ?? new(),
|
||||
|
|
@ -389,9 +631,9 @@ internal sealed partial class McpServerImpl : McpServer
|
|||
/// Registers the <c>server/discover</c> request handler introduced by the 2026-07-28 protocol revision (SEP-2575).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The handler is registered unconditionally so legacy clients can probe it too. It returns the server's
|
||||
/// supported protocol versions (<see cref="McpSessionHandler.SupportedProtocolVersions"/>), server
|
||||
/// capabilities, server info, and optional instructions.
|
||||
/// The handler is registered unconditionally so requests can be routed to the protocol boundary filters. Successful
|
||||
/// <c>server/discover</c> responses advertise only protocol versions available through per-request metadata; versions
|
||||
/// that require the <c>initialize</c> handshake are negotiated through <c>initialize</c> instead.
|
||||
/// </remarks>
|
||||
private void ConfigureDiscover(McpServerOptions options)
|
||||
{
|
||||
|
|
@ -400,7 +642,7 @@ internal sealed partial class McpServerImpl : McpServer
|
|||
{
|
||||
return new ValueTask<DiscoverResult>(new DiscoverResult
|
||||
{
|
||||
SupportedVersions = [.. McpSessionHandler.SupportedProtocolVersions],
|
||||
SupportedVersions = [.. _perRequestMetadataProtocolVersions],
|
||||
Capabilities = ServerCapabilities ?? new(),
|
||||
ServerInfo = options.ServerInfo ?? DefaultImplementation,
|
||||
Instructions = options.ServerInstructions,
|
||||
|
|
@ -435,6 +677,14 @@ internal sealed partial class McpServerImpl : McpServer
|
|||
_requestHandlers.Set(RequestMethods.SubscriptionsListen,
|
||||
async (request, jsonRpcRequest, cancellationToken) =>
|
||||
{
|
||||
if (!IsJuly2026OrLaterProtocolRequest(jsonRpcRequest))
|
||||
{
|
||||
throw new McpProtocolException(
|
||||
$"The method '{RequestMethods.SubscriptionsListen}' requires a newer protocol revision that supports per-request subscriptions; " +
|
||||
$"the negotiated protocol version is '{NegotiatedProtocolVersion ?? "(none)"}'.",
|
||||
McpErrorCode.MethodNotFound);
|
||||
}
|
||||
|
||||
var requested = request?.Notifications ?? new SubscriptionsListenNotifications();
|
||||
|
||||
// A stateless session (Streamable HTTP with no session) cannot deliver out-of-band
|
||||
|
|
@ -442,7 +692,7 @@ internal sealed partial class McpServerImpl : McpServer
|
|||
// changes back to the client (tracked by #1662). Rather than hold the POST open forever only
|
||||
// to deliver nothing - pinning the connection and its request scope - acknowledge the listen
|
||||
// request granting no notifications and complete immediately. This runs after protocol
|
||||
// negotiation, so it is not a legacy-server signal and never triggers a client fallback to the
|
||||
// negotiation, so it is not an initialize-handshake-server signal and never triggers a client fallback to the
|
||||
// initialize handshake.
|
||||
if (!HasStatefulTransport())
|
||||
{
|
||||
|
|
@ -522,7 +772,7 @@ internal sealed partial class McpServerImpl : McpServer
|
|||
/// </remarks>
|
||||
private async Task SendListChangedNotificationAsync(string notificationMethod)
|
||||
{
|
||||
// Legacy clients never open a subscriptions/listen stream, so they keep the session-wide broadcast.
|
||||
// Initialize-handshake clients never open a subscriptions/listen stream, so they keep the session-wide broadcast.
|
||||
// subscriptions/listen is a SEP-2575 feature, so clients on the 2026-07-28 or later revision instead get
|
||||
// a fan-out limited to the notification types they explicitly subscribed to.
|
||||
if (!IsJuly2026OrLaterProtocol())
|
||||
|
|
@ -805,8 +1055,8 @@ internal sealed partial class McpServerImpl : McpServer
|
|||
cancelTaskHandler ??= (static async (request, _) => throw new McpProtocolException($"Unknown task: '{request.Params?.TaskId}'", McpErrorCode.InvalidParams));
|
||||
|
||||
// The tasks/* methods do not exist before the 2026-07-28 revision (SEP-2663). Reject them with
|
||||
// MethodNotFound when the request was negotiated under a legacy protocol version. The handlers
|
||||
// stay registered so a dual-era server still serves them for 2026-07-28 requests.
|
||||
// MethodNotFound when the request was negotiated under an initialize-handshake protocol version. The handlers
|
||||
// stay registered so a dual-path server still serves them for 2026-07-28 requests.
|
||||
getTaskHandler = GateTaskMethodToJuly2026OrLaterProtocol(getTaskHandler, RequestMethods.TasksGet);
|
||||
updateTaskHandler = GateTaskMethodToJuly2026OrLaterProtocol(updateTaskHandler, RequestMethods.TasksUpdate);
|
||||
cancelTaskHandler = GateTaskMethodToJuly2026OrLaterProtocol(cancelTaskHandler, RequestMethods.TasksCancel);
|
||||
|
|
@ -883,7 +1133,7 @@ internal sealed partial class McpServerImpl : McpServer
|
|||
listResourceTemplatesHandler ??= (static async (_, __) => new ListResourceTemplatesResult());
|
||||
readResourceHandler ??= (static async (request, _) =>
|
||||
{
|
||||
var errorCode = McpHttpHeaders.UseInvalidParamsForMissingResource(request.Server.NegotiatedProtocolVersion)
|
||||
var errorCode = McpProtocolVersions.UseInvalidParamsForMissingResource(request.Server.NegotiatedProtocolVersion)
|
||||
? McpErrorCode.InvalidParams
|
||||
: McpErrorCode.ResourceNotFound;
|
||||
throw new McpProtocolException($"Unknown resource URI: '{request.Params?.Uri}'", errorCode);
|
||||
|
|
@ -1560,6 +1810,13 @@ internal sealed partial class McpServerImpl : McpServer
|
|||
RequestMethods.LoggingSetLevel,
|
||||
(request, jsonRpcRequest, cancellationToken) =>
|
||||
{
|
||||
if (IsJuly2026OrLaterProtocolRequest(jsonRpcRequest))
|
||||
{
|
||||
throw new McpProtocolException(
|
||||
$"The method '{RequestMethods.LoggingSetLevel}' is not available on protocol version '{jsonRpcRequest.Context?.ProtocolVersion ?? NegotiatedProtocolVersion}'. Use per-request _meta/{MetaKeys.LogLevel} instead.",
|
||||
McpErrorCode.MethodNotFound);
|
||||
}
|
||||
|
||||
// Store the provided level.
|
||||
if (request is not null)
|
||||
{
|
||||
|
|
@ -1794,7 +2051,7 @@ internal sealed partial class McpServerImpl : McpServer
|
|||
|
||||
/// <inheritdoc cref="IsJuly2026OrLaterProtocolRequest(JsonRpcRequest?)"/>
|
||||
internal bool IsJuly2026OrLaterProtocolRequest(JsonRpcMessageContext? requestContext) =>
|
||||
McpHttpHeaders.IsJuly2026OrLaterProtocolVersion(
|
||||
McpProtocolVersions.IsJuly2026OrLaterProtocolVersion(
|
||||
requestContext?.ProtocolVersion ?? NegotiatedProtocolVersion);
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ public sealed class McpServerOptions
|
|||
/// Gets or sets information about this server implementation, including its name and version.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This information is sent to the client during initialization to identify the server.
|
||||
/// This information is sent to the client during initialization or discovery to identify the server.
|
||||
/// It's displayed in client logs and can be used for debugging and compatibility checks.
|
||||
/// </remarks>
|
||||
public Implementation? ServerInfo { get; set; }
|
||||
|
|
@ -33,11 +33,23 @@ public sealed class McpServerOptions
|
|||
/// Gets or sets the protocol version supported by this server, using a date-based versioning scheme.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 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".
|
||||
/// If <see langword="null"/>, the server will advertise to the client the version requested
|
||||
/// by the client if that version is known to be supported, and otherwise will advertise the latest
|
||||
/// version supported by the server.
|
||||
/// <para>
|
||||
/// The protocol version defines which features and message formats this server supports. Supported
|
||||
/// values are <c>2024-11-05</c>, <c>2025-03-26</c>, <c>2025-06-18</c>, <c>2025-11-25</c>, and
|
||||
/// <c>2026-07-28</c>.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// If <see langword="null"/>, the server supports all of the versions listed above. For clients using
|
||||
/// the <c>initialize</c> handshake, the server returns the requested initialize-capable version when it
|
||||
/// is supported and otherwise returns <c>2025-11-25</c>. For clients using <c>server/discover</c> and
|
||||
/// per-request metadata, the server advertises the supported per-request metadata versions; currently
|
||||
/// this is <c>2026-07-28</c>.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Set this property to a specific supported value to pin the server to that version. Setting it to
|
||||
/// <c>2026-07-28</c> makes the server reject <c>initialize</c> handshakes; setting it to an earlier
|
||||
/// value makes the server reject <c>2026-07-28</c> per-request metadata.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public string? ProtocolVersion { get; set; }
|
||||
|
||||
|
|
@ -74,12 +86,13 @@ public sealed class McpServerOptions
|
|||
public bool ScopeRequests { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets preexisting knowledge about the client including its name and version to help support
|
||||
/// stateless Streamable HTTP servers that encode this knowledge in the mcp-session-id header.
|
||||
/// Gets or sets preexisting knowledge about the client including its name and version.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// When not specified, this information is sourced from the client's initialize request.
|
||||
/// When not specified, this information is sourced from the client's <c>initialize</c> request or,
|
||||
/// for protocol versions that use per-request metadata, from the current request's <c>_meta</c> field.
|
||||
/// This is typically set during session migration in conjunction with <see cref="KnownClientCapabilities"/>.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public Implementation? KnownClientInfo { get; set; }
|
||||
|
|
@ -90,7 +103,8 @@ public sealed class McpServerOptions
|
|||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// When not specified, this information is sourced from the client's initialize request.
|
||||
/// When not specified, this information is sourced from the client's <c>initialize</c> request or,
|
||||
/// for protocol versions that use per-request metadata, from the current request's <c>_meta</c> field.
|
||||
/// This is typically set during session migration in conjunction with <see cref="KnownClientInfo"/>.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
|
|
|
|||
Loading…
Reference in a new issue