Skip to content

Commit 6e485a9

Browse files
Order notification round-trip test by server-set LastUpdatedAt
The server emits the Working and Completed notifications in strict order (each SendTaskStatusNotificationAsync awaits the transport write), but the client-side McpSessionHandler dispatches each incoming message via a fire-and-forget Task with a forced thread-pool yield, so user-registered notification handlers may observe them out of receipt order. Net10's thread-pool scheduling exposed this race intermittently. Give the two notifications distinct LastUpdatedAt values on the server and sort by that timestamp before asserting types/payloads, so the test asserts the round-trip without depending on client-side dispatch order. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 81da12f commit 6e485a9

1 file changed

Lines changed: 19 additions & 11 deletions

File tree

tests/ModelContextProtocol.Tests/Server/McpTaskStoreTests.cs

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -347,15 +347,20 @@ public async Task SendTaskStatusNotificationAsync_FromTool_DeliversTypedNotifica
347347

348348
Assert.Equal("notified", Assert.IsType<TextContentBlock>(result.Content[0]).Text);
349349

350-
// Read both notifications and verify they round-trip to the right typed subtype + payload.
351-
var working = await notifications.Reader.ReadAsync(ct);
352-
var completed = await notifications.Reader.ReadAsync(ct);
353-
354-
var workingTyped = Assert.IsType<WorkingTaskNotificationParams>(working);
350+
// Read both notifications. The server emits them in strict order (each
351+
// SendTaskStatusNotificationAsync awaits the transport write before the next), but client-side
352+
// dispatch (McpSessionHandler.ProcessMessageAsync) is fire-and-forget per message, so user
353+
// handlers may observe them out of order. Reconstruct send order via the server-set
354+
// LastUpdatedAt timestamp.
355+
var first = await notifications.Reader.ReadAsync(ct);
356+
var second = await notifications.Reader.ReadAsync(ct);
357+
var ordered = new[] { first, second }.OrderBy(n => n.LastUpdatedAt).ToArray();
358+
359+
var workingTyped = Assert.IsType<WorkingTaskNotificationParams>(ordered[0]);
355360
Assert.Equal("notify-test-task-id", workingTyped.TaskId);
356361
Assert.Equal(McpTaskStatus.Working, workingTyped.Status);
357362

358-
var completedTyped = Assert.IsType<CompletedTaskNotificationParams>(completed);
363+
var completedTyped = Assert.IsType<CompletedTaskNotificationParams>(ordered[1]);
359364
Assert.Equal("notify-test-task-id", completedTyped.TaskId);
360365
Assert.Equal(McpTaskStatus.Completed, completedTyped.Status);
361366
Assert.Equal("notify-result", completedTyped.Result.GetString());
@@ -665,22 +670,25 @@ public static async Task<string> RootsTool(McpServer server, CancellationToken c
665670
[McpServerTool(Name = "notifying-tool"), System.ComponentModel.Description("A tool that emits SendTaskStatusNotificationAsync from inside the task wrapper")]
666671
public static async Task<string> NotifyingTool(McpServer server, CancellationToken cancellationToken)
667672
{
668-
var now = DateTimeOffset.UtcNow;
673+
var createdAt = DateTimeOffset.UtcNow;
669674

670675
// Emit working then completed notifications using the public SendTaskStatusNotificationAsync API,
671676
// so the test asserts the wire round-trip end-to-end (server → transport → client handler).
677+
// Use distinct LastUpdatedAt values so the test can reconstruct send order on the receive side
678+
// (client-side dispatch via McpSessionHandler.ProcessMessageAsync is fire-and-forget per message
679+
// and may surface notifications to user handlers out of receipt order).
672680
await server.SendTaskStatusNotificationAsync(new WorkingTaskNotificationParams
673681
{
674682
TaskId = "notify-test-task-id",
675-
CreatedAt = now,
676-
LastUpdatedAt = now,
683+
CreatedAt = createdAt,
684+
LastUpdatedAt = createdAt,
677685
}, cancellationToken);
678686

679687
await server.SendTaskStatusNotificationAsync(new CompletedTaskNotificationParams
680688
{
681689
TaskId = "notify-test-task-id",
682-
CreatedAt = now,
683-
LastUpdatedAt = now,
690+
CreatedAt = createdAt,
691+
LastUpdatedAt = createdAt.AddTicks(1),
684692
Result = JsonElement.Parse("\"notify-result\""),
685693
}, cancellationToken);
686694

0 commit comments

Comments
 (0)