Skip to content

Commit 8498b79

Browse files
Copilotjeffhandley
andcommitted
Reuse MCPEXP002 for RunSessionHandler (remove MCPEXP003)
Co-authored-by: jeffhandley <1031940+jeffhandley@users.noreply.github.com>
1 parent 8da9ccd commit 8498b79

File tree

10 files changed

+52
-29
lines changed

10 files changed

+52
-29
lines changed

docs/list-of-diagnostics.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,8 @@ If you use experimental APIs, you will get one of the diagnostics shown below. T
2323

2424
| Diagnostic ID | Description |
2525
| :------------ | :---------- |
26-
| `MCPEXP001` | MCP experimental APIs including Tasks and Extensions. Tasks provide a mechanism for asynchronous long-running operations that can be polled for status and results (see [MCP Tasks specification](https://modelcontextprotocol.io/specification/draft/basic/utilities/tasks)). Extensions provide a framework for extending the Model Context Protocol while maintaining interoperability (see [SEP-2133](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2133)). |
27-
| `MCPEXP002` | Subclassing `McpClient` and `McpServer` is experimental and subject to change (see [#1363](https://github.com/modelcontextprotocol/csharp-sdk/pull/1363)). |
28-
| `MCPEXP003` | `RunSessionHandler` is experimental and may be removed or change signatures in a future release. Consider using `ConfigureSessionOptions` instead, which provides access to the `HttpContext` of the initializing request with fewer known issues. |
26+
| `MCPEXP001` | Experimental APIs for features in the MCP specification itself, including Tasks and Extensions. Tasks provide a mechanism for asynchronous long-running operations that can be polled for status and results (see [MCP Tasks specification](https://modelcontextprotocol.io/specification/draft/basic/utilities/tasks)). Extensions provide a framework for extending the Model Context Protocol while maintaining interoperability (see [SEP-2133](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2133)). |
27+
| `MCPEXP002` | Experimental SDK APIs unrelated to the MCP specification itself, including subclassing `McpClient`/`McpServer` (see [#1363](https://github.com/modelcontextprotocol/csharp-sdk/pull/1363)) and `RunSessionHandler`, which may be removed or change signatures in a future release (consider using `ConfigureSessionOptions` instead). |
2928

3029
## Obsolete APIs
3130

samples/EverythingServer/Program.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
.WithHttpTransport(options =>
5252
{
5353
// Add a RunSessionHandler to remove all subscriptions for the session when it ends
54-
#pragma warning disable MCPEXP003 // RunSessionHandler is experimental
54+
#pragma warning disable MCPEXP002 // RunSessionHandler is experimental
5555
options.RunSessionHandler = async (httpContext, mcpServer, token) =>
5656
{
5757
if (mcpServer.SessionId == null)
@@ -77,7 +77,7 @@
7777
subscriptions.TryRemove(mcpServer.SessionId, out _);
7878
}
7979
};
80-
#pragma warning restore MCPEXP003
80+
#pragma warning restore MCPEXP002
8181
})
8282
.WithTools<AddTool>()
8383
.WithTools<AnnotatedMessageTool>()

src/Common/Experimentals.cs

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,24 @@ namespace ModelContextProtocol;
66
/// Defines diagnostic IDs, messages, and URLs for APIs annotated with <see cref="ExperimentalAttribute"/>.
77
/// </summary>
88
/// <remarks>
9+
/// Experimental diagnostic IDs are grouped by category:
10+
/// <list type="bullet">
11+
/// <item><description>
12+
/// <c>MCPEXP001</c> covers APIs related to experimental features in the MCP specification itself,
13+
/// such as Tasks and Extensions. These APIs may change as the specification evolves.
14+
/// </description></item>
15+
/// <item><description>
16+
/// <c>MCPEXP002</c> covers experimental SDK APIs that are unrelated to the MCP specification,
17+
/// such as subclassing internal types or SDK-specific extensibility hooks. These APIs may
18+
/// change or be removed based on SDK design feedback.
19+
/// </description></item>
20+
/// </list>
21+
/// <para>
922
/// When an experimental API is associated with an experimental specification, the message
1023
/// should refer to the specification version that introduces the feature and the SEP
1124
/// when available. If there is a SEP associated with the experimental API, the Url should
1225
/// point to the SEP issue.
26+
/// </para>
1327
/// <para>
1428
/// Experimental diagnostic IDs are in the format MCPEXP###.
1529
/// </para>
@@ -58,8 +72,14 @@ internal static class Experimentals
5872
public const string Extensions_Url = "https://github.com/modelcontextprotocol/csharp-sdk/blob/main/docs/list-of-diagnostics.md#mcpexp001";
5973

6074
/// <summary>
61-
/// Diagnostic ID for experimental subclassing of McpClient and McpServer.
75+
/// Diagnostic ID for experimental SDK APIs unrelated to the MCP specification,
76+
/// such as subclassing <c>McpClient</c>/<c>McpServer</c> and <c>RunSessionHandler</c>.
6277
/// </summary>
78+
/// <remarks>
79+
/// This diagnostic ID covers experimental SDK-level extensibility APIs. All constants
80+
/// in this group share the same diagnostic ID so users need only one suppression point
81+
/// for SDK design preview features.
82+
/// </remarks>
6383
public const string Subclassing_DiagnosticId = "MCPEXP002";
6484

6585
/// <summary>
@@ -70,12 +90,16 @@ internal static class Experimentals
7090
/// <summary>
7191
/// URL for experimental subclassing of McpClient and McpServer.
7292
/// </summary>
73-
public const string Subclassing_Url = "https://github.com/modelcontextprotocol/csharp-sdk/pull/1363";
93+
public const string Subclassing_Url = "https://github.com/modelcontextprotocol/csharp-sdk/blob/main/docs/list-of-diagnostics.md#mcpexp002";
7494

7595
/// <summary>
7696
/// Diagnostic ID for the experimental <c>RunSessionHandler</c> API.
7797
/// </summary>
78-
public const string RunSessionHandler_DiagnosticId = "MCPEXP003";
98+
/// <remarks>
99+
/// This uses the same diagnostic ID as <see cref="Subclassing_DiagnosticId"/> because
100+
/// both are experimental SDK APIs unrelated to the MCP specification.
101+
/// </remarks>
102+
public const string RunSessionHandler_DiagnosticId = "MCPEXP002";
79103

80104
/// <summary>
81105
/// Message for the experimental <c>RunSessionHandler</c> API.
@@ -85,5 +109,5 @@ internal static class Experimentals
85109
/// <summary>
86110
/// URL for the experimental <c>RunSessionHandler</c> API.
87111
/// </summary>
88-
public const string RunSessionHandler_Url = "https://github.com/modelcontextprotocol/csharp-sdk/blob/main/docs/list-of-diagnostics.md#mcpexp003";
112+
public const string RunSessionHandler_Url = "https://github.com/modelcontextprotocol/csharp-sdk/blob/main/docs/list-of-diagnostics.md#mcpexp002";
89113
}

src/ModelContextProtocol.AspNetCore/SseHandler.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,9 @@ public async Task HandleSseRequestAsync(HttpContext context)
5656
await using var mcpServer = McpServer.Create(transport, mcpServerOptions, loggerFactory, context.RequestServices);
5757
context.Features.Set(mcpServer);
5858

59-
#pragma warning disable MCPEXP003 // RunSessionHandler is experimental
59+
#pragma warning disable MCPEXP002 // RunSessionHandler is experimental
6060
var runSessionAsync = httpMcpServerOptions.Value.RunSessionHandler ?? StreamableHttpHandler.RunSessionAsync;
61-
#pragma warning restore MCPEXP003
61+
#pragma warning restore MCPEXP002
6262
await runSessionAsync(context, mcpServer, cancellationToken);
6363
}
6464
finally

src/ModelContextProtocol.AspNetCore/StreamableHttpHandler.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -390,9 +390,9 @@ private async ValueTask<StreamableHttpSession> CreateSessionAsync(
390390
var userIdClaim = GetUserIdClaim(context.User);
391391
var session = new StreamableHttpSession(sessionId, transport, server, userIdClaim, sessionManager);
392392

393-
#pragma warning disable MCPEXP003 // RunSessionHandler is experimental
393+
#pragma warning disable MCPEXP002 // RunSessionHandler is experimental
394394
var runSessionAsync = HttpServerTransportOptions.RunSessionHandler ?? RunSessionAsync;
395-
#pragma warning restore MCPEXP003
395+
#pragma warning restore MCPEXP002
396396
session.ServerRunTask = runSessionAsync(context, server, session.SessionClosed);
397397

398398
return session;

tests/ModelContextProtocol.AspNetCore.Tests/MapMcpStreamableHttpTests.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -213,14 +213,14 @@ public async Task CanResumeSessionWithMapMcpAndRunSessionHandler()
213213
}).WithHttpTransport(opts =>
214214
{
215215
ConfigureStateless(opts);
216-
#pragma warning disable MCPEXP003 // RunSessionHandler is experimental
216+
#pragma warning disable MCPEXP002 // RunSessionHandler is experimental
217217
opts.RunSessionHandler = async (context, server, cancellationToken) =>
218218
{
219219
Interlocked.Increment(ref runSessionCount);
220220
serverTcs.TrySetResult(server);
221221
await server.RunAsync(cancellationToken);
222222
};
223-
#pragma warning restore MCPEXP003
223+
#pragma warning restore MCPEXP002
224224
}).WithTools<EchoHttpContextUserTools>();
225225

226226
await using var app = Builder.Build();
@@ -483,13 +483,13 @@ public async Task DisposeAsync_DoesNotHang_WhenOwnsSessionIsFalse_WithUnsolicite
483483
Builder.Services.AddMcpServer().WithHttpTransport(opts =>
484484
{
485485
ConfigureStateless(opts);
486-
#pragma warning disable MCPEXP003 // RunSessionHandler is experimental
486+
#pragma warning disable MCPEXP002 // RunSessionHandler is experimental
487487
opts.RunSessionHandler = async (context, server, cancellationToken) =>
488488
{
489489
serverTcs.TrySetResult(server);
490490
await server.RunAsync(cancellationToken);
491491
};
492-
#pragma warning restore MCPEXP003
492+
#pragma warning restore MCPEXP002
493493
}).WithTools<ClaimsPrincipalTools>();
494494

495495
await using var app = Builder.Build();

tests/ModelContextProtocol.AspNetCore.Tests/ResumabilityIntegrationTestsBase.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -253,13 +253,13 @@ public virtual async Task Client_CanResumeUnsolicitedMessageStream_AfterDisconne
253253

254254
await using var app = await CreateServerAsync(configureTransport: options =>
255255
{
256-
#pragma warning disable MCPEXP003 // RunSessionHandler is experimental
256+
#pragma warning disable MCPEXP002 // RunSessionHandler is experimental
257257
options.RunSessionHandler = (httpContext, mcpServer, cancellationToken) =>
258258
{
259259
serverTcs.TrySetResult(mcpServer);
260260
return mcpServer.RunAsync(cancellationToken);
261261
};
262-
#pragma warning restore MCPEXP003
262+
#pragma warning restore MCPEXP002
263263
});
264264

265265
await using var client = await ConnectClientAsync();

tests/ModelContextProtocol.AspNetCore.Tests/SseIntegrationTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ public async Task ConnectAndReceiveNotification_InMemoryServer()
8383
Builder.Services.AddMcpServer()
8484
.WithHttpTransport(httpTransportOptions =>
8585
{
86-
#pragma warning disable MCPEXP003 // RunSessionHandler is experimental
86+
#pragma warning disable MCPEXP002 // RunSessionHandler is experimental
8787
httpTransportOptions.RunSessionHandler = (httpContext, mcpServer, cancellationToken) =>
8888
{
8989
// We could also use ServerCapabilities.NotificationHandlers, but it's good to have some test coverage of RunSessionHandler.
@@ -94,7 +94,7 @@ public async Task ConnectAndReceiveNotification_InMemoryServer()
9494
});
9595
return mcpServer.RunAsync(cancellationToken);
9696
};
97-
#pragma warning restore MCPEXP003
97+
#pragma warning restore MCPEXP002
9898
});
9999

100100
await using var app = Builder.Build();

tests/ModelContextProtocol.AspNetCore.Tests/StatelessServerTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,15 +158,15 @@ public async Task UnsolicitedNotification_Fails_WithInvalidOperationException()
158158
Builder.Services.AddMcpServer()
159159
.WithHttpTransport(options =>
160160
{
161-
#pragma warning disable MCPEXP003 // RunSessionHandler is experimental
161+
#pragma warning disable MCPEXP002 // RunSessionHandler is experimental
162162
options.RunSessionHandler = async (context, server, cancellationToken) =>
163163
{
164164
unsolicitedNotificationException = await Assert.ThrowsAsync<InvalidOperationException>(
165165
() => server.SendNotificationAsync(NotificationMethods.PromptListChangedNotification, TestContext.Current.CancellationToken));
166166

167167
await server.RunAsync(cancellationToken);
168168
};
169-
#pragma warning restore MCPEXP003
169+
#pragma warning restore MCPEXP002
170170
});
171171

172172
await StartAsync();

tests/ModelContextProtocol.AspNetCore.Tests/StreamableHttpServerConformanceTests.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -325,13 +325,13 @@ public async Task GetRequest_Receives_UnsolicitedNotifications()
325325
Builder.Services.AddMcpServer()
326326
.WithHttpTransport(options =>
327327
{
328-
#pragma warning disable MCPEXP003 // RunSessionHandler is experimental
328+
#pragma warning disable MCPEXP002 // RunSessionHandler is experimental
329329
options.RunSessionHandler = (httpContext, mcpServer, cancellationToken) =>
330330
{
331331
server = mcpServer;
332332
return mcpServer.RunAsync(cancellationToken);
333333
};
334-
#pragma warning restore MCPEXP003
334+
#pragma warning restore MCPEXP002
335335
});
336336

337337
await StartAsync();
@@ -367,13 +367,13 @@ public async Task SendNotificationAsync_DoesNotThrow_WhenNoGetRequestHasBeenMade
367367
Builder.Services.AddMcpServer()
368368
.WithHttpTransport(options =>
369369
{
370-
#pragma warning disable MCPEXP003 // RunSessionHandler is experimental
370+
#pragma warning disable MCPEXP002 // RunSessionHandler is experimental
371371
options.RunSessionHandler = (httpContext, mcpServer, cancellationToken) =>
372372
{
373373
server = mcpServer;
374374
return mcpServer.RunAsync(cancellationToken);
375375
};
376-
#pragma warning restore MCPEXP003
376+
#pragma warning restore MCPEXP002
377377
});
378378

379379
await StartAsync();
@@ -506,13 +506,13 @@ public async Task AsyncLocalSetInRunSessionHandlerCallback_Flows_ToAllToolCalls_
506506
.WithHttpTransport(options =>
507507
{
508508
options.PerSessionExecutionContext = true;
509-
#pragma warning disable MCPEXP003 // RunSessionHandler is experimental
509+
#pragma warning disable MCPEXP002 // RunSessionHandler is experimental
510510
options.RunSessionHandler = async (httpContext, mcpServer, cancellationToken) =>
511511
{
512512
asyncLocal.Value = $"RunSessionHandler ({totalSessionCount++})";
513513
await mcpServer.RunAsync(cancellationToken);
514514
};
515-
#pragma warning restore MCPEXP003
515+
#pragma warning restore MCPEXP002
516516
});
517517

518518
Builder.Services.AddSingleton(McpServerTool.Create([McpServerTool(Name = "async-local-session")] () => asyncLocal.Value));

0 commit comments

Comments
 (0)