Gate 2026-07-28 result fields on negotiated protocol version (#1721) (#1753)

Co-authored-by: Tarek Mahmoud Sayed <tarekms@ntdev.microsoft.com>
Copilot-Session: 80653343-5dcb-43cb-89b0-8ac8e572c4f7
This commit is contained in:
Tarek Mahmoud Sayed 2026-07-27 12:57:50 -07:00 • committed by GitHub
commit 6787c0cc19
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -609,7 +609,10 @@ internal sealed partial class McpServerImpl : McpServer
Instructions = options.ServerInstructions,
ServerInfo = options.ServerInfo ?? DefaultImplementation,
Capabilities = ServerCapabilities ?? new(),
ResultType = "complete",
// resultType is a 2026-07-28 result field. The initialize handshake is only available on
// 2025-11-25 and earlier revisions (2026-07-28+ negotiate via server/discover and throw
// above), so InitializeResult must never carry resultType (issue #1721).
};
},
McpJsonUtilities.JsonContext.Default.InitializeRequestParams,
@ -1635,8 +1638,12 @@ internal sealed partial class McpServerImpl : McpServer
return InvokeHandlerAsync(setLoggingLevelHandler, request!, jsonRpcRequest, cancellationToken);
}
// Otherwise, consider it handled.
return new ValueTask<EmptyResult>(EmptyResult.Instance);
// Otherwise, consider it handled. logging/setLevel is a legacy (<= 2025-11-25) method
// (2026-07-28+ is rejected above), so the response must not carry the 2026-07-28 resultType
// field. Return a fresh EmptyResult rather than the shared EmptyResult.Instance, which is
// pre-stamped with resultType="complete" for the 2026-07-28-only subscriptions/listen path
// (issue #1721).
return new ValueTask<EmptyResult>(new EmptyResult());
},
McpJsonUtilities.JsonContext.Default.SetLevelRequestParams,
McpJsonUtilities.JsonContext.Default.EmptyResult);
@ -1707,7 +1714,11 @@ internal sealed partial class McpServerImpl : McpServer
handler = async (request, cancellationToken) =>
{
var result = await innerHandler(request, cancellationToken).ConfigureAwait(false);
if (result is ICacheableResult cacheable)
// ttlMs and cacheScope are 2026-07-28 result fields; only stamp them when the request
// was negotiated under that revision or later. Earlier revisions (e.g. 2025-11-25) reject
// these as unrecognized keys (issue #1721).
if (result is ICacheableResult cacheable && IsJuly2026OrLaterProtocolRequest(request.JsonRpcRequest))
{
cacheable.TimeToLive ??= TimeSpan.Zero;
cacheable.CacheScope ??= CacheScope.Private;
@ -1723,7 +1734,12 @@ internal sealed partial class McpServerImpl : McpServer
handler = async (request, cancellationToken) =>
{
var result = await innerHandler(request, cancellationToken).ConfigureAwait(false);
if (result is Result protocolResult && protocolResult.ResultType is null)
// resultType is a 2026-07-28 result field; only stamp it when the request was negotiated
// under that revision or later. Earlier revisions (e.g. 2025-11-25) reject it as an
// unrecognized key (issue #1721).
if (result is Result protocolResult && protocolResult.ResultType is null &&
IsJuly2026OrLaterProtocolRequest(request.JsonRpcRequest))
{
protocolResult.ResultType = "complete";
}
@ -1750,7 +1766,12 @@ internal sealed partial class McpServerImpl : McpServer
handler = async (request, cancellationToken) =>
{
var result = await innerHandler(request, cancellationToken).ConfigureAwait(false);
if (!result.IsAlternate && result.Result is { ResultType: null } immediateResult)
// resultType is a 2026-07-28 result field; only stamp it when the request was negotiated
// under that revision or later. Earlier revisions (e.g. 2025-11-25) reject it as an
// unrecognized key (issue #1721).
if (!result.IsAlternate && result.Result is { ResultType: null } immediateResult &&
IsJuly2026OrLaterProtocolRequest(request.JsonRpcRequest))
{
immediateResult.ResultType = "complete";
}