* Initial plan * Allow handlers for empty tool/prompt/resource collections Changed condition from `IsEmpty: false` to `not null` check in McpServerImpl.cs for resources (line 298), prompts (line 459), and tools (line 547). This allows handlers to be synthesized even when collections are empty, enabling dynamic addition of items after initialization. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Stephen Toub <stoub@microsoft.com>
68 lines
2.2 KiB
C#
68 lines
2.2 KiB
C#
using Microsoft.Extensions.DependencyInjection;
|
|
using ModelContextProtocol.Protocol;
|
|
using ModelContextProtocol.Server;
|
|
|
|
namespace ModelContextProtocol.Tests;
|
|
|
|
public class CancellationTests : ClientServerTestBase
|
|
{
|
|
public CancellationTests(ITestOutputHelper testOutputHelper)
|
|
: base(testOutputHelper)
|
|
{
|
|
}
|
|
|
|
protected override void ConfigureServices(ServiceCollection services, IMcpServerBuilder mcpServerBuilder)
|
|
{
|
|
services.AddSingleton(McpServerTool.Create(WaitForCancellation));
|
|
}
|
|
|
|
private static async Task WaitForCancellation(CancellationToken cancellationToken)
|
|
{
|
|
try
|
|
{
|
|
await Task.Delay(-1, cancellationToken);
|
|
throw new InvalidOperationException("Unexpected completion without exception");
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async Task PrecancelRequest_CancelsBeforeSending()
|
|
{
|
|
await using var client = await CreateMcpClientForServer();
|
|
|
|
bool gotCancellation = false;
|
|
await using (Server.RegisterNotificationHandler(NotificationMethods.CancelledNotification, (notification, cancellationToken) =>
|
|
{
|
|
gotCancellation = true;
|
|
return default;
|
|
}))
|
|
{
|
|
await Assert.ThrowsAsync<OperationCanceledException>(async () => await client.ListToolsAsync(cancellationToken: new CancellationToken(true)));
|
|
}
|
|
|
|
Assert.False(gotCancellation);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CancellationPropagation_RequestingCancellationCancelsPendingRequest()
|
|
{
|
|
await using var client = await CreateMcpClientForServer();
|
|
|
|
var tools = await client.ListToolsAsync(cancellationToken: TestContext.Current.CancellationToken);
|
|
var waitTool = tools.First(t => t.Name == "wait_for_cancellation");
|
|
|
|
CancellationTokenSource cts = new();
|
|
var waitTask = waitTool.InvokeAsync(cancellationToken: cts.Token);
|
|
Assert.False(waitTask.IsCompleted);
|
|
|
|
await Task.Delay(1, TestContext.Current.CancellationToken);
|
|
Assert.False(waitTask.IsCompleted);
|
|
|
|
cts.Cancel();
|
|
await Assert.ThrowsAnyAsync<OperationCanceledException>(async () => await waitTask);
|
|
}
|
|
}
|