FastestMCP supports explicit progress reporting from handlers and background tasks.

This matters when work is slow enough that the caller should see incremental status instead of waiting for one final result:

  • long-running tools
  • background tasks
  • multi-step resource generation
  • workflows that need human-visible milestones

Two Ways To Report Progress

The low-level API is FastestMCP.Context.report_progress/4:

FastestMCP.Context.report_progress(ctx, 1, 3, "Fetched source data")

The higher-level API is the request-local progress helper:

progress = FastestMCP.Context.progress(ctx)
progress = FastestMCP.Progress.set_total(progress, 3)
progress = FastestMCP.Progress.increment(progress)
progress = FastestMCP.Progress.set_message(progress, "Fetched source data")

Use the helper when you want the runtime to track current state in one place. Use report_progress/4 when you already know the values and just want to emit them directly.

Only progress must strictly increase across notifications. total is optional, may be a floating-point value, and may be revised as the sender's estimate changes; it is not a fixed per-token contract. message is optional human-readable context.

Basic Example

alias FastestMCP.Context

server =
  FastestMCP.server("progress")
  |> FastestMCP.add_tool(
    "track",
    fn _arguments, ctx ->
      progress = Context.progress(ctx)

      progress = FastestMCP.Progress.set_total(progress, 3)
      progress = FastestMCP.Progress.increment(progress)
      progress = FastestMCP.Progress.set_message(progress, "Step 1")

      %{
        current: FastestMCP.Progress.current(progress),
        total: FastestMCP.Progress.total(progress),
        message: FastestMCP.Progress.message(progress)
      }
    end,
    task: true
  )

Immediate Calls vs Background Tasks

Progress behaves differently depending on the execution mode:

  • immediate calls can still use the progress helper for local state
  • background tasks persist progress onto the task record
  • connected clients can receive progress notifications while the task runs

That means the same handler code can work for both in-process tests and remote interactive clients.

Protocol progress tokens are owned by one active request. String and integer tokens remain distinct, duplicate active tokens are rejected, values must increase monotonically, and ordinary tokens are released when their request finishes. Task progress tokens remain live until the task reaches a terminal state. Unknown or late incoming progress is dropped.

Context.report_progress/4 returns explicit errors when a token is missing, inactive, decreasing, rate-limited, or cannot be delivered. The default outbound limit is 20 updates per second per token; incoming progress is bounded to 100 updates per second per session. Configure those runtime bounds with max_progress_per_second: and max_inbound_progress_per_second:. Client progress callbacks execute outside the session process.

Client-side Consumption

Connected clients can register a progress_handler:

client =
  FastestMCP.Client.connect!("http://127.0.0.1:4100/mcp",
    client_info: %{"name" => "docs-client", "version" => "1.0.0"},
    progress_handler: &IO.inspect/1
  )

This is the cleanest way to watch long-running work from integration tests or local tooling.

Progress and Background Task State

When progress is reported from a background task, FastestMCP stores it on the task itself. That means you can:

The stored progress includes:

  • current
  • the latest total, when one was reported
  • message
  • reported_at

Choosing Between Progress and Logging

Use progress when the caller needs structured task state.

Use Logging when the caller needs human-readable events that do not necessarily map to a numeric completion model.

In practice:

  • progress answers "how far along is this work?"
  • logs answer "what just happened?"

Why This Shape

FastestMCP treats progress as runtime state, not just output text.

The helper updates request-local state, task state, and client notifications in one place. That keeps background tasks, connected clients, and local handlers aligned instead of inventing one progress model per transport.