TanStack
MCP

MCP Server Tasks

Your long tool holds the HTTP call until the work ends. You have no task id for the host to poll.

Set execution: 'task' on toolDefinition.

The handler returns a task handle before the work ends. The tool function continues in this process. There is no queue.

ts
import { toolDefinition } from '@tanstack/ai'
import { createMCPServer, inMemoryTaskStore } from '@tanstack/ai-mcp/server'

const taskStore = inMemoryTaskStore()

const buildReport = toolDefinition({
  name: 'build_report',
  description: 'Build a long report',
  execution: 'task',
}).server(async () => {
  const text = await writeReport()
  return { text }
})

async function writeReport() {
  await new Promise((resolve) => {
    setTimeout(resolve, 5000)
  })
  return 'Quarterly report'
}

export default {
  async fetch(
    request: Request,
    _env: unknown,
    ctx: { waitUntil(promise: Promise<unknown>): void },
  ) {
    const server = createMCPServer({
      name: 'reports',
      version: '1.0.0',
      tools: [buildReport],
      taskStore,
      waitUntil: (promise) => ctx.waitUntil(promise),
    })
    return server.fetch(request)
  },
}

Your tool function returns the final value. When the tool function ends, the store saves that value.

What a task tool can use

The task keeps running after the call returns the task handle. So the task does not use the request that started it.

  • ctx.context.requestInput throws an error. Ask for input in a tool without execution: 'task'.
  • ctx.context.sample calls the sample option of createMCPServer. If you do not pass sample, ctx.context.sample throws an error.
  • ctx.abortSignal does not abort when the call request ends.

Keep the Worker alive

If you run on a Worker, pass waitUntil from the Worker context.

The server calls waitUntil with the tool promise. After the store saves the tool result or the tool error, this promise settles.

If you omit waitUntil, a Worker can stop before the tool function ends. On one long-lived process, you can omit waitUntil.

Keep the task record

  1. Create the store with inMemoryTaskStore outside fetch.
  2. Pass this store as taskStore.

A new store on each request has no record of the old task.

The package @tanstack/ai-mcp/server exports inMemoryTaskStore. The default store keeps each task in memory for this process. If you build the server once, you can omit taskStore.

If more than one instance can receive tasks/get, pass a taskStore that those instances share.

  • get: read one task by id.
  • set: save one task.
  • delete: remove one task.

Spec 2025 and spec 2026

Tasks are part of spec 2025-11-25. Spec 2026-07-28 has no tasks yet.

SpecWhat the call returns
2025-11-25A task handle. The host polls tasks/get, then reads tasks/result.
2026-07-28The tool result. The tool runs inline, and the call waits for it.

On spec 2025, the server sets execution.taskSupport to required, and advertises the tasks capability for tools/call.

Read the result

On spec 2025, the call response is the CreateTaskResult from the spec. The task field is a Task with status working. This status means that the tool function is still at work.

json
{
  "content": [{ "type": "text", "text": "task-id" }],
  "task": {
    "taskId": "task-id",
    "status": "working",
    "ttl": null,
    "createdAt": "2026-09-22T12:00:00.000Z",
    "lastUpdatedAt": "2026-09-22T12:00:00.000Z"
  }
}

tasks/get returns the same Task fields:

  • taskId: the id from the call.
  • status: working, completed, or failed.
  • ttl: null.
  • createdAt: the time of the first set.
  • lastUpdatedAt: the time of the last set.
  • statusMessage: the error text, when the status is failed.

tasks/result wraps the value as content. When the value is an object, tasks/result also returns structuredContent. If the status is not completed, tasks/result returns the error Task result is not ready.

A task uses one status:

  • working: the tool function is still at work.
  • completed: the store holds the tool result.
  • failed: the tool function threw an error.

If the tool function throws, the task keeps the text from that error in statusMessage. If that error has no text, the task keeps The tool failed.

An unknown task id on tasks/get returns the error Task not found. When the server has auth, a task belongs to the caller that started it: the clientId of the token plus its sub claim. Another caller also gets Task not found. MCP Server Auth shows how the verifier sets them.

The host polls until the status is completed or failed. A TanStack AI host polls until the task ends.

See MCP Server Tools.

After the status is completed, the host reads { text: 'Quarterly report' } from structuredContent.