using ModelContextProtocol.Extensions.Tasks;
using Microsoft.Extensions.DependencyInjection;
using ModelContextProtocol.Protocol;
using ModelContextProtocol.Server;
using System.Runtime.InteropServices;
using System.Text.Json;
using System.Text.Json.Serialization.Metadata;
namespace ModelContextProtocol.Tests.Server;
///
/// Verifies behavior when a handler returns a alternate without
/// the server having any tasks/get handler registered. After the ResultOrAlternate
/// generalization, the Core server no longer guards against this -- the extension is responsible
/// for ensuring lifecycle handlers are registered.
///
public class TaskHandlerConfigurationValidationTests : ClientServerTestBase
{
private static readonly JsonTypeInfo s_createTaskResultTypeInfo = McpTasksJsonContext.Default.CreateTaskResult;
public TaskHandlerConfigurationValidationTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper)
{
#if !NET
Assert.SkipWhen(RuntimeInformation.IsOSPlatform(OSPlatform.Windows), "https://github.com/modelcontextprotocol/csharp-sdk/issues/587");
#endif
}
#pragma warning disable MCPEXP002 // exercises the experimental CallToolWithAlternateHandler/ResultOrAlternate seam
protected override void ConfigureServices(ServiceCollection services, IMcpServerBuilder mcpServerBuilder)
{
mcpServerBuilder.Services.Configure(options =>
{
options.Capabilities ??= new ServerCapabilities();
// Configure a task-augmented handler without TaskStore or any of the
// task lifecycle request handlers.
options.Handlers.CallToolWithAlternateHandler = (context, cancellationToken) =>
new ValueTask>(
ResultOrAlternate.FromAlternate(
new CreateTaskResult
{
TaskId = "orphan-task",
Status = McpTaskStatus.Working,
CreatedAt = DateTimeOffset.UtcNow,
LastUpdatedAt = DateTimeOffset.UtcNow,
},
s_createTaskResultTypeInfo));
});
}
#pragma warning restore MCPEXP002
[Fact]
public async Task ServerAcceptsAlternateHandler_WithoutTasksGetHandler_NoStartupError()
{
// The Core guard that previously threw InvalidOperationException at request time when
// a CallToolWithAlternateHandler returned a CreateTaskResult without tasks/get being
// registered has been removed. The extension is now responsible for that guarantee.
// This test verifies the server starts and connects successfully with such configuration.
await using var client = await CreateMcpClientForServer();
// If we get here, the server accepted the handler config without error.
Assert.NotNull(client);
}
}