Kognita
All posts
Engineering2026-09-29·8 min read

Asynchronous MCP: using the tasks primitive for long-running AI jobs

Not every tool call returns in a second. Ingesting a corpus, running a deep analysis, generating a report — these need an async pattern. Here's how MCP's tasks primitive handles long-running work.

The default MCP tool call is synchronous: the client calls a tool, the server does the work, the result comes back. That model is perfect for fast operations — search the knowledge base, look up a record, fetch a document. It falls apart for work that takes minutes: ingesting a large document set, running a deep multi-step analysis, generating a big report. Holding a request open for minutes is fragile and often impossible. The async tasks pattern in MCP's roadmap addresses exactly this — long-running jobs that don't fit a single request/response. Here's how it works and when to reach for it.

asynchronous MCP tasks primitive

Why synchronous breaks down

A synchronous tool call assumes the work finishes within the lifetime of a single request. For fast operations that's fine. But long-running work breaks the assumption in several ways:

  • Timeouts. Requests time out — at the client, at proxies, at load balancers. A job that takes five minutes will hit some timeout along the path and fail, even though the work might have completed.
  • Blocking. A synchronous call ties up the connection (and often the model's turn) waiting. For interactive AI, blocking on a multi-minute operation is a terrible experience — the assistant is frozen.
  • No progress visibility. A synchronous call is opaque until it returns. For a long job, the user (and model) has no idea whether it's 10% done or stuck. No way to show progress, no way to cancel.
  • Fragility. If the connection drops mid-call, the result is lost even if the server finished the work.

Long-running AI work — exactly the kind agents increasingly want to trigger — needs a different shape.

The tasks pattern

The async tasks primitive decouples starting the work from getting the result:

  1. Start the task. The client calls the tool, but instead of blocking for the result, the server immediately returns a task handle (an ID) and accepts the job. The request returns fast; the work runs in the background.
  2. Track progress. The client can check the task's status — queued, running (with progress), completed, failed — by polling the handle or subscribing to updates. Now progress is visible: the assistant can tell the user "still processing, 60% done."
  3. Retrieve the result. When the task completes, the client fetches the result via the handle. If the connection dropped in between, no problem — the result is waiting, retrievable by ID.

This is the same durable pattern behind any well-built async system: hand back a reference immediately, do the work reliably in the background, let the caller reconcile later. It maps naturally onto job queues — which is exactly how systems like Kognita already run their heavy work.

Why this fits knowledge-base workloads especially

Knowledge-base operations split cleanly into fast and slow, and the slow ones are natural tasks:

  • Search is fast → synchronous. An agent calling search_knowledge_base wants results now, and gets them in one round trip.
  • Ingestion is slow → asynchronous. Adding a large document set means chunking, embedding, and storing potentially thousands of pieces — minutes of work. This is already an async, queue-backed background job in a well-designed system (Kognita queues ingestion via PGMQ and processes it out-of-band, returning a status you poll). Exposing that over MCP as a task is a natural fit: an agent starts ingestion, gets a handle, and checks back — rather than holding a connection open for the whole pipeline.
  • Deep analysis / report generation is slow → asynchronous. Multi-step reasoning over a whole corpus doesn't fit a single quick response.

The design lesson: expose your fast operations as synchronous tools and your slow operations as tasks. Trying to force a minutes-long job through a synchronous call is the mistake the tasks primitive exists to prevent.

Building async tools well

If you're implementing long-running MCP tools:

  • Return a handle immediately; never block on long work. The initial call should accept the job and hand back an ID fast.
  • Make status meaningful. Expose real states — queued, running with progress, completed, failed with a reason — so the client can inform the user and decide what to do. Opaque "still working" is barely better than blocking.
  • Make results retrievable by handle, durably. The whole point is surviving dropped connections and long waits. The result must be fetchable later, not tied to the original request's lifetime.
  • Support cancellation where it makes sense. A user who changes their mind about a big job should be able to stop it.
  • Reuse your existing job infrastructure. If you already run background work through a queue and worker (as most ingestion pipelines do), the MCP task layer is a thin interface over that — start enqueues, status reads the job state, result reads the output. You're exposing durable async work you already have, not building a new async system.

The takeaway

MCP's synchronous tool call is the right default for fast operations and the wrong tool for anything that takes minutes. The tasks primitive gives long-running AI work a proper shape: start-and-handle, trackable progress, durable retrievable results. For knowledge bases, the split is clean — search stays synchronous, ingestion and deep analysis become tasks backed by the job queue you already run. As agents take on heavier, longer operations, this async pattern is what keeps those operations reliable instead of timing out halfway through. Match the interaction model to the work: quick answers synchronously, long jobs as tasks.