Skip to content

Commit 933800b

Browse files
halter73Copilot
andcommitted
Make draft and HTTP sessions mutually exclusive; gate Tasks to draft
Over HTTP the draft revision is unconditionally sessionless, so the server now enforces that the draft protocol and an Mcp-Session-Id can never coexist: - Reject any request that carries the draft MCP-Protocol-Version together with an Mcp-Session-Id (POST/GET/DELETE). - When Stateless = false, refuse a sessionless draft request with -32004 UnsupportedProtocolVersion so a dual-era client downgrades to the legacy initialize handshake and obtains the session the author opted into. Stateless = true (the default) still serves sessionless draft natively. - Gate the SEP-2663 Tasks extension to the draft revision on the server: tasks/* handlers reject non-draft requests with MethodNotFound, and task augmentation is only applied for draft clients that opt in. This lets the per-request capability sync overwrite rather than merge a partial legacy envelope. - Mark the stateful-only HttpServerTransportOptions knobs obsolete (MCP9006) now that the default is stateless, and remove the dead fan-out / forceStateless plumbing left over from the stateful draft path. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 91f3c36 commit 933800b

13 files changed

Lines changed: 370 additions & 156 deletions

src/ModelContextProtocol.AspNetCore/DistributedCacheEventStreamStoreOptionsSetup.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
namespace ModelContextProtocol.AspNetCore;
66

7-
#pragma warning disable MCP9005 // This type only exists to configure the obsolete legacy resumability store.
7+
#pragma warning disable MCP9006 // This type only exists to configure the obsolete legacy resumability store.
88

99
/// <summary>
1010
/// Configures <see cref="DistributedCacheEventStreamStoreOptions"/> by resolving

src/ModelContextProtocol.AspNetCore/DistributedCacheEventStreamStoreOptionsValidator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
namespace ModelContextProtocol.AspNetCore;
77

8-
#pragma warning disable MCP9005 // This type only exists to validate the obsolete legacy resumability store options.
8+
#pragma warning disable MCP9006 // This type only exists to validate the obsolete legacy resumability store options.
99

1010
/// <summary>
1111
/// Validates that <see cref="DistributedCacheEventStreamStoreOptions.Cache"/> is set.

src/ModelContextProtocol.AspNetCore/HttpMcpServerBuilderExtensions.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ public static IMcpServerBuilder AddAuthorizationFilters(this IMcpServerBuilder b
8585
/// </para>
8686
/// </remarks>
8787
[Obsolete(ModelContextProtocol.Obsoletions.LegacyStatefulHttp_Message, DiagnosticId = ModelContextProtocol.Obsoletions.LegacyStatefulHttp_DiagnosticId, UrlFormat = ModelContextProtocol.Obsoletions.LegacyStatefulHttp_Url)]
88-
#pragma warning disable MCP9005 // The method is itself obsolete and intentionally wires up the legacy resumability store.
88+
#pragma warning disable MCP9006 // The method is itself obsolete and intentionally wires up the legacy resumability store.
8989
public static IMcpServerBuilder WithDistributedCacheEventStreamStore(this IMcpServerBuilder builder, Action<DistributedCacheEventStreamStoreOptions>? configureOptions = null)
9090
{
9191
ArgumentNullException.ThrowIfNull(builder);
@@ -101,5 +101,5 @@ public static IMcpServerBuilder WithDistributedCacheEventStreamStore(this IMcpSe
101101

102102
return builder;
103103
}
104-
#pragma warning restore MCP9005
104+
#pragma warning restore MCP9006
105105
}

src/ModelContextProtocol.AspNetCore/HttpServerTransportOptions.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,12 @@ public class HttpServerTransportOptions
6161
/// might arrive at another ASP.NET Core application process.
6262
/// Client sampling, elicitation, and roots capabilities are also disabled in stateless mode, because the server cannot make requests.
6363
/// <para>
64-
/// Requests that declare the <c>2026-07-28</c> draft protocol revision via the <c>MCP-Protocol-Version</c> header
65-
/// are always routed through the stateless path regardless of this property's value, because that revision
66-
/// removes <c>Mcp-Session-Id</c> entirely (SEP-2567).
64+
/// The <c>2026-07-28</c> draft protocol revision is sessionless and removes <c>Mcp-Session-Id</c> entirely
65+
/// (SEP-2567), so over HTTP draft requests are only ever served when <see langword="true"/>. When this
66+
/// property is <see langword="false"/>, a sessionless draft request is refused with a
67+
/// <c>-32004 UnsupportedProtocolVersion</c> error so that a dual-era client downgrades to the legacy
68+
/// <c>initialize</c> handshake and obtains the session that the server was configured to provide. A draft
69+
/// request that carries an <c>Mcp-Session-Id</c> is always rejected, regardless of this property's value.
6770
/// </para>
6871
/// </remarks>
6972
public bool Stateless { get; set; } = true;

src/ModelContextProtocol.AspNetCore/HttpServerTransportOptionsSetup.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ internal sealed class HttpServerTransportOptionsSetup(IServiceProvider servicePr
1212
{
1313
public void Configure(HttpServerTransportOptions options)
1414
{
15-
#pragma warning disable MCP9005 // Stateful Streamable HTTP options are obsolete but still wired up internally.
15+
#pragma warning disable MCP9006 // Stateful Streamable HTTP options are obsolete but still wired up internally.
1616
options.EventStreamStore ??= serviceProvider.GetService<ISseEventStreamStore>();
1717
options.SessionMigrationHandler ??= serviceProvider.GetService<ISessionMigrationHandler>();
18-
#pragma warning restore MCP9005
18+
#pragma warning restore MCP9006
1919
}
2020
}

src/ModelContextProtocol.AspNetCore/IdleTrackingBackgroundService.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using Microsoft.Extensions.Hosting;
1+
using Microsoft.Extensions.Hosting;
22
using Microsoft.Extensions.Logging;
33
using Microsoft.Extensions.Options;
44

@@ -18,14 +18,14 @@ public IdleTrackingBackgroundService(
1818
ILogger<IdleTrackingBackgroundService> logger)
1919
{
2020
// Still run loop given infinite IdleTimeout to enforce the MaxIdleSessionCount and assist graceful shutdown.
21-
#pragma warning disable MCP9005 // Stateful Streamable HTTP options are obsolete but still wired up internally.
21+
#pragma warning disable MCP9006 // Stateful Streamable HTTP options are obsolete but still wired up internally.
2222
if (options.Value.IdleTimeout != Timeout.InfiniteTimeSpan)
2323
{
2424
ArgumentOutOfRangeException.ThrowIfLessThan(options.Value.IdleTimeout, TimeSpan.Zero);
2525
}
2626

2727
ArgumentOutOfRangeException.ThrowIfLessThan(options.Value.MaxIdleSessionCount, 0);
28-
#pragma warning restore MCP9005
28+
#pragma warning restore MCP9006
2929

3030
_sessions = sessions;
3131
_options = options;

src/ModelContextProtocol.AspNetCore/SseEventStreamReaderExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
namespace ModelContextProtocol.AspNetCore;
99

10-
#pragma warning disable MCP9005 // These extensions only operate on the obsolete legacy resumability reader.
10+
#pragma warning disable MCP9006 // These extensions only operate on the obsolete legacy resumability reader.
1111

1212
/// <summary>
1313
/// Provides extension methods for <see cref="ISseEventStreamReader"/>.

src/ModelContextProtocol.AspNetCore/StatefulSessionManager.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System.Collections.Concurrent;
1+
using System.Collections.Concurrent;
22
using System.Diagnostics;
33
using System.Diagnostics.CodeAnalysis;
44
using System.Runtime.InteropServices;
@@ -17,11 +17,11 @@ internal sealed partial class StatefulSessionManager(
1717
private readonly ConcurrentDictionary<string, StreamableHttpSession> _sessions = new(StringComparer.Ordinal);
1818

1919
private readonly TimeProvider _timeProvider = httpServerTransportOptions.Value.TimeProvider;
20-
#pragma warning disable MCP9005 // Stateful Streamable HTTP options are obsolete but still wired up internally.
20+
#pragma warning disable MCP9006 // Stateful Streamable HTTP options are obsolete but still wired up internally.
2121
private readonly TimeSpan _idleTimeout = httpServerTransportOptions.Value.IdleTimeout;
2222
private readonly long _idleTimeoutTicks = GetIdleTimeoutInTimestampTicks(httpServerTransportOptions.Value.IdleTimeout, httpServerTransportOptions.Value.TimeProvider);
2323
private readonly int _maxIdleSessionCount = httpServerTransportOptions.Value.MaxIdleSessionCount;
24-
#pragma warning restore MCP9005
24+
#pragma warning restore MCP9006
2525

2626
private readonly object _idlePruningLock = new();
2727
private readonly List<long> _idleTimestamps = [];

0 commit comments

Comments
 (0)