Run long tool calls as MCP Tasks
Some tools take minutes or hours: a build, a batch export, a job that waits for a person. With MCP Tasks, a server can answer a tool call with a task instead of blocking. The client then polls the task, answers any questions it asks, and reads the result when it is done.
DVARA, the AI governance platform, governs these long calls too. The DVARA MCP Gateway carries Tasks between your clients and your MCP servers. Each task stays inside its workspace, only the API key that started it can use it, and every step is policy-checked and written to the tamper-evident audit trail. A task has a limit on its calls, every change of its status is recorded, and you can stop it from Flightdeck or by killing the agent session that started it.
Not included in DVARA Open Source.
What are MCP Tasks?
In MCP 2026-07-28, Tasks are an extension, not part of the core protocol.
The extension's name is io.modelcontextprotocol/tasks. Like every MCP
extension, it is off until both sides ask for it.
- Only
tools/callcan return a task. The answer hasresultType: "task", ataskId, astatusand a lifetime (ttlMs). - The client uses three methods on the task:
tasks/getto poll it,tasks/updateto answer the questions it asks, andtasks/cancelto ask the server to stop. - There is no
tasks/list. A client keeps the task ids it was given.
When does the Gateway use Tasks?
A tool call can become a task only when all of these are true:
- The client is on MCP
2026-07-28and declares the Tasks extension on the request. - The workspace allows the extension (see Control which MCP extensions a client may use). A workspace policy can still deny it.
- The MCP server declares the extension, on
2026-07-28.
If any of these is false, the call runs as an ordinary tool call. A server that answers with a task anyway is refused. The client never receives a task it did not agree to.
To allow Tasks for every workspace, set the fleet default:
DVARA_MCP_GATEWAY_EXTENSIONS_ALLOWED=io.modelcontextprotocol/tasks
To allow it for one workspace only, add io.modelcontextprotocol/tasks on
Agents → MCP Extensions in Flightdeck.
Start a task
Declare the extension in the request's _meta:
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "ci__build",
"arguments": { "target": "release" },
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientCapabilities": {
"extensions": { "io.modelcontextprotocol/tasks": {} }
}
}
}
}
If the server decides the call will take a while, the answer is a task:
{
"resultType": "task",
"taskId": "dvt1.Q2xx…",
"status": "working",
"ttlMs": 600000,
"pollIntervalMs": 5000
}
The taskId is the Gateway's own id for the task, not the server's. Keep it.
Poll, answer and cancel
Send tasks/get, tasks/update or tasks/cancel with the task id in
params.taskId and in the Mcp-Name header. Declare the extension again in
_meta, as on every 2026-07-28 request.
curl -X POST https://gateway.example.com/mcp \
-H "Authorization: Bearer $DVARA_API_KEY" \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-H 'MCP-Protocol-Version: 2026-07-28' \
-H 'Mcp-Method: tasks/get' \
-H "Mcp-Name: $TASK_ID" \
-d '{"jsonrpc":"2.0","id":2,"method":"tasks/get","params":{"taskId":"'"$TASK_ID"'","_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientCapabilities":{"extensions":{"io.modelcontextprotocol/tasks":{}}}}}}'
- A task in
input_requiredlists its questions ininputRequests. The Gateway checks them as it checks any server question: requests to run a model are refused, and form questions follow the workspace's settings. Answer withtasks/updateandinputResponses. - A
completedtask carries the tool's result inresult. The Gateway scans that result for prompt injection and personal data, as it scans any tool result. tasks/cancelasks the server to stop. Stopping is up to the server, so the task may still finish.
Follow a task without polling
A client that declared the Tasks extension can ask to be told when a task
changes, with subscriptions/listen:
{
"jsonrpc": "2.0",
"id": 3,
"method": "subscriptions/listen",
"params": {
"notifications": { "taskIds": ["dvt1.Q2xx…"] },
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientCapabilities": {
"extensions": { "io.modelcontextprotocol/tasks": {} }
}
}
}
}
The answer is a stream. Its first message lists the task ids the Gateway will
follow. It follows only tasks this API key started that are still live, up to
16 per stream; any other id is left out without an error. Next comes each
task's current state, then a notifications/tasks message each time a task's
status changes, until the task is done or the stream ends. Each message carries
the same task that tasks/get would return.
The Gateway checks each task for the stream every 15 seconds
(DVARA_MCP_GATEWAY_SUBSCRIPTIONS_POLL_INTERVAL). Each check is a governed call,
like a poll from the client. The API key needs the mcp:call scope.
Who can use a task?
Only the API key that started the task can poll, update or cancel it. For an OAuth caller, it is the same user from the same issuer. Another key in the same workspace, a key in another workspace, or a changed id all get the same answer:
{ "code": -32602, "message": "Failed to retrieve task: Task not found" }
The answer is the same in each case on purpose, so no one can learn that someone else's task exists.
The Gateway never shows a client the server's own task id. Its ids are sealed:
they cannot be guessed or edited, and they work on every Gateway replica. They
stay valid for 7 days after the task was created. Change that with
DVARA_MCP_GATEWAY_TASKS_MAX_AGE (an ISO-8601 duration, such as P30D).
Task ids are sealed with a key derived from the audit signing secret
(DVARA_AUDIT_HMAC_SECRET). The Gateway relays Tasks only when that secret is
set. Changing the secret makes every open task id stop working, though the
tasks keep running on their servers.
Tasks that need approval
A workspace can require a person to approve calls to some tools or servers before they run (see the Approvals page in Flightdeck). When an approved call comes back as a task, the approval goes with that task:
- It covers this task only. The approval is tied to the one task its call started, and to that call's arguments. It cannot be used for another task, even one started again with the same arguments.
- No second approval for polls.
tasks/getandtasks/updateon the task are checked against the approval and go on at once. Nobody is asked again. - It ends with the task, or sooner. The approval ends when the task id
expires, when the lifetime the server first gave the task (
ttlMs) runs out, or after one day, whichever comes first. Change the day withDVARA_MCPGATEWAY_AGENTIC_APPROVAL_TASKMAXAGE(an ISO-8601 duration, such asPT12H). If it ends while the task is still running, the Gateway cancels the task. - Cancelling the task withdraws the approval. This includes a cancel from
the client, the kill switch, a cancel on the MCP Tasks page, or the
task's call limit. After the client cancels,
tasks/updateis refused, buttasks/getstill shows how the task ended. - It survives restarts. Every Gateway replica knows the approval, and so does Flightdeck's database. If every replica restarts while Flightdeck is down, calls on the task are refused until Flightdeck is back. They are never let through without a check.
A task that started before its tool needed approval is refused once the tool
needs it. tasks/cancel is never refused.
Limit and stop tasks
A task can run for days, so the Gateway governs the whole task, not only each call:
- A limit on calls. Each task may make 1,000 calls (polls, answers and
cancels, including the checks for a notification stream). Change it with
DVARA_MCP_GATEWAY_TASKS_MAX_CALLS. Each call also counts against the usual rate limits and budgets. - The kill switch stops tasks. When you kill an agent session in Flightdeck, the tasks that session started are cancelled too, even when the client polls them from another session.
- Loops are caught across the whole task. Sending a task the same answers again and again, or starting the same task again and again with the same key, is treated as a loop, however far apart the calls are and whichever session they come from. Polling a task is never a loop. See Loops in long-running tasks.
- Cancel from Flightdeck. The MCP Tasks page lists a workspace's live tasks with their status. A workspace admin can cancel the workspace's tasks. Platform owners and policy admins see every workspace's tasks and can cancel any of them. Developers and viewers can see the list but not cancel.
When the Gateway cancels a task, every Gateway replica stops serving it within
about 5 seconds. The next tasks/get answers with the task as cancelled and says
why:
{
"resultType": "complete",
"taskId": "dvt1.Q2xx…",
"status": "cancelled",
"statusMessage": "Cancelled by the gateway: an administrator cancelled this task."
}
tasks/update is refused. The Gateway also asks the server to stop the task,
once, the next time the client calls. Stopping is up to the server, so a task
whose client never calls again may run on at the server until its own lifetime
ends.
The Gateway replicas share what they know about each task, so a task's limit and status hold across replicas and survive the restart of any one replica. A cancel from Flightdeck survives every restart.
Loops in long-running tasks
Loop detection usually looks at one agent session, and forgets a session after an hour without calls. A task can run for days and be answered from any session, so the Gateway also keeps a history for each task and for the tasks each key starts. It uses the same rules and the same workspace settings as session loop detection (five repeats in a row, a cycle repeated three times, or more than 60 in a minute):
- The same answers to a task. Each
tasks/updateis compared with the earlier answers to that task, for as long as the task id is valid. The fifth identical answer in a row is refused withAGENT_LOOP_DETECTEDand is not sent to the server. The same answer to a new question is not a repeat. - The same task started again. Each new task is compared with the tasks the
same key started in the last six hours (same server, tool and arguments). A
daily job is not a loop. Change the window with
dvara.mcp-gateway.agentic.loop-detection.task-creation-window. - Polls are not repeats.
tasks/getnever counts, however often a client polls.
Every loop is recorded as AGENT_LOOP_DETECTED with the task's reference, so you
can find the task on the MCP Tasks page and cancel it. When the workspace
has auto-kill on, the Gateway also cancels the task, asks the server to stop it,
and kills the session that started it. A task started again in a loop then comes
back already cancelled:
{
"resultType": "task",
"taskId": "dvt1.Q2xx…",
"status": "cancelled",
"statusMessage": "Cancelled by the gateway: a loop was detected."
}
The histories are shared by every Gateway replica in a cluster, so a loop spread over replicas is caught at the same call.
Errors you may see
| Answer | Why |
|---|---|
-32021 Missing required client capability | The request did not declare the Tasks extension. |
-32601 Method not found | The workspace does not allow Tasks, the request is not on 2026-07-28, or the method is tasks/list. |
-32602 Task not found | The id is not one the Gateway gave this key in this workspace. |
-32602 Task has expired | The id is older than the allowed age. |
-32602 Task has been cancelled | The Gateway cancelled the task, and the request was tasks/update. |
AGENT_LOOP_DETECTED (HTTP 429) | The same answers were sent to this task too many times in a row. The answer was not sent. |
MCP_APPROVAL_NOT_BOUND | The task's tool needs approval, and no approval covers this task. |
MCP_APPROVAL_UNAVAILABLE | The Gateway could not check the task's approval. Try again once Flightdeck is reachable. |
MCP_APPROVAL_REVOKED | The task was cancelled, so its approval no longer covers a tasks/update. |
| HTTP 502 | The server sent a task the call did not agree to, or it no longer supports Tasks. |
What is recorded?
The audit log records each step of a task's life, without the answers a client sends:
MCP_TASK_CREATED: a server started a task.MCP_TASK_UPDATED: the client answered the task's questions. Only the question keys are recorded.MCP_TASK_CANCEL_REQUESTED: the client asked to cancel.MCP_TASK_REFUSED: a task request was refused, and why.MCP_TASK_STATUS_CHANGED: the task's status changed, from what to what. Recorded once per change, however many replicas see it.MCP_TASK_CANCELLED: the Gateway cancelled the task, and why: its session was killed, an administrator cancelled it, it used its calls, or it was looping.AGENT_LOOP_DETECTED: a loop in the task's answers, or the same task started again and again, with the task's reference.MCP_TASK_ADMIN_CANCELLED: someone cancelled the task in Flightdeck.MCP_APPROVAL_TASK_BOUND: an approval was tied to the task its call started.MCP_APPROVAL_TASK_USED: a poll or answer was checked against that approval.MCP_APPROVAL_TASK_EXPIRED: the approval ended while the task ran, and the Gateway cancelled the task.MCP_APPROVAL_TASK_REVOKED: the task was cancelled, so its approval was withdrawn.
Each approval event names both the approval and the task, so you can follow an approval from the person who gave it to every call made under it.
Each poll is also recorded as a governed MCP call, with the same rate limits, budgets and policies as a tool call.
Where to go next
- Govern MCP Apps and Tasks, the order to turn on MCP Apps, Tasks and sampling, and what to set first.
- Move MCP clients and servers to 2026-07-28