Add stateless Streamable HTTP support (#392)

- It allows a single MCP session spanning multiple requests
  to be handled by different servers without sharing state
- It does require the servers share data protection keys, but this
  is standard for ASP.NET Core cookies and antiforgery as well
- It immediately throws for unsupported operations like sampling
This commit is contained in:
Stephen Halter 2025-05-14 04:51:03 -07:00 • committed by GitHub
commit ce9f73f43f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -3,6 +3,7 @@ using ModelContextProtocol.Protocol.Types;
using ModelContextProtocol.Server;
using ModelContextProtocol.Utils.Json;
using Serilog;
using System.Diagnostics;
using System.Text;
using System.Text.Json;
@ -16,12 +17,11 @@ public class Program
{
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Verbose() // Capture all log levels
.WriteTo.File(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "logs", "TestServer_.log"),
.WriteTo.File(Path.Combine(AppContext.BaseDirectory, "logs", "TestServer_.log"),
rollingInterval: RollingInterval.Day,
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}")
.CreateLogger();
var logsPath = Path.Combine(AppContext.BaseDirectory, "testserver.log");
loggingBuilder.AddSerilog();
}
@ -378,6 +378,26 @@ public class Program
};
}
private static void HandleStatelessMcp(IApplicationBuilder app)
{
var serviceCollection = new ServiceCollection();
serviceCollection.AddLogging();
serviceCollection.AddSingleton(app.ApplicationServices.GetRequiredService<ILoggerFactory>());
serviceCollection.AddSingleton(app.ApplicationServices.GetRequiredService<DiagnosticListener>());
serviceCollection.AddRoutingCore();
serviceCollection.AddMcpServer(ConfigureOptions).WithHttpTransport(options => options.Stateless = true);
var appBuilder = new ApplicationBuilder(serviceCollection.BuildServiceProvider());
appBuilder.UseRouting();
appBuilder.UseEndpoints(innerEndpoints =>
{
innerEndpoints.MapMcp("/stateless");
});
app.Run(appBuilder.Build());
}
public static async Task MainAsync(string[] args, ILoggerProvider? loggerProvider = null, IConnectionListenerFactory? kestrelTransport = null, CancellationToken cancellationToken = default)
{
Console.WriteLine("Starting server...");
@ -419,6 +439,9 @@ public class Program
app.UseRouting();
app.UseEndpoints(_ => { });
// Handle the /stateless endpoint if no other endpoints have been matched by the call to UseRouting above.
HandleStatelessMcp(app);
app.MapMcp();
await app.RunAsync(cancellationToken);