using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Primitives; using ModelContextProtocol.Client; namespace ModelContextProtocol.AspNetCore.Tests; public class MapMcpStreamableHttpTests(ITestOutputHelper outputHelper) : MapMcpTests(outputHelper) { protected override bool UseStreamableHttp => true; protected override bool Stateless => false; [Theory] [InlineData("/a", "/a")] [InlineData("/a", "/a/")] [InlineData("/a/", "/a/")] [InlineData("/a/", "/a")] [InlineData("/a/b", "/a/b")] public async Task CanConnect_WithMcpClient_AfterCustomizingRoute(string routePattern, string requestPath) { Builder.Services.AddMcpServer(options => { options.ServerInfo = new() { Name = "TestCustomRouteServer", Version = "1.0.0", }; }).WithHttpTransport(ConfigureStateless); await using var app = Builder.Build(); app.MapMcp(routePattern); await app.StartAsync(TestContext.Current.CancellationToken); await using var mcpClient = await ConnectAsync(requestPath); Assert.Equal("TestCustomRouteServer", mcpClient.ServerInfo.Name); } [Fact] public async Task StreamableHttpMode_Works_WithRootEndpoint() { Builder.Services.AddMcpServer(options => { options.ServerInfo = new() { Name = "StreamableHttpTestServer", Version = "1.0.0", }; }).WithHttpTransport(ConfigureStateless); await using var app = Builder.Build(); app.MapMcp(); await app.StartAsync(TestContext.Current.CancellationToken); await using var mcpClient = await ConnectAsync("/", new() { Endpoint = new Uri("http://localhost/"), TransportMode = HttpTransportMode.AutoDetect }); Assert.Equal("StreamableHttpTestServer", mcpClient.ServerInfo.Name); } [Fact] public async Task AutoDetectMode_Works_WithRootEndpoint() { Builder.Services.AddMcpServer(options => { options.ServerInfo = new() { Name = "AutoDetectTestServer", Version = "1.0.0", }; }).WithHttpTransport(ConfigureStateless); await using var app = Builder.Build(); app.MapMcp(); await app.StartAsync(TestContext.Current.CancellationToken); await using var mcpClient = await ConnectAsync("/", new() { Endpoint = new Uri("http://localhost/"), TransportMode = HttpTransportMode.AutoDetect }); Assert.Equal("AutoDetectTestServer", mcpClient.ServerInfo.Name); } [Fact] public async Task AutoDetectMode_Works_WithSseEndpoint() { Assert.SkipWhen(Stateless, "SSE endpoint is disabled in stateless mode."); Builder.Services.AddMcpServer(options => { options.ServerInfo = new() { Name = "AutoDetectSseTestServer", Version = "1.0.0", }; }).WithHttpTransport(ConfigureStateless); await using var app = Builder.Build(); app.MapMcp(); await app.StartAsync(TestContext.Current.CancellationToken); await using var mcpClient = await ConnectAsync("/sse", new() { Endpoint = new Uri("http://localhost/sse"), TransportMode = HttpTransportMode.AutoDetect }); Assert.Equal("AutoDetectSseTestServer", mcpClient.ServerInfo.Name); } [Fact] public async Task SseMode_Works_WithSseEndpoint() { Assert.SkipWhen(Stateless, "SSE endpoint is disabled in stateless mode."); Builder.Services.AddMcpServer(options => { options.ServerInfo = new() { Name = "SseTestServer", Version = "1.0.0", }; }).WithHttpTransport(ConfigureStateless); await using var app = Builder.Build(); app.MapMcp(); await app.StartAsync(TestContext.Current.CancellationToken); await using var mcpClient = await ConnectAsync(transportOptions: new() { Endpoint = new Uri("http://localhost/sse"), TransportMode = HttpTransportMode.Sse }); Assert.Equal("SseTestServer", mcpClient.ServerInfo.Name); } [Fact] public async Task StreamableHttpClient_SendsMcpProtocolVersionHeader_AfterInitialization() { var protocolVersionHeaderValues = new List(); Builder.Services.AddMcpServer().WithHttpTransport(ConfigureStateless).WithTools(); await using var app = Builder.Build(); app.Use(next => { return async context => { if (!StringValues.IsNullOrEmpty(context.Request.Headers["mcp-session-id"])) { protocolVersionHeaderValues.Add(context.Request.Headers["mcp-protocol-version"]); } await next(context); }; }); app.MapMcp(); await app.StartAsync(TestContext.Current.CancellationToken); await using var mcpClient = await ConnectAsync(clientOptions: new() { ProtocolVersion = "2025-03-26", }); await mcpClient.ListToolsAsync(cancellationToken: TestContext.Current.CancellationToken); // The header should be included in the GET request, the initialized notification, and the tools/list call. Assert.Equal(3, protocolVersionHeaderValues.Count); Assert.All(protocolVersionHeaderValues, v => Assert.Equal("2025-03-26", v)); } }