Development Choices

Report progress for long-running MCP requests

Author
Joseph TrasattiMember of technical staff
Published
Section
MCP
Length
5 min read3 sources cited

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

An MCP request with a progress token receives progress notifications before the final result
The token joins progress updates to the request they describe.
  1. Create a token for the active request

    Generate a progressToken before 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.

  2. 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 RequestId and ProgressToken separately: 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.

  3. Echo the same token from the receiver

    When work advances, the receiver sends a JSON-RPC notification whose method is notifications/progress. Its params.progressToken must 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-42 and 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.

  4. Choose one progress scale and keep it stable

    Each progress value 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. progress may be an integer or floating-point number.

    Supply total only when the receiver knows the denominator. When present, keep that total consistent across the operation. A changing denominator makes values such as 50 impossible 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, progress and total can 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, omit total and 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 message for 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.

  5. Handle notifications without weakening timeout policy

    On receipt, find the active operation by progressToken, verify that progress is greater than the stored value, and retain the first supplied total for 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.

  6. 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 CreateTaskResult has been returned. Use that same token throughout the task’s lifetime, then stop when the task reaches completed, failed, or cancelled.

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

  1. MCP lifecycle specification dated November 25, 2025modelcontextprotocol.io
  2. MCP progress utility dated November 25, 2025modelcontextprotocol.io
  3. MCP schema reference dated November 25, 2025modelcontextprotocol.io

See also