Report progress for long-running MCP requests
Put a unique progress token in the request’s `_meta`, then echo it in every `notifications/progress` message. Increase `progress` monotonically, keep any supplied `total` consistent, and stop notifications when the operation ends. Treat updates as optional status signals, not proof that the request can run without a maximum timeout.
Prerequisites
Complete initialization before starting the operation. The MCP lifecycle specification dated November 25, 2025 places normal request traffic in the operation phase, after protocol-version and capability negotiation. If that exchange is not already implemented, start with the MCP initialization lifecycle.
Progress reporting is optional. Under the MCP progress utility dated November 25, 2025, a requestor asks for updates by supplying a token, but the receiver may choose not to send any. Keep an ordinary request timeout and final result path; progress notifications supplement them rather than replacing them.
Report the operation’s progress
-
Create a token for the active request
Generate a
progressTokenbefore sending the long-running request. It must be a string or integer, and it must be unique across all requests that are active at the same time.The uniqueness condition is what makes concurrent work distinguishable. Two requests may emit interleaved notifications over the same connection, so ordering alone cannot tell the requestor which operation moved forward. A token reused only after its previous operation has ended does not create that ambiguity; a fixed token reused while several operations are active does.
Treat the token as opaque correlation data. It does not need to encode a user, method, percentage, or database key. Encoding those details creates coupling without changing how the protocol matches notifications.
-
Put the token in the request metadata
Add the token at
params._meta.progressToken. The requestor includes it only when it wants progress notifications for that operation. An illustrative tool request looks like this:{ "jsonrpc": "2.0", "id": 42, "method": "tools/call", "params": { "name": "long_operation", "arguments": {}, "_meta": { "progressToken": "job-42" } } }Do not substitute the JSON-RPC request ID for this field. The MCP schema reference dated November 25, 2025 defines
RequestIdandProgressTokenseparately: the request ID identifies the request-response exchange, while the progress token associates out-of-band notifications with the operation.Register local state under the token before dispatching the request. That state can hold the last accepted progress value, the supplied total, and the UI or log destination. Registering first prevents an early notification from arriving before the requestor has somewhere to route it.
-
Echo the same token from the receiver
When work advances, the receiver sends a JSON-RPC notification whose method is
notifications/progress. Itsparams.progressTokenmust be the original token from the active request:{ "jsonrpc": "2.0", "method": "notifications/progress", "params": { "progressToken": "job-42", "progress": 50, "total": 100, "message": "Processing the current batch" } }The notification has no request ID because it does not expect a response. The token is therefore the routing mechanism: the requestor looks up
job-42and updates only that operation. Never attach an unknown token to the most recently started request; concurrent requests make that guess unsafe.Send notifications only for a token supplied by an active request whose operation is still in progress. The receiver controls the notification frequency and may send none. Both parties should rate-limit progress traffic, so an inner loop should not emit a notification for every trivial unit of work. Fewer updates cost less transport and processing work, while more updates make visible movement more granular; MCP sets no fixed interval.
-
Choose one progress scale and keep it stable
Each
progressvalue must increase from the previous notification for that token. Repeating the same value or resetting it to zero for a new phase breaks the monotonic sequence.progressmay be an integer or floating-point number.Supply
totalonly when the receiver knows the denominator. When present, keep that total consistent across the operation. A changing denominator makes values such as50impossible to interpret reliably: the same progress value represents a different fraction whenever the total changes.Define the unit before emitting the first update. If the operation has a stable count of items,
progressandtotalcan use that count. If only relative advancement is available, use a stable scale. When the amount of work cannot be known without doing the work, omittotaland send increasing progress values instead. The cost is that the requestor cannot calculate a trustworthy percentage, but that is better than manufacturing one from an unstable estimate.Use the optional
messagefor short, human-readable context, such as the current phase. Do not use a phase change as a reason to reset the numeric value. For an operation such as automatic tagging through an MCP server, the message can distinguish analysis from writing while the numeric sequence continues to rise. -
Handle notifications without weakening timeout policy
On receipt, find the active operation by
progressToken, verify thatprogressis greater than the stored value, and retain the first suppliedtotalfor consistency checks. Update the operation’s visible status, then store the new progress value.A valid notification shows that work has advanced; it does not prove that the operation will finish. MCP implementations should set timeouts for sent requests. They may reset a request’s timeout clock when a matching progress notification arrives, but they should still enforce a maximum timeout even while updates continue. If that maximum expires, stop waiting and follow the in-flight MCP request cancellation flow.
Keep progress separate from diagnostic events. Progress answers “how far has this operation advanced?” A structured MCP logging notification carries diagnostic information instead. Mixing the two makes it harder for a requestor to present status without also parsing logs.
-
Stop notifications and release the token
Stop emitting progress notifications when the operation completes. The receiver must not report progress against a token whose request is no longer active or whose operation is no longer in progress. After the final result or error is handled, remove the token and its stored progress state; it no longer identifies active work.
Task-augmented requests have a longer boundary. Their original progress token continues to identify the task after
CreateTaskResulthas been returned. Use that same token throughout the task’s lifetime, then stop when the task reachescompleted,failed, orcancelled.
Expected result
A completed implementation sends progress only when the requestor supplied a unique token. Every notification echoes that token, routes to the correct concurrent operation, advances its progress value monotonically, and either omits the total or keeps it consistent. Notifications stop when the operation ends, while request timeouts and final responses remain independently enforceable.
Sources
- MCP lifecycle specification dated November 25, 2025modelcontextprotocol.io
- MCP progress utility dated November 25, 2025modelcontextprotocol.io
- MCP schema reference dated November 25, 2025modelcontextprotocol.io
See also
Define MCP prompt arguments, validate every prompts/get request, and use completion without treating suggestions as enforcement.
How MCP clients discover server-provided prompts, supply arguments, and receive role-tagged messages without invoking tools or selecting context.
Trace initialization, verify the HTTP version header, and stop cleanly when client and server share no supported MCP protocol version.
Implement bidirectional MCP cancellation, stop costly work, suppress cancelled responses, and handle completion races without protocol errors.