Early bird discounts live! Claim your offer

Async Jobs

How long-running MCP tool calls return immediately and let clients poll for completion.

Some MCP operations — today, only bulk_generate_access_codes — take longer than the 30-second budget a short-lived HTTP request can hold. Those tools return a job_id immediately and the client polls get_job_status for completion.

The pattern

  1. The client invokes a long-running tool — for example bulk_generate_access_codes with a batch of several hundred codes.
  2. The server enqueues the work and returns immediately:
{
  "data": {
    "job_id": "0a4e7b96-c358-4d12-9f6b-25a8013ce74f",
    "status": "queued",
    "enqueued_at": "2026-05-18T10:05:00Z",
    "plan_id": "c4e82f16-93a7-4d5b-b81c-6e0f27a94d3b",
    "quantity": 250,
    "preview": {
      "free_remaining": 120,
      "chargeable_quantity": 130,
      "will_bill_overage": true
    }
  }
}

preview is there so an agent can tell a human what the batch will cost before the work runs. See bulk_generate_access_codes.

  1. The client periodically calls get_job_status with the job_id:
{
  "data": {
    "job_id": "0a4e7b96-c358-4d12-9f6b-25a8013ce74f",
    "tool_name": "bulk_generate_access_codes",
    "status": "completed",
    "result": { "count": 250 },
    "error": null,
    "started_at": "2026-05-18T10:05:01Z",
    "completed_at": "2026-05-18T10:05:04Z"
  }
}

The envelope is the same for every async tool; result is whatever the tool that enqueued the work returns, so read it against that tool's own page.

  1. When status becomes completed or failed, the caller reads result or error accordingly.

Every job reaches one of those two. A worker that gives up after its retries records the failure on the row rather than leaving it queued, and the guard clauses that abandon a batch early — a deleted plan, a project with no access-code payment method, a deleted user — each say so in error.message.

Status values

StatusMeaning
queuedJob enqueued but not yet picked up by a worker.
runningWorker started executing the job.
completedJob finished successfully. data.result carries the output.
failedJob failed. data.error carries a standard error envelope.

Suggested polling cadence

  • First 10 seconds: poll every second.
  • Next 50 seconds: poll every 5 seconds.
  • After 1 minute: poll every 30 seconds.

Jobs typically complete in well under a minute.

Persisting across sessions

Job IDs are stable. An agent that loses its conversation context can still poll get_job_status from a new session as long as the token owner matches — the job row is scoped to the token's team the same as any other read.

Which tools are async

Only bulk_generate_access_codes enqueues an async job today. Every other tool runs synchronously. The tool's description (surfaced at MCP handshake time) states whether the tool returns a job_id.

How is this guide?

On this page

Subscriby is a product designed by you — for you.

No boardroom full of executives deciding what we ships next. Our roadmap always shaped by you with your feedback.

Share feedback or a request