This episode dissects MCP Tasks, the official extension for long-running work: its journey from an experiment in the 2025-11-25 spec to an official extension in 2026-07-28, the tasks/get, tasks/update, and tasks/cancel primitives, the task lifecycle with its states and progress, and its integration with agentic workflow patterns.

In episode 11 you saw MCP Apps — tools that return interactive UI resources rendered by the host in a sandboxed iframe. Now it's the turn of its counterpart that gained official status in the same version: MCP Tasks. The problem it answers is simple but annoying: how does a model handle work that takes a long time — deploys, database migrations, video rendering — without leaving a single JSON-RPC request hanging for minutes?
This episode dissects Tasks' journey from an experiment in the 2025-11-25 spec to an official extension in the 2026-07-28 spec, the tasks/get, tasks/update, and tasks/cancel primitives, the task lifecycle with its states and progress, and how this pattern fits into the agentic workflows you build.
Before 2025-11-25 there was no standard way to model long-running work in MCP. Developers wrote their own polling, or abused the SSE stream to hold a connection open. In the 2025-11-25 spec, Tasks was first proposed as an experimental feature — okay to study, not yet a foundation for production.
The big change came in the 2026-07-28 spec: Tasks was moved out of the core document and formalized as an official extension. The consequences matter for you to understand:
Extension status also means clearer support boundaries: a host that doesn't support Tasks simply ignores its methods, and the server still works normally for ordinary tools and resources.
The Tasks extension adds three JSON-RPC methods on the server side. You're already familiar with the tools/list and resources/read patterns from episodes 4 and 5; Tasks follows the same idiom:
tasks/get — the server fetches a status snapshot of a task by ID. Used by clients for polling or checking the result when a task finishes.tasks/update — called by the server (as a notification) to report state or progress changes on a running task.tasks/cancel — asks the server to cancel an unfinished task. Best-effort: a server already in the middle of an unstoppable operation may refuse.A tasks/get request looks like this:
{
"jsonrpc": "2.0",
"id": 42,
"method": "tasks/get",
"params": {
"taskId": "task_7f3a91c2"
}
}The response carries the task status: the current state, progress (as a number and a message that can be shown to the user), and the result if it's finished. tasks/cancel is similar to tasks/get — it just sends a taskId — and returns the cancelled task along with the reason for cancellation.
Every task has a state that transitions over its lifetime. The common flow:
created --> running --> completed
| |
| +--> failed
+-----------+--> cancelledtasks/update notifications.tasks/get.tasks/cancel or by the server itself.Progress is sent via tasks/update notifications with a lightweight structure: taskId, state, progress (e.g. a percentage), and a short human-readable message like "Creating cluster in region ap-southeast-1". The host displays this to the user as an indicator. Notifications are one-way — the client doesn't need to reply — so the streaming load on the transport is much smaller than round-trip confirmation patterns.
Tasks are the natural place for an agent to request something long-running while staying responsive. Imagine this flow: a user asks to "deploy the application to staging". The agent calls the deploy tool, the server immediately creates a task and returns a taskId, and the agent can then:
tasks/get calls or wait for the final tasks/update notification.tasks/cancel and communicate the cancellation.This is complementary to MRTR from episode 8: MRTR handles back-and-forth conversations in the middle of a single call (e.g. user confirmation), while Tasks handles long-running work whose lifetime far exceeds a single request. Their combination enables interactive flows that don't hang — the key is in the design: a task must be checkpointable (state is stored), idempotent (safe to call again), and cancellable (willing to stop at a safe point).
The official TypeScript (and Python) SDKs already expose Tasks helpers. An example server with a simple Tasks extension:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
const server = new McpServer({ name: "deploy-server", version: "1.0.0" });
server.task("deploy", {
description: "Deploy aplikasi ke environment tertentu"
}, async (params, { task }) => {
task.update("Menyiapkan artefak", 10);
await build(params.artifact);
task.update("Upload ke registry", 55);
await upload(params.artifact);
task.update("Rolling deploy", 90);
await rollingDeploy(params.env);
return { message: "Deploy selesai" };
});The client calls the ordinary deploy tool; behind the scenes the SDK creates a task, emits tasks/update notifications through the task.update hook, and returns the result as content. What you must watch out for: don't store task state only in process memory — if the server restarts, the task loses its history. Store state in persistent storage if a task can outlive a single process.
Info
Don't confuse a tasks/get result with a tool return. A tool return (content) is the synchronous answer to a single call; a task result is the final product of long-running work with a state history. For short work that finishes immediately, just use a regular tool — Tasks adds state management overhead you don't need for operations under 1 second.
Episode 12 moves you from the world of synchronous tools to the world of long-running work. You saw Tasks progress from an experiment in the 2025-11-25 spec to an official extension in 2026-07-28, learned the tasks/get, tasks/update, and tasks/cancel primitives, understood the created through cancelled state machine, and how this pattern sits alongside MRTR to build interactive agentic workflows without hanging.
Key takeaways:
tasks/get to fetch status, tasks/update for progress notifications, tasks/cancel for best-effort cancellation.created to completed, failed, or cancelled, with progress broadcast via one-way notifications.In the next episode 13 we go down to the transport level: Transport Deep Dive — comparing Streamable HTTP with stdio, when to choose each, and how the process lifecycle works. See you there!