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
3 changed files with 1755 additions and 589 deletions
|
|
@ -294,24 +294,20 @@ internal sealed partial class McpClientImpl : McpClient
|
|||
// handshake. Instead, the client calls server/discover to learn the server's
|
||||
// capabilities and then begins sending normal RPCs that carry protocolVersion /
|
||||
// clientInfo / clientCapabilities in their per-request _meta. A null ProtocolVersion
|
||||
// prefers the 2026-07-28 revision and automatically falls back to the legacy initialize
|
||||
// handshake when the server doesn't support it. The legacy branch below runs only when
|
||||
// prefers the 2026-07-28 revision and automatically falls back to the initialize
|
||||
// handshake when the server doesn't support it. The initialize branch below runs only when
|
||||
// the caller explicitly pins a version that still supports Streamable HTTP sessions (opting out of the default).
|
||||
if (_options.ProtocolVersion is null || McpHttpHeaders.IsJuly2026OrLaterProtocolVersion(_options.ProtocolVersion))
|
||||
if (_options.ProtocolVersion is null || McpProtocolVersions.RequiresPerRequestMetadata(_options.ProtocolVersion))
|
||||
{
|
||||
string preferredVersion = _options.ProtocolVersion ?? McpHttpHeaders.July2026ProtocolVersion;
|
||||
|
||||
// Eagerly set the negotiated version so InjectRequestMetaIfNeeded recognizes us as being
|
||||
// on the 2026-07-28 revision when SendRequestAsync is invoked for server/discover.
|
||||
_negotiatedProtocolVersion = preferredVersion;
|
||||
_sessionHandler.NegotiatedProtocolVersion = preferredVersion;
|
||||
string preferredVersion = _options.ProtocolVersion ?? McpProtocolVersions.July2026ProtocolVersion;
|
||||
|
||||
DiscoverResult? discoverResult = null;
|
||||
bool fallbackToLegacy = false;
|
||||
bool fallbackToInitialize = false;
|
||||
IList<string>? serverSupportedVersions = null;
|
||||
string discoverVersion = preferredVersion;
|
||||
|
||||
// Apply a probe timeout so dual-era clients don't block forever waiting for a
|
||||
// legacy server that silently drops unknown methods (per stdio.mdx fallback rules).
|
||||
// Apply a probe timeout so dual-path clients don't block forever waiting for an
|
||||
// initialize-handshake server that silently drops unknown methods (per stdio.mdx fallback rules).
|
||||
// The probe timeout is configurable via McpClientOptions.DiscoverProbeTimeout and is
|
||||
// always bounded by InitializationTimeout (only applied when it is the tighter bound).
|
||||
var probeTimeout = _options.DiscoverProbeTimeout;
|
||||
|
|
@ -323,75 +319,101 @@ internal sealed partial class McpClientImpl : McpClient
|
|||
|
||||
try
|
||||
{
|
||||
discoverResult = await SendRequestAsync(
|
||||
RequestMethods.ServerDiscover,
|
||||
new DiscoverRequestParams(),
|
||||
McpJsonUtilities.JsonContext.Default.DiscoverRequestParams,
|
||||
McpJsonUtilities.JsonContext.Default.DiscoverResult,
|
||||
cancellationToken: probeCts.Token).ConfigureAwait(false);
|
||||
discoverResult = await SendDiscoverAsync(discoverVersion, probeCts.Token).ConfigureAwait(false);
|
||||
}
|
||||
catch (UnsupportedProtocolVersionException ex)
|
||||
{
|
||||
// Spec-recognized modern-server signal: -32022 with data.supported[]. The server is
|
||||
// modern but doesn't speak our preferred version. Retry with a mutually supported
|
||||
// version from data.supported[] instead of falling back to legacy initialize.
|
||||
fallbackToLegacy = true;
|
||||
// Spec-recognized SEP-2575 signal: -32022 with data.supported[]. The server is
|
||||
// refusing our preferred version. Retry with a supported per-request metadata
|
||||
// version if one exists; otherwise fall back to initialize with the highest
|
||||
// mutually supported initialize-capable version.
|
||||
serverSupportedVersions = (IList<string>)ex.Supported;
|
||||
var retryVersion = serverSupportedVersions
|
||||
.Where(McpProtocolVersions.PerRequestMetadataProtocolVersions.Contains)
|
||||
.OrderByDescending(v => v, StringComparer.Ordinal)
|
||||
.FirstOrDefault();
|
||||
|
||||
if (retryVersion is not null)
|
||||
{
|
||||
if (_options.ProtocolVersion is { } pinnedVersion &&
|
||||
StringComparer.Ordinal.Compare(retryVersion, pinnedVersion) < 0)
|
||||
{
|
||||
throw new McpException(
|
||||
$"The server does not support the requested protocol version '{pinnedVersion}'. " +
|
||||
"Leave McpClientOptions.ProtocolVersion unset to allow automatic fallback to an older version. " +
|
||||
$"Server-supported versions: {string.Join(", ", serverSupportedVersions)}.");
|
||||
}
|
||||
|
||||
discoverVersion = retryVersion;
|
||||
discoverResult = await SendDiscoverAsync(discoverVersion, probeCts.Token).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
fallbackToInitialize = true;
|
||||
}
|
||||
}
|
||||
catch (MissingRequiredClientCapabilityException)
|
||||
{
|
||||
// Spec-recognized modern-server signal: -32021. The server is modern but rejected
|
||||
// Spec-recognized SEP-2575 signal: -32021. The server rejected
|
||||
// our capability set. Surface as-is (no fallback): the user must add capabilities.
|
||||
throw;
|
||||
}
|
||||
catch (McpProtocolException ex) when (ex.ErrorCode == McpErrorCode.HeaderMismatch)
|
||||
{
|
||||
// Spec-recognized modern-server signal: -32020. The server is modern but rejected
|
||||
// Spec-recognized SEP-2575 signal: -32020. The server rejected
|
||||
// our request envelope (e.g., the MCP-Protocol-Version HTTP header didn't match
|
||||
// the body _meta.io.modelcontextprotocol/protocolVersion). Surface as-is (no
|
||||
// fallback): falling back to legacy initialize wouldn't fix a malformed envelope.
|
||||
// fallback): falling back to initialize wouldn't fix a malformed envelope.
|
||||
throw;
|
||||
}
|
||||
catch (McpProtocolException ex) when (
|
||||
ex.ErrorCode == McpErrorCode.InvalidRequest &&
|
||||
ex.Message.Contains(McpHttpHeaders.SessionId, StringComparison.Ordinal))
|
||||
{
|
||||
// Local transport validation: a 2026-07-28+ response must not carry HTTP session state.
|
||||
// This is not evidence of an initialize-handshake server, so do not fall back.
|
||||
throw;
|
||||
}
|
||||
catch (McpProtocolException)
|
||||
{
|
||||
// Per spec PR #2844, the fallback MUST NOT be keyed to a single error code.
|
||||
// Any non-modern JSON-RPC error from the probe indicates a legacy server.
|
||||
// Any non-SEP-2575 JSON-RPC error from the probe indicates an initialize-handshake server.
|
||||
// Common causes include MethodNotFound from a server that has no
|
||||
// server/discover handler, InvalidParams from a server confused by the
|
||||
// SEP-2575 _meta envelope, ParseError from a server that can't handle our
|
||||
// payload shape, or any other transport-defined error. The three modern-server
|
||||
// payload shape, or any other transport-defined error. The three SEP-2575
|
||||
// signals (-32022 UnsupportedProtocolVersion, -32021
|
||||
// MissingRequiredClientCapability, -32020 HeaderMismatch) are caught above and
|
||||
// never reach here.
|
||||
fallbackToLegacy = true;
|
||||
fallbackToInitialize = true;
|
||||
}
|
||||
catch (OperationCanceledException) when (probeCts.IsCancellationRequested && !initializationCts.IsCancellationRequested)
|
||||
{
|
||||
// Probe timeout elapsed without a response. Per stdio.mdx fallback rules, no
|
||||
// response within a reasonable timeout means the server is legacy. Fall back.
|
||||
fallbackToLegacy = true;
|
||||
// response within a reasonable timeout means the server requires initialize. Fall back.
|
||||
fallbackToInitialize = true;
|
||||
}
|
||||
|
||||
if (discoverResult is not null && !discoverResult.SupportedVersions.Contains(preferredVersion))
|
||||
if (discoverResult is not null && !discoverResult.SupportedVersions.Contains(discoverVersion))
|
||||
{
|
||||
// Server is reachable and supports server/discover, but doesn't support the
|
||||
// 2026-07-28 version. Fall back to legacy initialize with the highest
|
||||
// mutually-supported version from supportedVersions[].
|
||||
fallbackToLegacy = true;
|
||||
// version we are using. Fall back to initialize with the highest
|
||||
// mutually-supported initialize-capable version from supportedVersions[].
|
||||
fallbackToInitialize = true;
|
||||
serverSupportedVersions = discoverResult.SupportedVersions;
|
||||
}
|
||||
|
||||
if (fallbackToLegacy)
|
||||
if (fallbackToInitialize)
|
||||
{
|
||||
// Reset negotiated state and try legacy initialize.
|
||||
// Reset negotiated state and try initialize.
|
||||
_negotiatedProtocolVersion = null;
|
||||
_sessionHandler.NegotiatedProtocolVersion = null;
|
||||
|
||||
string fallbackVersion = serverSupportedVersions?
|
||||
.Where(McpSessionHandler.SupportedProtocolVersions.Contains)
|
||||
.Where(McpProtocolVersions.InitializeHandshakeProtocolVersions.Contains)
|
||||
.OrderByDescending(v => v, StringComparer.Ordinal)
|
||||
.FirstOrDefault()
|
||||
?? McpHttpHeaders.November2025ProtocolVersion;
|
||||
?? McpProtocolVersions.November2025ProtocolVersion;
|
||||
|
||||
// A non-null ProtocolVersion is also the minimum: refuse to fall back below the
|
||||
// explicitly requested version. String.Compare is the spec's prescribed ordering
|
||||
|
|
@ -403,11 +425,11 @@ internal sealed partial class McpClientImpl : McpClient
|
|||
$"The server does not support the requested protocol version '{pinnedVersion}'. " +
|
||||
"Leave McpClientOptions.ProtocolVersion unset to allow automatic fallback to an older version. " +
|
||||
(serverSupportedVersions is null
|
||||
? "The server appears to be a legacy server that requires the deprecated initialize handshake."
|
||||
? "The server appears to require the initialize handshake."
|
||||
: $"Server-supported versions: {string.Join(", ", serverSupportedVersions)}."));
|
||||
}
|
||||
|
||||
await PerformLegacyInitializeAsync(fallbackVersion, initializationCts.Token).ConfigureAwait(false);
|
||||
await PerformInitializeHandshakeAsync(fallbackVersion, initializationCts.Token).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -422,14 +444,29 @@ internal sealed partial class McpClientImpl : McpClient
|
|||
_serverInfo = discoverResult.ServerInfo;
|
||||
_serverInstructions = discoverResult.Instructions;
|
||||
}
|
||||
|
||||
async Task<DiscoverResult> SendDiscoverAsync(string protocolVersion, CancellationToken cancellationToken)
|
||||
{
|
||||
// Eagerly set the negotiated version so InjectRequestMetaIfNeeded recognizes us as being
|
||||
// on a per-request metadata revision when SendRequestAsync is invoked for server/discover.
|
||||
_negotiatedProtocolVersion = protocolVersion;
|
||||
_sessionHandler.NegotiatedProtocolVersion = protocolVersion;
|
||||
|
||||
return await SendRequestAsync(
|
||||
RequestMethods.ServerDiscover,
|
||||
new DiscoverRequestParams(),
|
||||
McpJsonUtilities.JsonContext.Default.DiscoverRequestParams,
|
||||
McpJsonUtilities.JsonContext.Default.DiscoverResult,
|
||||
cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Legacy initialize handshake. Reached only when the caller explicitly pinned a
|
||||
// initialize handshake. Reached only when the caller explicitly pinned a
|
||||
// ProtocolVersion that still supports Streamable HTTP sessions (opting out of the default), so
|
||||
// _options.ProtocolVersion is non-null here.
|
||||
string requestProtocol = _options.ProtocolVersion ?? McpHttpHeaders.November2025ProtocolVersion;
|
||||
await PerformLegacyInitializeAsync(requestProtocol, initializationCts.Token).ConfigureAwait(false);
|
||||
string requestProtocol = _options.ProtocolVersion ?? McpProtocolVersions.November2025ProtocolVersion;
|
||||
await PerformInitializeHandshakeAsync(requestProtocol, initializationCts.Token).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException oce) when (initializationCts.IsCancellationRequested && !cancellationToken.IsCancellationRequested)
|
||||
|
|
@ -449,10 +486,10 @@ internal sealed partial class McpClientImpl : McpClient
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs the legacy initialize handshake (initialize request + initialized notification),
|
||||
/// Performs the initialize handshake (initialize request + initialized notification),
|
||||
/// records the negotiated protocol version, and stores the server capabilities/info/instructions.
|
||||
/// </summary>
|
||||
private async Task PerformLegacyInitializeAsync(string requestProtocol, CancellationToken cancellationToken)
|
||||
private async Task PerformInitializeHandshakeAsync(string requestProtocol, CancellationToken cancellationToken)
|
||||
{
|
||||
var initializeResponse = await SendRequestAsync(
|
||||
RequestMethods.Initialize,
|
||||
|
|
@ -479,18 +516,16 @@ internal sealed partial class McpClientImpl : McpClient
|
|||
_serverInstructions = initializeResponse.Instructions;
|
||||
|
||||
// When the user explicitly pinned a version that supports Streamable HTTP sessions, the server MUST respect it.
|
||||
// When the user pinned the 2026-07-28 version but we fell back (e.g., legacy server rejected
|
||||
// server/discover), or when no version was pinned, accept any supported response. This is the
|
||||
// spec-mandated behavior: a 2026-07-28 client must be able to downgrade to whatever
|
||||
// version the server advertises.
|
||||
// When no version was pinned, accept any supported initialize-handshake response. initialize cannot negotiate
|
||||
// the 2026-07-28 and later protocol revisions.
|
||||
bool isResponseProtocolValid;
|
||||
if (_options.ProtocolVersion is { } optionsProtocol && !McpHttpHeaders.IsJuly2026OrLaterProtocolVersion(optionsProtocol))
|
||||
if (_options.ProtocolVersion is { } optionsProtocol && !McpProtocolVersions.IsJuly2026OrLaterProtocolVersion(optionsProtocol))
|
||||
{
|
||||
isResponseProtocolValid = optionsProtocol == initializeResponse.ProtocolVersion;
|
||||
}
|
||||
else
|
||||
{
|
||||
isResponseProtocolValid = McpSessionHandler.SupportedProtocolVersions.Contains(initializeResponse.ProtocolVersion);
|
||||
isResponseProtocolValid = McpProtocolVersions.InitializeHandshakeProtocolVersions.Contains(initializeResponse.ProtocolVersion);
|
||||
}
|
||||
if (!isResponseProtocolValid)
|
||||
{
|
||||
|
|
@ -525,7 +560,7 @@ internal sealed partial class McpClientImpl : McpClient
|
|||
_serverInstructions = resumeOptions.ServerInstructions;
|
||||
_negotiatedProtocolVersion = resumeOptions.NegotiatedProtocolVersion
|
||||
?? _options.ProtocolVersion
|
||||
?? McpHttpHeaders.November2025ProtocolVersion;
|
||||
?? McpProtocolVersions.November2025ProtocolVersion;
|
||||
|
||||
// Update session handler with the negotiated protocol version for telemetry
|
||||
_sessionHandler.NegotiatedProtocolVersion = _negotiatedProtocolVersion;
|
||||
|
|
@ -691,7 +726,7 @@ internal sealed partial class McpClientImpl : McpClient
|
|||
/// <summary>
|
||||
/// Injects the 2026-07-28 protocol's per-request <c>_meta</c> fields (protocol version, client info,
|
||||
/// client capabilities) into the request when this client negotiated the 2026-07-28 or later revision
|
||||
/// (SEP-2575). No-op on a legacy session.
|
||||
/// (SEP-2575). No-op on an initialize-handshake session.
|
||||
/// </summary>
|
||||
private void InjectRequestMetaIfNeeded(JsonRpcRequest request)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -52,16 +52,20 @@ public sealed class McpClientOptions
|
|||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// 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>
|
||||
/// When <see langword="null"/> (the default), the client prefers the latest revision (<c>2026-07-28</c>),
|
||||
/// which removed the <c>initialize</c> handshake and Streamable HTTP sessions. It probes with
|
||||
/// <c>server/discover</c> and automatically falls back to the legacy <c>initialize</c> handshake,
|
||||
/// downgrading to any version the server advertises, when the server does not support that revision.
|
||||
/// <c>server/discover</c> and automatically falls back to the <c>initialize</c> handshake,
|
||||
/// downgrading to an initialize-capable version the server advertises, when the server does not support that revision.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// When non-<see langword="null"/>, this value is both the requested version and the minimum the client
|
||||
/// will accept: the client requests exactly this version and refuses to downgrade below it, throwing an
|
||||
/// <see cref="McpException"/> instead of falling back. Setting it to <c>2026-07-28</c> therefore disables
|
||||
/// the automatic legacy-server fallback, and setting it to a version that still supports Streamable HTTP
|
||||
/// the automatic initialize-handshake server fallback, and setting it to a version that still supports Streamable HTTP
|
||||
/// sessions, such as <c>2025-11-25</c>, forces the <c>initialize</c> handshake and fails if the server
|
||||
/// negotiates a different version. To try more than one version, leave this unset for automatic fallback
|
||||
/// or retry the connection with a different value.
|
||||
|
|
@ -90,7 +94,7 @@ public sealed class McpClientOptions
|
|||
|
||||
/// <summary>
|
||||
/// Gets or sets the timeout applied to the <c>server/discover</c> probe that the client issues
|
||||
/// before falling back to the legacy <c>initialize</c> handshake.
|
||||
/// before falling back to the <c>initialize</c> handshake.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The probe timeout. The default value is 5 seconds. Use
|
||||
|
|
@ -102,17 +106,17 @@ public sealed class McpClientOptions
|
|||
/// This timeout only has an effect when the client prefers the <c>2026-07-28</c> protocol revision, that is,
|
||||
/// when <see cref="ProtocolVersion"/> is <see langword="null"/> (the default) or <c>2026-07-28</c>.
|
||||
/// In that mode the client first probes the server with a
|
||||
/// <c>server/discover</c> request. A legacy server that predates the <c>2026-07-28</c> revision may
|
||||
/// <c>server/discover</c> request. A server that predates the <c>2026-07-28</c> revision may
|
||||
/// silently drop the unknown method, so the probe is bounded by this timeout; when it elapses the
|
||||
/// client concludes the server is legacy and falls back to the <c>initialize</c> handshake on the
|
||||
/// same connection. When the caller pins a legacy <see cref="ProtocolVersion"/>, no probe is issued
|
||||
/// client concludes the server requires <c>initialize</c> and falls back to that handshake on the
|
||||
/// same connection. When the caller pins an initialize-capable <see cref="ProtocolVersion"/>, no probe is issued
|
||||
/// and this value has no effect.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The default is intentionally short so that dual-era clients fall back quickly against legacy
|
||||
/// The default is intentionally short so that dual-path clients fall back quickly against initialize-handshake
|
||||
/// servers. Increase it for high-latency environments (for example, cold-start serverless peers or
|
||||
/// satellite links) where a short probe could trigger the legacy fallback before a server on the new revision
|
||||
/// server has had a chance to respond. The probe is always also bounded by
|
||||
/// satellite links) where a short probe could trigger the initialize fallback before a server on the
|
||||
/// per-request metadata revision has had a chance to respond. The probe is always also bounded by
|
||||
/// <see cref="InitializationTimeout"/>, which governs the overall connect budget: if this value is
|
||||
/// greater than or equal to <see cref="InitializationTimeout"/>, the probe is effectively bounded by
|
||||
/// <see cref="InitializationTimeout"/> alone.
|
||||
|
|
|
|||
|
|
@ -67,19 +67,19 @@ internal sealed partial class StreamableHttpClientSessionTransport : TransportBa
|
|||
// Per spec PR #2844 (HTTP backwards compatibility), a 400 Bad Request that carries a
|
||||
// JSON-RPC error envelope means the peer is signalling something application-level about
|
||||
// our request. Surface ANY JSON-RPC error on a 400 as McpProtocolException so the
|
||||
// connect-time logic can react. For example, the three modern protocol error codes
|
||||
// connect-time logic can react. For example, the three per-request metadata protocol error codes
|
||||
// (-32022 UnsupportedProtocolVersion, -32021 MissingRequiredClientCapability,
|
||||
// -32020 HeaderMismatch) lead to typed exceptions, while other codes (e.g. -32600 from
|
||||
// legacy servers that don't understand the SEP-2575 _meta envelope) become generic
|
||||
// McpProtocolException instances and trigger the fallback-to-legacy-initialize path.
|
||||
// initialize-handshake servers that don't understand the SEP-2575 _meta envelope) become generic
|
||||
// McpProtocolException instances and trigger the initialize-handshake fallback path.
|
||||
// Other status codes (401 auth, 403 forbidden, 404 session-not-found, 5xx server) continue
|
||||
// to surface as HttpRequestException to preserve back-compat with transport-layer behaviors.
|
||||
// The three modern protocol error codes are also surfaced for non-400 status codes
|
||||
// The three per-request metadata protocol error codes are also surfaced for non-400 status codes
|
||||
// for robustness. Servers occasionally emit them with 4xx codes other than 400.
|
||||
if (!response.IsSuccessStatusCode &&
|
||||
await TryReadJsonRpcErrorAsync(response, cancellationToken).ConfigureAwait(false) is { } parsedError &&
|
||||
(response.StatusCode == HttpStatusCode.BadRequest ||
|
||||
IsModernProtocolErrorCode((McpErrorCode)parsedError.Error.Code)))
|
||||
IsPerRequestMetadataProtocolErrorCode((McpErrorCode)parsedError.Error.Code)))
|
||||
{
|
||||
throw McpSessionHandler.CreateRemoteProtocolExceptionFromError(parsedError);
|
||||
}
|
||||
|
|
@ -87,7 +87,7 @@ internal sealed partial class StreamableHttpClientSessionTransport : TransportBa
|
|||
await response.EnsureSuccessStatusCodeWithResponseBodyAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static bool IsModernProtocolErrorCode(McpErrorCode code) =>
|
||||
private static bool IsPerRequestMetadataProtocolErrorCode(McpErrorCode code) =>
|
||||
code is McpErrorCode.UnsupportedProtocolVersion
|
||||
or McpErrorCode.MissingRequiredClientCapability
|
||||
or McpErrorCode.HeaderMismatch;
|
||||
|
|
@ -217,7 +217,7 @@ internal sealed partial class StreamableHttpClientSessionTransport : TransportBa
|
|||
if (rpcRequest.Method == RequestMethods.Initialize && rpcResponseOrError is JsonRpcResponse initResponse)
|
||||
{
|
||||
// We've successfully initialized! Copy session-id and protocol version, then start GET request if any.
|
||||
if (response.Headers.TryGetValues("Mcp-Session-Id", out var sessionIdValues))
|
||||
if (response.Headers.TryGetValues(McpHttpHeaders.SessionId, out var sessionIdValues))
|
||||
{
|
||||
SessionId = sessionIdValues.FirstOrDefault();
|
||||
}
|
||||
|
|
@ -528,7 +528,7 @@ internal sealed partial class StreamableHttpClientSessionTransport : TransportBa
|
|||
string? protocolVersion,
|
||||
string? lastEventId = null)
|
||||
{
|
||||
if (sessionId is not null)
|
||||
if (sessionId is not null && McpProtocolVersions.SupportsHttpSessions(protocolVersion))
|
||||
{
|
||||
headers.Add(McpHttpHeaders.SessionId, sessionId);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue