using ModelContextProtocol.Extensions.Tasks; using Microsoft.Extensions.DependencyInjection; using ModelContextProtocol.Client; using ModelContextProtocol.Protocol; using ModelContextProtocol.Server; using System.Runtime.InteropServices; #pragma warning disable MCPEXP001 namespace ModelContextProtocol.Tests.Server; /// /// Pins the behavior when a client signals the SEP-2575 tasks opt-in via _meta but the server /// has neither nor a /// configured. The expected behavior is a silent synchronous fallback: the server returns the normal /// with no Task envelope and no exception. /// public class McpServerTasksNoStoreTests : ClientServerTestBase { public McpServerTasksNoStoreTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper) { #if !NET Assert.SkipWhen(RuntimeInformation.IsOSPlatform(OSPlatform.Windows), "https://github.com/modelcontextprotocol/csharp-sdk/issues/587"); #endif } protected override void ConfigureServices(ServiceCollection services, IMcpServerBuilder mcpServerBuilder) { // Intentionally do NOT configure TaskStore or CallToolWithAlternateHandler. mcpServerBuilder.WithTools(); } [Fact] public async Task ClientOptIn_NoTaskStore_FallsBackToSyncResult() { await using var client = await CreateMcpClientForServer(); var ct = TestContext.Current.CancellationToken; // CallToolAsTaskAsync always writes the SEP-2575 tasks opt-in into _meta. var augmented = await client.CallToolAsTaskAsync( new CallToolRequestParams { Name = "sync-tool" }, ct); // With no task store configured, the server must complete synchronously and return // the standard CallToolResult — not a task envelope. Assert.False(augmented.IsTask); Assert.NotNull(augmented.Result); Assert.Equal("sync result", Assert.IsType(augmented.Result!.Content[0]).Text); } [Fact] public async Task ClientOptIn_NoTaskStore_CallToolAsync_StillReturnsResult() { // CallToolAsync (the higher-level convenience) must also work in the no-store case, // delegating to the underlying sync path without throwing. await using var client = await CreateMcpClientForServer(); var ct = TestContext.Current.CancellationToken; var result = await client.CallToolWithPollingAsync( new CallToolRequestParams { Name = "sync-tool" }, cancellationToken: ct); Assert.NotNull(result); Assert.Equal("sync result", Assert.IsType(result.Content[0]).Text); } [McpServerToolType] private sealed class NoStoreTools { [McpServerTool(Name = "sync-tool"), System.ComponentModel.Description("A plain sync tool")] public static string SyncTool() => "sync result"; } }