From 272ebc970aa726237819980306021bced8cc4a9a Mon Sep 17 00:00:00 2001 From: Jayaraman Venkatesan <112980436+jayaraman-venkatesan@users.noreply.github.com> Date: Wed, 8 Apr 2026 22:00:49 -0400 Subject: [PATCH] Fix stateless HTTP transport advertising listChanged capability (#1486) --- .../Server/McpServerImpl.cs | 12 +++++++++ .../StatelessServerTests.cs | 26 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/src/ModelContextProtocol.Core/Server/McpServerImpl.cs b/src/ModelContextProtocol.Core/Server/McpServerImpl.cs index 50c988cde..04d11e016 100644 --- a/src/ModelContextProtocol.Core/Server/McpServerImpl.cs +++ b/src/ModelContextProtocol.Core/Server/McpServerImpl.cs @@ -98,6 +98,18 @@ public McpServerImpl(ITransport transport, McpServerOptions options, ILoggerFact _notificationHandlers.RegisterRange(notificationHandlers); } + // In stateless mode, the server cannot send unsolicited notifications, + // so listChanged should not be advertised. + if (transport is StreamableHttpServerTransport { Stateless: true }) + { + if (ServerCapabilities.Tools is not null) + ServerCapabilities.Tools.ListChanged = null; + if (ServerCapabilities.Prompts is not null) + ServerCapabilities.Prompts.ListChanged = null; + if (ServerCapabilities.Resources is not null) + ServerCapabilities.Resources.ListChanged = null; + } + // Now that everything has been configured, subscribe to any necessary notifications. if (transport is not StreamableHttpServerTransport streamableHttpTransport || streamableHttpTransport.Stateless is false) { diff --git a/tests/ModelContextProtocol.AspNetCore.Tests/StatelessServerTests.cs b/tests/ModelContextProtocol.AspNetCore.Tests/StatelessServerTests.cs index 131adcdf2..80c37ea61 100644 --- a/tests/ModelContextProtocol.AspNetCore.Tests/StatelessServerTests.cs +++ b/tests/ModelContextProtocol.AspNetCore.Tests/StatelessServerTests.cs @@ -292,6 +292,32 @@ public async Task ConfigureSessionOptions_RunsPerRequest_InStatelessMode() Assert.Equal("configured-beta", Assert.IsType(content2).Text); } + [Fact] + public async Task StatelessMode_DoesNotAdvertise_ListChangedCapabilities() + { + Builder.Services.AddMcpServer() + .WithHttpTransport(options => + { + options.Stateless = true; + }) + .WithTools([McpServerTool.Create(() => "result", new() { Name = "myTool" })]) + .WithPrompts([McpServerPrompt.Create(() => new GetPromptResult(), new() { Name = "myPrompt" })]) + .WithResources([McpServerResource.Create(() => new ReadResourceResult(), new() { UriTemplate = "resource://test" })]); + + _app = Builder.Build(); + _app.MapMcp(); + await _app.StartAsync(TestContext.Current.CancellationToken); + + HttpClient.DefaultRequestHeaders.Accept.Add(new("application/json")); + HttpClient.DefaultRequestHeaders.Accept.Add(new("text/event-stream")); + + await using var client = await ConnectMcpClientAsync(); + + Assert.Null(client.ServerCapabilities.Tools?.ListChanged); + Assert.Null(client.ServerCapabilities.Prompts?.ListChanged); + Assert.Null(client.ServerCapabilities.Resources?.ListChanged); + } + [McpServerTool(Name = "testSamplingErrors")] public static async Task TestSamplingErrors(McpServer server) {