MCP Tasks¶
The platform implements the MCP Tasks extension, io.modelcontextprotocol/tasks
(specification, revision 2026-07-28).
A client that declares it is answered with a task for the calls that run long,
and follows the work with tasks/get and tasks/cancel instead of holding a
request open.
Which calls become tasks¶
| Tool | What the task is |
|---|---|
run_script |
The managed-script run itself. The task id is the run id, and nothing is stored beside it. See Following a run as an MCP task. |
trino_export |
The export, executed apart from the request on the replica that took the call. |
api_export |
The same. |
graphql_export |
The same. |
Every other tool is answered with its result, as before.
A call becomes a task only when the request itself declares the extension, in its per-request client capabilities:
{
"jsonrpc": "2.0", "id": 7, "method": "tools/call",
"params": {
"name": "trino_export",
"arguments": {"connection": "warehouse", "sql": "SELECT ...", "name": "orders", "format": "csv"},
"_meta": {
"io.modelcontextprotocol/clientCapabilities": {
"extensions": {"io.modelcontextprotocol/tasks": {}}
}
}
}
}
A request that does not declare it is answered exactly as before, whatever the session declared earlier: the extension forbids answering such a request with a task.
The server advertises the extension in its capabilities wherever a database is
configured and tasks.enabled is not false.
The flow¶
sequenceDiagram
participant C as Client
participant P as Platform
participant W as Work (export or run)
C->>P: tools/call (declares the extension)
P->>P: authorize, gates, consent
P->>W: start apart from the request
P-->>C: resultType "task", taskId, status "working"
loop every pollIntervalMs
C->>P: tasks/get {taskId}
P-->>C: status working, statusMessage
end
W-->>P: result
C->>P: tasks/get {taskId}
P-->>C: status completed, result inlined
- The call is authorized, gated and asked for consent before any task exists, so a refusal or a consent prompt is answered directly.
- The answer is a
CreateTaskResult:resultType: "task",taskId,status,statusMessage,createdAt,lastUpdatedAt,ttlMsandpollIntervalMs. The task exists in the database before the answer is sent, so atasks/getto any replica resolves it. tasks/getanswers with the task and, once it ends, its outcome: acompletedtask carries the tool's result underresult(a tool error included, withisError: true); afailedtask carries the JSON-RPC error undererror; acancelledtask carries neither.tasks/cancelasks the work to stop and acknowledges at once. A run stops ascancel_runstops it; an export stops at once on the replica running it, or at that replica's next heartbeat, within seconds, when another replica took the cancel. Work that finished first keeps its outcome.tasks/updateis acknowledged for a task the caller may follow. No task the platform runs asks the client for input.- There is no
tasks/listand notasks/result: the 2026-07-28 extension has neither.
Who may follow a task¶
A task is followed by whoever started it and by administrators; a run's task
also by the script's owner, the same rule as get_run. Everyone else, and a
request with no identity, is told the task does not exist (-32602). A
tasks/* request from a client that does not declare the extension is refused
with -32021, naming the capability. Task ids carry 128 random bits.
An export as a task¶
An export executed as a task runs on the replica that took the call, on a context that outlives the request. Progress and log notifications are not sent for it (the extension carries none for a task). Its audit row is written when it ends, with what it did, so the call catalog and the audit log record the export rather than the handle.
The task is a row in call_tasks. While the export runs, its replica stamps the
row every two seconds and reads in the same statement whether a cancel was asked
for. A working task whose replica stopped stamping for 30 seconds belongs to a
replica that is gone, and reads as failed with an error that says so; call the
tool again. On a shutdown the platform lets running exports finish within the
shutdown grace, and records the ones still running as failed.
Configuration¶
tasks:
enabled: true # default; false answers every call directly
retention_days: 7 # how long an export task is kept for its caller to read
A run's task lives as long as the run (scripts.run_retention_days). An export
task is deleted by the retention pass once retention_days have passed since it
was created; ttlMs on the task says when.