SSE client transport: tolerate return of RPC responses in POST requests. (#445)

* SSE client transport: handle return of RPC responses in POST requests.

* Remove accidental change.

* Address feedback

* Remove unused namespace.
This commit is contained in:
Eirik Tsarpalis 2025-05-23 21:39:54 +03:00 • committed by GitHub
commit 77f9e64e04
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -58,6 +58,22 @@ public partial class SseIntegrationTests(ITestOutputHelper outputHelper) : Kestr
Assert.True(true);
}
[Fact]
public async Task ConnectAndReceiveMessage_ServerReturningJsonInPostRequest()
{
await using var app = Builder.Build();
MapAbsoluteEndpointUriMcp(app, respondInJson: true);
await app.StartAsync(TestContext.Current.CancellationToken);
await using var mcpClient = await ConnectMcpClientAsync();
// Send a test message through POST endpoint
await mcpClient.SendNotificationAsync("test/message", new Envelope { Message = "Hello, SSE!" }, serializerOptions: JsonContext.Default.Options, cancellationToken: TestContext.Current.CancellationToken);
Assert.True(true);
}
[Fact]
public async Task ConnectAndReceiveNotification_InMemoryServer()
{
@ -220,7 +236,7 @@ public partial class SseIntegrationTests(ITestOutputHelper outputHelper) : Kestr
Assert.Equal("Failed to add header '' with value '' from AdditionalHeaders.", ex.Message);
}
private static void MapAbsoluteEndpointUriMcp(IEndpointRouteBuilder endpoints)
private static void MapAbsoluteEndpointUriMcp(IEndpointRouteBuilder endpoints, bool respondInJson = false)
{
var loggerFactory = endpoints.ServiceProvider.GetRequiredService<ILoggerFactory>();
var optionsSnapshot = endpoints.ServiceProvider.GetRequiredService<IOptions<McpServerOptions>>();
@ -267,7 +283,7 @@ public partial class SseIntegrationTests(ITestOutputHelper outputHelper) : Kestr
await Results.BadRequest("Session not started.").ExecuteAsync(context);
return;
}
var message = (JsonRpcMessage?)await context.Request.ReadFromJsonAsync(McpJsonUtilities.DefaultOptions.GetTypeInfo(typeof(JsonRpcMessage)), context.RequestAborted);
var message = await context.Request.ReadFromJsonAsync<JsonRpcMessage>(McpJsonUtilities.DefaultOptions, context.RequestAborted);
if (message is null)
{
await Results.BadRequest("No message in request body.").ExecuteAsync(context);
@ -276,7 +292,15 @@ public partial class SseIntegrationTests(ITestOutputHelper outputHelper) : Kestr
await session.OnMessageReceivedAsync(message, context.RequestAborted);
context.Response.StatusCode = StatusCodes.Status202Accepted;
await context.Response.WriteAsync("Accepted");
if (respondInJson)
{
await context.Response.WriteAsJsonAsync(message, McpJsonUtilities.DefaultOptions, cancellationToken: context.RequestAborted);
}
else
{
await context.Response.WriteAsync("Accepted");
}
});
}