Skip to content

Commit 5b3348f

Browse files
Copilotstephentoub
andcommitted
Fix error code usage to align with updated MCP spec guidance
Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
1 parent dc8feff commit 5b3348f

File tree

6 files changed

+49
-17
lines changed

6 files changed

+49
-17
lines changed

docs/concepts/tasks/tasks.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -424,8 +424,8 @@ Task operations may throw <xref:ModelContextProtocol.McpException> with these er
424424

425425
| Error Code | Scenario |
426426
|------------|----------|
427-
| `InvalidParams` | Invalid or nonexistent task ID |
428-
| `InvalidRequest` | Tool with `taskSupport: forbidden` called with task metadata, or tool with `taskSupport: required` called without task metadata |
427+
| `InvalidParams` | Invalid or nonexistent task ID, invalid cursor, or attempting to cancel a task already in a terminal status |
428+
| `InvalidParams` | Tool with `taskSupport: forbidden` called with task metadata, or tool with `taskSupport: required` called without task metadata |
429429
| `InternalError` | Task execution failure or result unavailable |
430430

431431
Example error handling:

src/ModelContextProtocol.Core/Client/McpClientImpl.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,21 @@ private void RegisterTaskHandlers(RequestHandlers requestHandlers, IMcpTaskStore
461461
throw new McpProtocolException("Missing required parameter 'taskId'", McpErrorCode.InvalidParams);
462462
}
463463

464+
// Get current task status
465+
var currentTask = await taskStore.GetTaskAsync(taskId, SessionId, cancellationToken).ConfigureAwait(false);
466+
if (currentTask is null)
467+
{
468+
throw new McpProtocolException($"Task not found: '{taskId}'", McpErrorCode.InvalidParams);
469+
}
470+
471+
// Validate not already in terminal status
472+
if (currentTask.Status is McpTaskStatus.Completed or McpTaskStatus.Failed or McpTaskStatus.Cancelled)
473+
{
474+
throw new McpProtocolException(
475+
$"Cannot cancel task '{taskId}' in terminal status '{currentTask.Status}'.",
476+
McpErrorCode.InvalidParams);
477+
}
478+
464479
// Signal cancellation if task is still running
465480
_taskCancellationTokenProvider!.Cancel(taskId);
466481

src/ModelContextProtocol.Core/Server/McpServerImpl.cs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -591,7 +591,7 @@ await originalListToolsHandler(request, cancellationToken).ConfigureAwait(false)
591591
{
592592
throw new McpProtocolException(
593593
$"Tool '{tool.ProtocolTool.Name}' does not support task-augmented execution.",
594-
McpErrorCode.MethodNotFound);
594+
McpErrorCode.InvalidParams);
595595
}
596596

597597
// Task augmentation requested - return CreateTaskResult
@@ -604,7 +604,7 @@ await originalListToolsHandler(request, cancellationToken).ConfigureAwait(false)
604604
throw new McpProtocolException(
605605
$"Tool '{tool.ProtocolTool.Name}' requires task-augmented execution. " +
606606
"Include a 'task' parameter with the request.",
607-
McpErrorCode.MethodNotFound);
607+
McpErrorCode.InvalidParams);
608608
}
609609

610610
// Normal synchronous execution
@@ -771,6 +771,21 @@ async Task<JsonElement> GetTaskResultAsync(RequestContext<GetTaskPayloadRequestP
771771
throw new McpProtocolException("Missing required parameter 'taskId'", McpErrorCode.InvalidParams);
772772
}
773773

774+
// Get current task status
775+
var currentTask = await taskStore.GetTaskAsync(taskId, SessionId, cancellationToken).ConfigureAwait(false);
776+
if (currentTask is null)
777+
{
778+
throw new McpProtocolException($"Task not found: '{taskId}'", McpErrorCode.InvalidParams);
779+
}
780+
781+
// Validate not already in terminal status
782+
if (currentTask.Status is McpTaskStatus.Completed or McpTaskStatus.Failed or McpTaskStatus.Cancelled)
783+
{
784+
throw new McpProtocolException(
785+
$"Cannot cancel task '{taskId}' in terminal status '{currentTask.Status}'.",
786+
McpErrorCode.InvalidParams);
787+
}
788+
774789
// Signal cancellation if task is still running
775790
_taskCancellationTokenProvider!.Cancel(taskId);
776791

tests/ModelContextProtocol.Tests/Server/McpServerTaskAugmentedValidationTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ await client.CallToolAsync(
100100
TestContext.Current.CancellationToken));
101101

102102
Assert.Contains("does not support task-augmented execution", exception.Message, StringComparison.OrdinalIgnoreCase);
103-
Assert.Equal(McpErrorCode.MethodNotFound, exception.ErrorCode);
103+
Assert.Equal(McpErrorCode.InvalidParams, exception.ErrorCode);
104104
}
105105

106106
[Fact]
@@ -227,7 +227,7 @@ await client.CallToolAsync(
227227
TestContext.Current.CancellationToken));
228228

229229
Assert.Contains("requires task-augmented execution", exception.Message, StringComparison.OrdinalIgnoreCase);
230-
Assert.Equal(McpErrorCode.MethodNotFound, exception.ErrorCode);
230+
Assert.Equal(McpErrorCode.InvalidParams, exception.ErrorCode);
231231
}
232232

233233
[Fact]

tests/ModelContextProtocol.Tests/Server/TaskCancellationIntegrationTests.cs

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -460,13 +460,14 @@ public async Task CompletedTask_CannotTransitionToOtherStatus()
460460

461461
Assert.Equal(McpTaskStatus.Completed, taskStatus.Status);
462462

463-
// Act - Try to cancel a completed task
464-
var cancelResult = await client.CancelTaskAsync(taskId, cancellationToken: TestContext.Current.CancellationToken);
463+
// Act & Assert - Try to cancel a completed task should throw InvalidParams
464+
var exception = await Assert.ThrowsAsync<McpProtocolException>(async () =>
465+
await client.CancelTaskAsync(taskId, cancellationToken: TestContext.Current.CancellationToken));
465466

466-
// Assert - Status should still be completed (not cancelled)
467-
Assert.Equal(McpTaskStatus.Completed, cancelResult.Status);
467+
Assert.Equal(McpErrorCode.InvalidParams, exception.ErrorCode);
468+
Assert.Contains("terminal status", exception.Message, StringComparison.OrdinalIgnoreCase);
468469

469-
// Verify via get
470+
// Verify task status unchanged
470471
var verifyStatus = await client.GetTaskAsync(taskId, cancellationToken: TestContext.Current.CancellationToken);
471472
Assert.Equal(McpTaskStatus.Completed, verifyStatus.Status);
472473
}
@@ -500,10 +501,11 @@ public async Task FailedTask_CannotTransitionToOtherStatus()
500501

501502
Assert.Equal(McpTaskStatus.Failed, taskStatus.Status);
502503

503-
// Act - Try to cancel a failed task
504-
var cancelResult = await client.CancelTaskAsync(taskId, cancellationToken: TestContext.Current.CancellationToken);
504+
// Act & Assert - Try to cancel a failed task should throw InvalidParams
505+
var exception = await Assert.ThrowsAsync<McpProtocolException>(async () =>
506+
await client.CancelTaskAsync(taskId, cancellationToken: TestContext.Current.CancellationToken));
505507

506-
// Assert - Status should still be failed
507-
Assert.Equal(McpTaskStatus.Failed, cancelResult.Status);
508+
Assert.Equal(McpErrorCode.InvalidParams, exception.ErrorCode);
509+
Assert.Contains("terminal status", exception.Message, StringComparison.OrdinalIgnoreCase);
508510
}
509511
}

tests/ModelContextProtocol.Tests/Server/ToolTaskSupportTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -526,8 +526,8 @@ public async Task SyncTool_WithRequiredTaskSupport_CannotBeCalledDirectly()
526526
arguments: new Dictionary<string, object?> { ["input"] = "test" },
527527
cancellationToken: TestContext.Current.CancellationToken).AsTask());
528528

529-
// The server returns MethodNotFound because direct invocation is not allowed for required-task tools
530-
Assert.Equal(McpErrorCode.MethodNotFound, exception.ErrorCode);
529+
// The server returns InvalidParams because direct invocation is not allowed for required-task tools
530+
Assert.Equal(McpErrorCode.InvalidParams, exception.ErrorCode);
531531
Assert.Contains("task", exception.Message, StringComparison.OrdinalIgnoreCase);
532532
}
533533

0 commit comments

Comments
 (0)