Skip to main content
notifications/progress is a server → client notification. The client opts in by sending _meta.progressToken on the tools/call params; the server can then emit progress events as the work proceeds. GetMCP exposes an emitter API that custom tools and integrations can call. The built-in ToolExecutor is synchronous and doesn’t auto-emit progress — it’s straightforward request-response — so this is wired for tools you author yourself.

Opt-in From the Client

The client signals it wants progress updates by attaching a progressToken to its tools/call request:
{
  "jsonrpc": "2.0",
  "id":      7,
  "method":  "tools/call",
  "params": {
    "name":      "long_running_export",
    "arguments": { "table": "orders" },
    "_meta": { "progressToken": "export-abc123" }
  }
}
GetMCP captures the token and stores it on the request context. If the token is missing, the emitter is a no-op and zero traffic is generated.

Wire-level Shape (emitted by server)

{
  "jsonrpc": "2.0",
  "method":  "notifications/progress",
  "params": {
    "progressToken": "export-abc123",
    "progress":      42,
    "total":         100,
    "message":       "Processing batch 42 of 100"
  }
}
params.progressToken
string | integer
required
Echoes the token the client supplied on the originating request.
params.progress
number
required
Monotonically increasing counter. Same units as total when set.
params.total
number
Optional ceiling — clients render a percentage when both are present.
params.message
string
Optional human-readable status line (“Processed 42 of 100 orders”).

Emitting From PHP

use GetMCP\Protocol\ProgressEmitter;

ProgressEmitter::emit(
    $session_id,
    $progress_token,   // captured from the request context
    42,                // progress
    100,               // total (optional)
    'Processing batch 42 of 100' // message (optional)
);
The emitter pushes the notification onto the per-session outbound queue. The next HTTP response from this session will include the notification as part of a JSON-RPC batch. A typical pattern inside a custom tool’s loop:
foreach ( $batches as $i => $batch ) {
    process_batch( $batch );
    ProgressEmitter::emit( $session_id, $token, $i + 1, count( $batches ) );
}

Delivery Model

Because Streamable HTTP doesn’t keep an SSE channel open by default, progress notifications are queued server-side and delivered piggy-backed onto the next response. For long-running tool calls this means:
  • If the tool runs in a single HTTP request that takes 30 seconds, progress is queued but not delivered until the request completes — at which point all progress events arrive as a batch alongside the final result.
  • If the tool returns quickly and the client polls again, progress emitted between calls arrives on the next response.
The emitter API is forward-compatible: a future SSE GET handler can drain the same queue without any change to the emit call sites.
Progress notifications are informational. Clients are not required to render them. Always make sure your tool returns a final result regardless of whether progress was delivered — the protocol guarantees the result, not the progress events.