65 lines
3.4 KiB
Markdown
65 lines
3.4 KiB
Markdown
---
|
|
title: Cancellation
|
|
author: jeffhandley
|
|
description: How to cancel in-flight MCP requests using cancellation tokens and notifications.
|
|
uid: cancellation
|
|
---
|
|
|
|
## Cancellation
|
|
|
|
MCP supports [cancellation] of in-flight requests. Either side can cancel a previously issued request, and `CancellationToken` parameters on MCP methods are wired to send and receive `notifications/cancelled` notifications over the protocol.
|
|
|
|
[cancellation]: https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/cancellation
|
|
[task cancellation]: https://learn.microsoft.com/dotnet/standard/parallel-programming/task-cancellation
|
|
|
|
> [!NOTE]
|
|
> The source and lifetime of the `CancellationToken` provided to server handlers depends on the transport and session mode. In [stateless mode](xref:stateless#stateless-mode-recommended), the token is tied to the HTTP request — if the client disconnects, the handler is cancelled. In [stateful mode](xref:stateless#stateful-mode-sessions), the token is tied to the session lifetime. See [Cancellation and disposal](xref:stateless#cancellation-and-disposal) for details.
|
|
|
|
### How cancellation maps to MCP notifications
|
|
|
|
When a `CancellationToken` passed to a client method (such as <xref:ModelContextProtocol.Client.McpClient.CallToolAsync*>) is cancelled, a `notifications/cancelled` notification is sent to the server with the request ID. On the server side, the `CancellationToken` provided to the tool method is then triggered, allowing the handler to stop work gracefully. This same mechanism works in reverse for server-to-client requests.
|
|
|
|
### Server-side cancellation handling
|
|
|
|
Server tool methods receive a `CancellationToken` that is triggered when the client sends a cancellation notification. Pass this token through to any async operations so they stop promptly:
|
|
|
|
```csharp
|
|
[McpServerTool, Description("A long-running computation")]
|
|
public static async Task<string> LongComputation(
|
|
[Description("Number of iterations")] int iterations,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
for (int i = 0; i < iterations; i++)
|
|
{
|
|
await Task.Delay(1000, cancellationToken);
|
|
}
|
|
|
|
return $"Completed {iterations} iterations.";
|
|
}
|
|
```
|
|
|
|
When the client sends a cancellation notification, the `OperationCanceledException` propagates back to the client as a cancellation response.
|
|
|
|
### Cancellation notification details
|
|
|
|
The cancellation notification includes:
|
|
|
|
- **RequestId**: The ID of the request to cancel, allowing the receiver to correlate the cancellation with the correct in-flight request.
|
|
- **Reason**: An optional human-readable reason for the cancellation.
|
|
|
|
Cancellation notifications can be observed by registering a handler. For broader interception of notifications and other messages, <xref:ModelContextProtocol.Server.McpMessageFilter> delegates can be added to the <xref:ModelContextProtocol.Server.McpMessageFilters.IncomingFilters> collection in <xref:ModelContextProtocol.Server.McpServerOptions.Filters>.
|
|
|
|
```csharp
|
|
mcpClient.RegisterNotificationHandler(
|
|
NotificationMethods.CancelledNotification,
|
|
(notification, ct) =>
|
|
{
|
|
var cancelled = notification.Params?.Deserialize<CancelledNotificationParams>(
|
|
McpJsonUtilities.DefaultOptions);
|
|
if (cancelled is not null)
|
|
{
|
|
Console.WriteLine($"Request {cancelled.RequestId} cancelled: {cancelled.Reason}");
|
|
}
|
|
return default;
|
|
});
|
|
```
|