Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/modelcontextprotocol/csharp-sdk/llms.txt

Use this file to discover all available pages before exploring further.

The MCP C# SDK provides built-in support for progress tracking during long-running operations. This feature allows servers to send real-time progress updates to clients while processing tools, prompts, or resources.

Server Implementation

Sending Progress Notifications

Servers can send progress updates using the SendNotificationAsync method on McpServer. The server must check if the client provided a progressToken in the request before sending notifications.
1

Accept McpServer in your tool

Add McpServer and RequestContext<CallToolRequestParams> parameters to access the server instance and request context:
[McpServerTool]
[Description("Demonstrates a long running tool with progress updates")]
public static async Task<string> LongRunningTool(
    McpServer server,
    RequestContext<CallToolRequestParams> context,
    int duration = 10,
    int steps = 5)
{
    var progressToken = context.Params?.ProgressToken;
    // ...
}
2

Check for progress token

Verify that the client provided a progressToken before sending notifications:
if (progressToken is not null)
{
    // Client requested progress updates
}
3

Send progress notifications

Use SendNotificationAsync with ProgressNotificationParams to report progress:
await server.SendNotificationAsync(
    "notifications/progress",
    new ProgressNotificationParams
    {
        ProgressToken = progressToken.Value,
        Progress = new ProgressNotificationValue
        {
            Progress = i,
            Total = steps,
            Message = $"Step {i} of {steps} completed."
        }
    });

Complete Server Example

Here’s a complete tool that performs work in steps and reports progress:
~/workspace/source/docs/concepts/progress/samples/server/Tools/LongRunningTools.cs
using System.ComponentModel;
using ModelContextProtocol;
using ModelContextProtocol.Protocol;
using ModelContextProtocol.Server;

namespace Progress.Tools;

[McpServerToolType]
public class LongRunningTools
{
    [McpServerTool, Description("Demonstrates a long running tool with progress updates")]
    public static async Task<string> LongRunningTool(
        McpServer server,
        RequestContext<CallToolRequestParams> context,
        int duration = 10,
        int steps = 5)
    {
        var progressToken = context.Params?.ProgressToken;
        var stepDuration = duration / steps;

        for (int i = 1; i <= steps; i++)
        {
            await Task.Delay(stepDuration * 1000);

            if (progressToken is not null)
            {
                await server.SendNotificationAsync("notifications/progress", new ProgressNotificationParams
                {
                    ProgressToken = progressToken.Value,
                    Progress = new ProgressNotificationValue
                    {
                        Progress = i,
                        Total = steps,
                        Message = $"Step {i} of {steps} completed.",
                    },
                });
            }
        }

        return $"Long running tool completed. Duration: {duration} seconds. Steps: {steps}.";
    }
}

Client Implementation

Clients have two ways to receive progress updates: using a Progress<T> handler or registering a global notification handler. The simplest approach is to pass a Progress<ProgressNotificationValue> instance when calling a tool:
~/workspace/source/docs/concepts/progress/samples/client/Program.cs
var progressHandler = new Progress<ProgressNotificationValue>(value =>
{
    Console.WriteLine($"Tool progress: {value.Progress} of {value.Total} - {value.Message}");
});

var result = await mcpClient.CallToolAsync(
    toolName: "longRunningTool",
    progress: progressHandler);
This approach automatically:
  • Generates a unique progressToken
  • Sends it with the request
  • Routes progress notifications to your handler
  • Only receives updates for this specific request

Using Global Notification Handler

For more control, register a global handler to receive all progress notifications:
var progressToken = Guid.NewGuid().ToString();

await using var handler = mcpClient.RegisterNotificationHandler(
    NotificationMethods.ProgressNotification,
    (notification, cancellationToken) =>
    {
        if (JsonSerializer.Deserialize<ProgressNotificationParams>(notification.Params) is { } pn &&
            pn.ProgressToken == progressToken)
        {
            Console.WriteLine($"Tool progress: {pn.Progress.Progress} of {pn.Progress.Total} - {pn.Progress.Message}");
        }
        return ValueTask.CompletedTask;
    });

var result = await mcpClient.CallToolAsync(
    new CallToolRequestParams
    {
        Name = "longRunningTool",
        ProgressToken = progressToken
    });
The global handler receives all progress notifications from the server. You must filter by progressToken to identify notifications for your specific request.

Progress Notification Structure

The ProgressNotificationValue contains:
PropertyTypeDescription
ProgressdoubleCurrent progress value
Totaldouble?Total expected value (optional)
Messagestring?Human-readable status message (optional)

Best Practices

Use progress tracking for operations that:
  • Take more than a few seconds
  • Have measurable progress (e.g., processing items, steps in a workflow)
  • Would benefit from user feedback during execution
Examples: image generation, batch processing, complex calculations, data imports
Balance between responsiveness and performance:
  • Too frequent: Network overhead, client UI updates
  • Too infrequent: Poor user experience
  • Recommended: Every 1-5 seconds or 5-10% progress increments
Not all clients request progress updates. Always verify the token exists before sending notifications:
if (context.Params?.ProgressToken is { } token)
{
    // Send progress
}
Servers are not required to support progress tracking. Clients should never depend on receiving progress updates and should implement appropriate timeouts.

API Reference