Automatically fall back from Streamable HTTP to SSE on the client by default (#456)
This commit is contained in:
parent
adb2098e4d
commit
317f468807
7 changed files with 489 additions and 49 deletions
|
|
@ -56,7 +56,7 @@ public abstract class HttpServerIntegrationTests : LoggedTest, IClassFixture<Sse
|
|||
|
||||
[Fact]
|
||||
public async Task ListTools_Sse_TestServer()
|
||||
{
|
||||
{
|
||||
// arrange
|
||||
|
||||
// act
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using ModelContextProtocol.Client;
|
||||
|
||||
namespace ModelContextProtocol.AspNetCore.Tests;
|
||||
|
||||
|
|
@ -34,4 +35,112 @@ public class MapMcpStreamableHttpTests(ITestOutputHelper outputHelper) : MapMcpT
|
|||
|
||||
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(options: new()
|
||||
{
|
||||
Endpoint = new Uri("http://localhost/sse"),
|
||||
TransportMode = HttpTransportMode.Sse
|
||||
});
|
||||
|
||||
Assert.Equal("SseTestServer", mcpClient.ServerInfo.Name);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,16 +20,17 @@ public abstract class MapMcpTests(ITestOutputHelper testOutputHelper) : KestrelI
|
|||
options.Stateless = Stateless;
|
||||
}
|
||||
|
||||
protected async Task<IMcpClient> ConnectAsync(string? path = null)
|
||||
protected async Task<IMcpClient> ConnectAsync(string? path = null, SseClientTransportOptions? options = null)
|
||||
{
|
||||
// Default behavior when no options are provided
|
||||
path ??= UseStreamableHttp ? "/" : "/sse";
|
||||
|
||||
var sseClientTransportOptions = new SseClientTransportOptions()
|
||||
await using var transport = new SseClientTransport(options ?? new SseClientTransportOptions()
|
||||
{
|
||||
Endpoint = new Uri($"http://localhost{path}"),
|
||||
UseStreamableHttp = UseStreamableHttp,
|
||||
};
|
||||
await using var transport = new SseClientTransport(sseClientTransportOptions, HttpClient, LoggerFactory);
|
||||
TransportMode = UseStreamableHttp ? HttpTransportMode.StreamableHttp : HttpTransportMode.Sse,
|
||||
}, HttpClient, LoggerFactory);
|
||||
|
||||
return await McpClientFactory.CreateAsync(transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,6 @@ public class StatelessServerIntegrationTests(SseServerIntegrationTestFixture fix
|
|||
{
|
||||
Endpoint = new Uri("http://localhost/stateless"),
|
||||
Name = "In-memory Streamable HTTP Client",
|
||||
UseStreamableHttp = true,
|
||||
TransportMode = HttpTransportMode.StreamableHttp,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ public class StatelessServerTests(ITestOutputHelper outputHelper) : KestrelInMem
|
|||
{
|
||||
Endpoint = new Uri("http://localhost/"),
|
||||
Name = "In-memory Streamable HTTP Client",
|
||||
UseStreamableHttp = true,
|
||||
TransportMode = HttpTransportMode.StreamableHttp,
|
||||
};
|
||||
|
||||
private async Task StartAsync()
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ public class StreamableHttpClientConformanceTests(ITestOutputHelper outputHelper
|
|||
await using var transport = new SseClientTransport(new()
|
||||
{
|
||||
Endpoint = new("http://localhost/mcp"),
|
||||
UseStreamableHttp = true,
|
||||
TransportMode = HttpTransportMode.StreamableHttp,
|
||||
}, HttpClient, LoggerFactory);
|
||||
|
||||
await using var client = await McpClientFactory.CreateAsync(transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
|
@ -118,7 +118,7 @@ public class StreamableHttpClientConformanceTests(ITestOutputHelper outputHelper
|
|||
await using var transport = new SseClientTransport(new()
|
||||
{
|
||||
Endpoint = new("http://localhost/mcp"),
|
||||
UseStreamableHttp = true,
|
||||
TransportMode = HttpTransportMode.StreamableHttp,
|
||||
}, HttpClient, LoggerFactory);
|
||||
|
||||
await using var client = await McpClientFactory.CreateAsync(transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ public class StreamableHttpServerIntegrationTests(SseServerIntegrationTestFixtur
|
|||
{
|
||||
Endpoint = new Uri("http://localhost/"),
|
||||
Name = "In-memory Streamable HTTP Client",
|
||||
UseStreamableHttp = true,
|
||||
TransportMode = HttpTransportMode.StreamableHttp,
|
||||
};
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
Loading…
Reference in a new issue