---
title: 'MCP Tasks Extension: A Working TypeScript Server (2026)'
description: The MCP Tasks extension defines three methods for async tool calls, but the official SDK still ships an older shape. Here is a tested server that gets it right.
url: https://nerdleveltech.com/mcp-tasks-extension-tutorial
language: en
date: '2026-09-18'
category: ai-ml
tags:
  - mcp
  - model context protocol
  - tasks extension
  - typescript
  - async tool calls
  - mcp sdk
  - agent tooling
markdown_url: https://nerdleveltech.com/mcp-tasks-extension-tutorial.md
attribution: 'Source: NerdLevelTech — https://nerdleveltech.com/mcp-tasks-extension-tutorial'
terms: https://nerdleveltech.com/llms.txt
---

# MCP Tasks Extension: A Working TypeScript Server (2026)

The MCP Tasks extension (`io.modelcontextprotocol/tasks`), ratified in the 2026-07-28 spec, lets a tool call return a durable handle instead of blocking: clients poll `tasks/get`, answer mid-task input via `tasks/update`, and cancel with `tasks/cancel`[^1][^2]. No official TypeScript SDK package implements that exact shape yet.

That gap isn't obvious from the docs. As of this writing, `@modelcontextprotocol/sdk`'s built-in task support still speaks an older, pre-ratification version, and its newly split v2 packages don't implement Tasks at all in their active wire vocabulary — they only keep that same older version around as dead, non-runtime types. This post builds a server against the ratified spec by hand, verifies it end to end against a real client, and shows exactly where the shipped SDK diverges.

## TL;DR

The ratified MCP Tasks extension has three methods — `tasks/get`, `tasks/update`, `tasks/cancel` — and no `tasks/list`, by design, for security reasons[^1]. `@modelcontextprotocol/sdk` v1.30.0's `experimental/tasks` module still implements an earlier shape with `tasks/list`, `tasks/result`, and no `tasks/update` at all[^3]; the new `@modelcontextprotocol/{core,server,client}` v2.0.0 packages don't implement any Tasks methods in their active wire vocabulary at all, and keep that same earlier shape around only as dead, non-runtime type exports[^4]. The one officially published package that does match the ratified spec, `@modelcontextprotocol/ext-tasks`, is requester-side only[^5]. This post hand-rolls a spec-correct server against the SDK's low-level `Server` class, runs it against a real client, and walks through a genuine bug the process caught: returning a stray `setTimeout` handle in a task record silently hangs the client, because the transport can't serialize it and never reports why.

## What you'll learn

- The exact wire shapes of `tasks/get`, `tasks/update`, and `tasks/cancel` per the ratified 2026-07-28 spec, including the `input_required` round trip
- Why `@modelcontextprotocol/sdk`'s own capability-gating code — not just its docs — proves it doesn't know about `tasks/update`
- How the split v2 SDK packages (`@modelcontextprotocol/core`, `/server`, `/client`) compare, and where `@modelcontextprotocol/ext-tasks` fits as the one ratified-spec-aligned package that exists today
- How to implement all three task methods by hand against the low-level `Server` class, including the `tasks` capability declaration the SDK enforces at runtime
- A real bug caught only by running the code: a non-serializable value in a task record hangs the client with no error on either side
- What a full task lifecycle looks like end to end, from `working` through `input_required` to `completed`, with real captured output

## What the Tasks extension actually specifies

Tasks are a Model Context Protocol extension, not a change to the core protocol — support is negotiated per request, and a server may return a task handle in place of a normal result for any request type the extension covers (currently just `tools/call`)[^1]. When a server decides to defer a call, it returns a `CreateTaskResult` — a normal-looking JSON-RPC result with `resultType: "task"` and a `Task` object embedded in it:

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "resultType": "task",
    "taskId": "786512e2-9e0d-44bd-8f29-789f320fe840",
    "status": "working",
    "createdAt": "2025-11-25T10:30:00Z",
    "lastUpdatedAt": "2025-11-25T10:40:00Z",
    "ttlMs": 60000,
    "pollIntervalMs": 5000
  }
}
```

From there the client owns three operations, and only three[^1]:

| Method | Purpose | Notable rule |
|---|---|---|
| `tasks/get` | Poll current status; terminal states inline their `result` or `error` directly in the response | No separate "fetch the result" call — `tasks/get` on a `completed` task returns the result itself |
| `tasks/update` | Answer a task's `input_required` state via `inputResponses`, keyed to the `inputRequests` the server sent | Acknowledgement is fire-and-forget; the client resumes polling `tasks/get` to see the effect |
| `tasks/cancel` | Signal intent to stop a task | Cooperative only — the server may finish anyway, and the client isn't obligated to wait for a `cancelled` status before discarding local state |

A `Task` moves through five possible statuses: `working`, `input_required`, `completed`, `cancelled`, `failed`[^1]. The spec is explicit that there is deliberately no `tasks/list`:

> "Because there is no `tasks/list`, a server cannot inadvertently leak the existence of one caller's tasks to another. This is an improvement over the `2025-11-25` tasks specification, in which a poorly-scoped list could expose unrelated task IDs."[^1]

That line matters for what follows, because the SDK code currently shipping still has a `tasks/list` method.

## What's actually shipping: three implementations, one spec

Before writing a line of server code, it's worth knowing what's actually installable today (checked 2026-09-18). There are three separate places Tasks logic lives in the TypeScript ecosystem, and they don't agree with each other.

**`@modelcontextprotocol/sdk@1.30.0`** — the long-running v1.x SDK line — ships an `./experimental/tasks` module. Installing it and reading the compiled `dist/esm/types.js` directly shows the literal JSON-RPC method strings it registers: `tasks/get`, `tasks/result`, `tasks/list`, `tasks/cancel`, and a notification method `notifications/tasks/status`[^3]. There is no `tasks/update` anywhere in the package. That's the `2025-11-25` draft shape the spec's own security section describes as superseded, not the ratified one.

**`@modelcontextprotocol/{core,server,client}@2.0.0`** — the newly split v2 line — tells a starker story than a surface grep for method strings suggests. The compiled output is organized into separate versioned "wire era" registries: a legacy `wire/rev2025-11-25/registry.ts` region, and an active `wire/rev2026-07-28/registry.ts` region that the SDK's live request dispatch actually consults. `tasks/get`, `tasks/list`, `tasks/result`, `tasks/cancel`, and `notifications/tasks/status` do still exist in the compiled output[^4] — but only inside the legacy registry, and the JSDoc directly above each one reads "`@deprecated 2025-11-25 wire vocabulary with no SDK runtime; kept importable for interoperability only`." The *active* 2026-07-28 registry — the one whose `requestMethodKeys`/`notificationMethodKeys` actually back the running SDK's dispatch — has zero `tasks/*` or `notifications/tasks*` entries, old shape or new[^4]. So v2 didn't just fail to catch up to the ratified three-method shape; its live wire vocabulary dropped Tasks entirely, and the old shape survives only as inert legacy types that no request handler ever sees. A GitHub issue filed at an MCP maintainer's request while v2 was still in beta confirms this wasn't an accident: "v2 removed experimental tasks per SEP-2663," pending a proper reimplementation once the extension's own package caught up[^6]. As of this writing, v2.0.0 is GA and neither its live registry nor its legacy one implements the ratified shape.

**`@modelcontextprotocol/ext-tasks@0.1.0`** is the one officially published `modelcontextprotocol` org package that does match. It's the official reference implementation of the ratified extension, published from the `modelcontextprotocol/ext-tasks` GitHub repository[^5]. Its `core/v2` module's compiled schema file contains exactly the ratified method set — `tasks/get`, `tasks/update`, `tasks/cancel`, `notifications/tasks` — confirmed by grepping the built JavaScript, not just reading the TypeScript source[^5]. But the package is requester-side only: its `client` entry point (`createTaskSessionFromClient`, `withTasks`) drives a task-augmented tool call from the client, and it explicitly requires an `@modelcontextprotocol/client` v2 `Client` instance, not the v1.x SDK's[^5]. There's no equivalent server-side helper published. Its separate `receiver` module binds to the same v2 `Client` type as the requester side, but is documented as handling `2025-11-25` Tasks requests — the old shape again, and for a different scenario (server-to-client sampling and elicitation, not `tools/call`).

Put together: if you want a server that speaks the ratified three-method Tasks extension today, no published package hands it to you. You implement it against the low-level protocol primitives, which is what the rest of this post does.

## Building the server by hand

The demo server below uses `@modelcontextprotocol/sdk@1.30.0`'s low-level `Server` class — the SDK's own type declarations mark it `@deprecated` with the note "Use `McpServer` instead for the high-level API. Only use `Server` for advanced use cases," which implementing a not-yet-supported extension qualifies as[^3]. The wire schemas are written from scratch against the spec text quoted above, not imported from `experimental/tasks`, since that module speaks the wrong methods.

```ts
// schemas.ts — Zod schemas transcribed directly from the ratified spec.
import { z } from "zod";

export const TaskStatusSchema = z.enum([
  "working", "input_required", "completed", "cancelled", "failed",
]);

export const TaskSchema = z.object({
  taskId: z.string(),
  status: TaskStatusSchema,
  statusMessage: z.string().optional(),
  createdAt: z.string(),
  lastUpdatedAt: z.string(),
  ttlMs: z.number().nullable(),
  pollIntervalMs: z.number().optional(),
});

export const CreateTaskResultSchema = TaskSchema.extend({
  resultType: z.literal("task"),
});

export const GetTaskRequestSchema = z.object({
  method: z.literal("tasks/get"),
  params: z.object({ taskId: z.string() }),
});

export const UpdateTaskRequestSchema = z.object({
  method: z.literal("tasks/update"),
  params: z.object({
    taskId: z.string(),
    inputResponses: z.record(z.string(), z.object({
      action: z.string(),
      content: z.record(z.string(), z.unknown()).optional(),
    })),
  }),
});

export const CancelTaskRequestSchema = z.object({
  method: z.literal("tasks/cancel"),
  params: z.object({ taskId: z.string() }),
});
```

(The full file also defines a matching `GetTaskResultSchema`, `UpdateTaskResultSchema`, and `CancelTaskResultSchema` for the three methods' responses, following the same pattern — trimmed here for space.)

The first runtime surprise shows up immediately when you try to register a handler for `tasks/get`. The low-level `Server`'s `setRequestHandler` calls an internal `assertRequestHandlerCapability(method)` check before it'll accept the registration, and its compiled source hardcodes exactly which methods that check recognizes:

```js
// from @modelcontextprotocol/sdk@1.30.0's compiled server/index.js
case 'tasks/get':
case 'tasks/list':
case 'tasks/result':
case 'tasks/cancel':
    if (!this._capabilities.tasks) {
        throw new Error(`Server does not support tasks capability (required for ${method})`);
    }
    break;
```

Two things fall out of that switch statement. First, you must declare `capabilities: { tools: {}, tasks: {} }` when constructing the `Server`, or registering a `tasks/get` or `tasks/cancel` handler throws immediately. Second, `tasks/update` is not in that list at all — the SDK's capability gate has no concept of it, so registering a handler for it neither requires nor benefits from the `tasks` capability flag. That gap by itself is a working proof that the shipped SDK's task-capability plumbing was built against the pre-`tasks/update` shape.

```ts
// server.ts — advertise tools + tasks, register the standard tool methods,
// then register the three Tasks methods by hand.
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { ListToolsRequestSchema, CallToolRequestSchema } from "@modelcontextprotocol/sdk/types.js";
import { GetTaskRequestSchema, UpdateTaskRequestSchema, CancelTaskRequestSchema } from "./schemas.js";

const server = new Server(
  { name: "tasks-demo-server", version: "0.1.0" },
  { capabilities: { tools: {}, tasks: {} } },
);
```

The tool itself, `summarize_report`, always defers to a task when the caller declares support for the extension, and falls back to an ordinary synchronous result otherwise — the spec is explicit that "a server **MUST NOT** return `CreateTaskResult` to a client that did not include the extension capability on its request"[^1]:

```ts
function clientDeclaredTasksCapability(meta: unknown): boolean {
  if (!meta || typeof meta !== "object") return false;
  const caps = (meta as Record<string, unknown>)["io.modelcontextprotocol/clientCapabilities"];
  const extensions = caps && typeof caps === "object" ? (caps as any).extensions : undefined;
  return !!extensions && "io.modelcontextprotocol/tasks" in extensions;
}

server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const wantsTasks = clientDeclaredTasksCapability((request.params as any)._meta);
  if (!wantsTasks) {
    return { content: [{ type: "text", text: "Summary: (synchronous fallback)" }] };
  }

  const taskId = crypto.randomUUID();
  const created = new Date().toISOString();
  const record = {
    taskId, status: "working" as const,
    createdAt: created, lastUpdatedAt: created,
    ttlMs: 5 * 60 * 1000, pollIntervalMs: 500,
  };
  tasks.set(taskId, record);
  scheduleWork(taskId, (request.params.arguments as any)?.simulateFail); // simulated background work; see below for a bug it exposed
  return { resultType: "task", ...record };
});
```

## The client, and the poll loop

The client declares the extension the same way, in `_meta` on the `tools/call` request, then drives its own poll loop against `tasks/get` — honoring `pollIntervalMs` and switching behavior on `status`:

```ts
const initial = await client.request(
  {
    method: "tools/call",
    params: {
      name: "summarize_report",
      arguments: { reportId },
      _meta: {
        "io.modelcontextprotocol/clientCapabilities": {
          extensions: { "io.modelcontextprotocol/tasks": {} },
        },
      },
    },
  },
  CallToolOrTaskResultSchema,
);

if (initial.resultType !== "task") { /* handle the synchronous path */ }

let { taskId, pollIntervalMs = 1000 } = initial;
while (true) {
  await sleep(pollIntervalMs);
  const status = await client.request(
    { method: "tasks/get", params: { taskId } },
    GetTaskResultSchema,
  );
  pollIntervalMs = status.pollIntervalMs ?? pollIntervalMs;

  if (status.status === "input_required") {
    await client.request(
      {
        method: "tasks/update",
        params: {
          taskId,
          inputResponses: {
            redaction_level: { action: "accept", content: { level: "partial" } },
          },
        },
      },
      UpdateTaskResultSchema,
    );
    continue;
  }
  if (status.status === "completed") { console.log(status.result); break; }
  if (status.status === "failed") { console.log(status.error); break; }
  if (status.status === "cancelled") break;
}
```

## The input_required round trip

Much of the existing coverage of Tasks stops at the simple case — create a task, poll it, get a result. The more useful part of the spec, and the part that actually distinguishes Tasks from a plain "poll until done" pattern, is `input_required`: a server can pause a task mid-flight, surface one or more outstanding requests in `inputRequests`, and wait for the client to answer them through `tasks/update` before resuming[^1]. The demo server exercises this deliberately: `summarize_report`'s simulated work pauses after ~1.5 seconds to ask which PII redaction level to apply, using the same `elicitation/create` shape a direct elicitation request would use:

```json
{
  "status": "input_required",
  "inputRequests": {
    "redaction_level": {
      "method": "elicitation/create",
      "params": {
        "mode": "form",
        "message": "Report Q3-vendor-audit: how should PII be redacted in the summary?",
        "requestedSchema": {
          "type": "object",
          "properties": { "level": { "type": "string", "enum": ["none", "partial", "full"] } },
          "required": ["level"]
        }
      }
    }
  }
}
```

The client answers with a matching key in `inputResponses` on a `tasks/update` call. The acknowledgement is intentionally empty and eventually consistent — the spec allows the server to accept the response and ack before the task's polled status reflects it — so the client's only correct move is to keep polling `tasks/get`, not to treat the `tasks/update` response itself as confirmation that anything changed[^1].

## Real output

Running the client against the server end to end, with no mocking, produces this (redaction level `partial`, then a second call that deliberately exercises the `failed` path):

```text
--- calling summarize_report(reportId=Q3-vendor-audit, simulateFail=false) ---
Task created: e08594eb-1dd4-4066-b120-d35b442513db (status=working, pollIntervalMs=500)
  poll 1: status=working
  poll 2: status=working
  poll 3: status=input_required
  server needs input: {
  mode: 'form',
  message: 'Report Q3-vendor-audit: how should PII be redacted in the summary?',
  requestedSchema: { type: 'object', properties: { level: [Object] }, required: [ 'level' ] }
}
  sent tasks/update with redaction level = partial
  poll 4: status=working
  poll 5: status=completed
  completed. result: {
  "content": [
    { "type": "text", "text": "Summary ready (redaction=partial): report is 3 pages, 2 action items, 1 blocked dependency." }
  ],
  "isError": false
}

--- calling summarize_report(reportId=Q3-vendor-audit-2, simulateFail=true) ---
Task created: 41652027-6ec0-43cb-b48b-074319c2bb1c (status=working, pollIntervalMs=500)
  poll 1: status=working
  poll 2: status=working
  poll 3: status=failed
  failed: { code: -32603, message: 'Upstream summarizer timed out' }
```

A separate run also confirmed the two edge cases the spec calls out explicitly: a client that omits the capability declaration gets the plain synchronous fallback result, never a `CreateTaskResult`[^1], and sending `tasks/cancel` against a `working` task moves it to `cancelled` on the next poll, consistent with cancellation being cooperative rather than guaranteed[^1].

## The bug that will hang your client silently

The first version of the server's tool handler stored each task's `setTimeout` handle directly on the same object it returned to the client, to make cleanup on cancellation easy:

```ts
const record = { taskId, status: "working", /* ...*/ };
tasks.set(taskId, record);
record._timer = setTimeout(() => { /* advance the task */ }, 1500);
return { resultType: "task", ...record }; // _timer included by the spread
```

Running the client against this version didn't throw an error on either side — it just hung forever on the first `tools/call`. The server's handler ran and returned successfully; the client never received a response. The cause: a `NodeJS.Timeout` object contains internal circular references, so serializing it as part of the JSON-RPC result fails inside the transport's write path, and that failure isn't surfaced back to the request that triggered it. The fix is to destructure the non-serializable field out before returning:

```ts
const { _timer, ...publicRecord } = record;
return { resultType: "task", ...publicRecord };
```

This is disclosed here because it's exactly the kind of failure mode that's invisible from reading documentation and only shows up by actually running task-augmented code against a real client — which none of the current SDK-vs-spec writeups this post found seem to have done.

## Three implementations, side by side

| | `sdk` v1.30.0 `experimental/tasks` | `core`/`server`/`client` v2.0.0 (built in) | `ext-tasks` v0.1.0 `core/v2` |
|---|---|---|---|
| Matches ratified 2026-07-28 spec | No — `2025-11-25` shape | No — active wire registry has no Tasks methods at all[^4] | Yes[^5] |
| Methods present | `tasks/get`, `tasks/list`, `tasks/result`, `tasks/cancel`[^3] | None active — the same four exist only as deprecated, non-runtime legacy types[^4] | `tasks/get`, `tasks/update`, `tasks/cancel`[^5] |
| `tasks/update` / `input_required` support | No | No | Yes, client-side |
| Server-side helper published | Yes (wrong shape) | No — legacy helper types exist but carry no SDK runtime[^4] | No — requester-side only |
| Usable today for a ratified-spec server | No | No | No (client only) |

## Verification

Every version number, method name, and capability-gating behavior in this post was confirmed by installing the actual packages and reading their compiled output, not by reading documentation alone. `@modelcontextprotocol/sdk@1.30.0` was resolved as `npm`'s current `latest` tag at the time of writing; its `dist/esm/types.js` and `dist/esm/server/index.js` were grepped directly for the JSON-RPC method literals and the capability-gating switch statement quoted above. `@modelcontextprotocol/core@2.0.0`, `/server@2.0.0`, and `/client@2.0.0` were installed the same way and grepped for the same strings, which led to reading the surrounding compiled code directly — that's what surfaced the two separate wire-era registries and the `@deprecated ... no SDK runtime` JSDoc on the legacy one. `@modelcontextprotocol/ext-tasks@0.1.0` was installed and its `core/v2`, `client`, and `receiver` `.d.ts` and compiled `.js` files were read directly to confirm which methods and which client type each entry point targets. The GitHub issue on the v2 beta's task removal was read in full, not summarized secondhand.

The server and client code shown above was written to a real project (Node.js v22.23.2, TypeScript 7.0.2, tsx 4.23.13, Zod 4.6.5), type-checked with `tsc --strict --noEmit`, and executed end to end over a real stdio transport between two separate processes — not mocked. Every status transition shown in the "Real output" section, including the `input_required`/`tasks/update` round trip, the `completed` and `failed` terminal states, the synchronous-fallback path, and cancellation, was exercised by running the code and capturing its actual console output, reproduced above with only whitespace reflowed for line width. The `_timer` serialization bug was found this way, not invented for the post.

## FAQ

**What are the three methods in the MCP Tasks extension?**
`tasks/get` (poll status, with the result or error inlined once terminal), `tasks/update` (answer an `input_required` request), and `tasks/cancel` (request cancellation). There is deliberately no `tasks/list`[^1].

**Does the official MCP TypeScript SDK support the ratified Tasks extension?**
Not as of this writing. `@modelcontextprotocol/sdk@1.30.0`'s `experimental/tasks` module still implements the pre-ratification `2025-11-25` shape. The newly split `@modelcontextprotocol/core`/`server`/`client@2.0.0` packages go further in the other direction: their active wire vocabulary implements no Tasks methods at all, keeping the `2025-11-25` shape only as dead type exports explicitly marked "no SDK runtime." Confirmed by inspecting their compiled JSON-RPC method literals and registry code directly[^3][^4].

**Is there any official package that implements the ratified spec?**
`@modelcontextprotocol/ext-tasks@0.1.0`'s `core/v2` and `client` modules match the ratified three-method shape, but the package is requester-side (client) only — there's no published server-side helper for it yet[^5].

**What happens if a client calls a task-augmented tool without declaring the extension capability?**
Per spec, the server must not return a `CreateTaskResult` in that case. In this post's demo server, the tool instead returns an ordinary synchronous result[^1].

**Is `tasks/cancel` guaranteed to stop a task?**
No. Cancellation is cooperative: the server acknowledges the request but decides whether and when to actually stop the work, and the task may still reach a terminal status other than `cancelled`[^1].

## Bottom line

The ratified MCP Tasks extension is a clean three-method design — `tasks/get`, `tasks/update`, `tasks/cancel` — with a real, useful mid-task input flow. What isn't yet true, as of September 2026, is that installing the official SDK gets you an implementation of it: the stable v1.x line still speaks the extension's earlier, pre-ratification shape, the new v2 packages don't speak any version of it in their active wire vocabulary at all, and the one officially published package that does match the ratified spec only covers the client side. Building a compliant server today means going around the SDK's `experimental/tasks` helpers and implementing the three methods directly against the documented wire format — which, once you know the capability-gating switch statement only recognizes the old method names, is a few dozen lines of code, not a framework.

## Related reads

- [MCP Tasks Extension: A Working Python Server (2026)](https://nerdleveltech.com/mcp-tasks-extension-python-tutorial) — the same gap, checked fresh against the Python SDK: same missing implementation, plus a client-side bug this TypeScript post didn't hit
- [MCP Server in TypeScript: OAuth 2.1 + Streamable HTTP (2026)](https://nerdleveltech.com/mcp-server-typescript-oauth-streamable-http-production-tutorial)
- [MCP Goes Stateless: The 2026-07-28 Spec Explained](https://nerdleveltech.com/mcp-stateless-protocol-enterprise-authorization)
- [MCP Servers Explained: Claude's New AI Backbone](https://nerdleveltech.com/mcp-servers-explained-claudes-new-ai-backbone-for-real-automation)
- [AI SDK Tool Drift Detection: What It Misses in 2026](https://nerdleveltech.com/ai-sdk-tool-drift-detection-mcp-rug-pull) — pinning MCP tool definitions against a rug pull, and the two fields the built-in fingerprint leaves out
- [MCP Events Tutorial: Build a Webhook Server (2026)](https://nerdleveltech.com/mcp-events-webhook-server-tutorial) — the draft extension for server-to-client event notifications, with its webhook mode built by hand and tested, since the SDK releases I checked on 2026-09-30 don't ship it

[^1]: Model Context Protocol, "Tasks" (MCP Tasks Extension, spec version 2026-07-28) — https://tasks.extensions.modelcontextprotocol.io/specification/draft/tasks (three methods, `Task` status enum, capability negotiation, `input_required`/`inputRequests`/`inputResponses` flow, security considerations including the removal of `tasks/list`, cancellation semantics; fetched 2026-09-18)
[^2]: Model Context Protocol, "Specification" (2026-07-28) — https://modelcontextprotocol.io/specification/2026-07-28 (lists Tasks as an official extension of the 2026-07-28 spec release; fetched 2026-09-18)
[^3]: `@modelcontextprotocol/sdk` v1.30.0 on npm — https://registry.npmjs.org/@modelcontextprotocol/sdk/latest (resolved as `latest`; `experimental/tasks` export path, method literals `tasks/get`/`tasks/result`/`tasks/list`/`tasks/cancel`/`notifications/tasks/status` confirmed in compiled `dist/esm/types.js`; capability-gating switch statement confirmed in `dist/esm/server/index.js`; `Server` class documented `@deprecated`; installed and inspected directly, 2026-09-18)
[^4]: `@modelcontextprotocol/core`, `@modelcontextprotocol/server`, `@modelcontextprotocol/client`, all v2.0.0 on npm (installed and inspected directly, 2026-09-18) — compiled `dist` output contains two versioned wire-era registries; the `wire/rev2025-11-25/registry.ts` region defines `tasks/get`, `tasks/cancel`, `tasks/list`, `tasks/result`, and `notifications/tasks/status`, each documented `@deprecated 2025-11-25 wire vocabulary with no SDK runtime; kept importable for interoperability only`; the active `wire/rev2026-07-28/registry.ts` region's `requestMethodKeys`/`notificationMethodKeys` — which the SDK's live dispatch consults — contain no `tasks/*` or `notifications/tasks*` entries at all; `tasks/update` is absent from both registries
[^5]: `@modelcontextprotocol/ext-tasks` v0.1.0 on npm, reference implementation from https://github.com/modelcontextprotocol/ext-tasks (based on SEP-2663; Apache-2.0; installed and inspected directly, 2026-09-18) — `core/v2/schemas.js` method literals `tasks/get`/`tasks/update`/`tasks/cancel`/`notifications/tasks` confirmed matching the ratified spec; `client` entry point requires `@modelcontextprotocol/client` (v2); `receiver` entry point documented as handling `2025-11-25`-era sampling/elicitation task requests
[^6]: GitHub, `modelcontextprotocol/modelcontextprotocol` issue #3051, "docs: update or freeze SEP-1686 tasks code examples for the v2 SDK" — https://github.com/modelcontextprotocol/modelcontextprotocol/issues/3051 (filed 2026-07-08 at maintainer David Soria Parra's request; states TypeScript SDK v2 was then at `2.0.0-beta.2` as split `@modelcontextprotocol/{client,server,core,node,express,hono,server-legacy}` packages and that "v2 removed experimental tasks per SEP-2663"; fetched 2026-09-18)
