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.

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.

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
  • total
  • 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.