tasks
Tasks extension (io.modelcontextprotocol/tasks, SEP-2663).
Tasks let a server defer the result of a tools/call: instead of blocking for the
CallToolResult, the server immediately returns a CreateTaskResult carrying a
task id, and the client polls tasks/get for status and the eventual result.
SEP-2663 (https://modelcontextprotocol.io/seps/2663-tasks-extension.md) is an
opt-in extension, wire-incompatible with the 2025-11-25 in-core Tasks design that
still ships (types-only) in mcp_types. The SEP-2663-shaped wire models live in
mcp.shared.tasks (re-exported here); this module is the server runtime.
Key SEP-2663 rules implemented here:
- The SERVER decides task augmentation, per request, at its discretion (the
Tasks(augment=...)predicate). The legacyparams.taskfield is ignored (it is not the opt-in). - A
CreateTaskResultis only returned to a client that declared the extension on the request; atasks/*call from a modern client that did not declare it is rejected withMISSING_REQUIRED_CLIENT_CAPABILITY(-32021), and a legacy (<= 2025-11-25) call getsMETHOD_NOT_FOUND-- the extension is not defined on that wire. CreateTaskResultisResult & Taskflat, withresultType: "task".tasks/getreturns the task (resultType: "complete"), inlining the originalCallToolResulton acompletedtask or the JSON-RPC error on afailedone -- never both. A tool result withisError: trueis acompletedtask;failedis reserved for JSON-RPC errors.- A multi round-trip interim (
resultType: "input_required") is passed through un-augmented: SEP-2663 resolves MRTR exchanges on the originaltools/callbefore task creation, so only the leg that produces the final result becomes a task. tasks/cancelandtasks/updateare empty acknowledgements (resultType: "complete"is required on the ack). Cancellation is cooperative and may never take effect; updates for input requests that were never issued are ignored. Both are no-ops here by construction (see below).
Execution model: the tool runs to completion inside the interceptor, so a task is
born terminal, in {completed, failed} -- SEP-2663 allows any initial status
(the embedded task is "typically (though not necessarily)" working). A chain
that produces a result -- isError: true included -- records a completed task.
A chain that raises a JSON-RPC error (or a nested interceptor that returns
ErrorData) records a failed task carrying that error, and the declaring
client receives a failed CreateTaskResult instead of the JSON-RPC error;
tasks/get then inlines the error. A task exists only once its outcome exists:
there is no working state to corrupt and no terminal transition to guard, so
cancellation can still never take effect (terminal statuses are absorbing --
unchanged invariant). Errors propagate untouched on every non-augmented path: a
non-declaring client, a legacy connection, or an augment predicate that
excluded the call. Background execution (returning working tasks), the in-task
input_required/inputResponses loop over tasks/update, and
notifications/tasks over subscriptions/listen are deferred follow-ups, each
needing deeper SDK plumbing. (SEP-2663's Mcp-Name: <taskId> routing header --
the SEP-2243 header family -- is already handled by the shared header table in
mcp.shared.inbound.)
Task ids are unguessable bearer capabilities: any caller presenting a valid id
may poll the task. That is deliberate -- the modern wire has no sessions, and a
reconnecting client must be able to poll. Servers that need stricter scoping,
bounded retention without TTLs, or durable multi-worker storage supply their own
store via Tasks(store=...); the in-memory default is per-process.
EXTENSION_ID
module-attribute
EXTENSION_ID = 'io.modelcontextprotocol/tasks'
The Tasks extension identifier (SEP-2663).
CreateTaskResult
Bases: _CarriesTtlMs, Result
Result & Task flat, discriminated by result_type: "task" (SEP-2663).
Inherits Result's camelCase alias generator, so snake_case fields serialize
to resultType/taskId/ttlMs/... on the wire.
Source code in src/mcp/shared/tasks.py
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 | |
Task
Bases: _CarriesTtlMs, _TasksModel
SEP-2663 task snapshot (note the *Ms field names, unlike the 2025 design).
Source code in src/mcp/shared/tasks.py
72 73 74 75 76 77 78 79 80 81 | |
TaskStatus
module-attribute
TaskStatus = Literal[
"working",
"input_required",
"completed",
"failed",
"cancelled",
]
SEP-2663 task statuses.
Clock
module-attribute
Returns the current time as an aware UTC datetime (injectable for determinism).
TaskRecord
dataclass
What a TaskStore persists for one task.
task is the wire snapshot; the outcome rides beside it, discriminated by
task.status: a completed task stores the serialized CallToolResult in
result (error is None), a failed task stores the JSON-RPC error dict
in error (result is None). expires_at is the absolute deadline
derived from ttlMs (None = never expires).
Source code in src/mcp/server/tasks.py
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 | |
TaskStore
Bases: Protocol
Persistence seam for task records.
SEP-2663 requires a task to be durably created before its CreateTaskResult
is returned, so multi-worker deployments must back this with shared storage;
the in-memory default is per-process.
Contract: get returns None both for unknown ids and for records whose
expires_at has passed -- a store enforces its records' TTLs the way an
external store with native expiry would.
Source code in src/mcp/server/tasks.py
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 | |
InMemoryTaskStore
Per-process TaskStore for stdio servers and single-process development.
Expired records are dropped on access and swept on every put, so the store
only retains live tasks. Tasks without a TTL are retained for the store's
lifetime -- configure Tasks(default_ttl_ms=...) to bound retention.
Source code in src/mcp/server/tasks.py
149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 | |
Tasks
Bases: Extension
The Tasks extension: server-decided task-augmented tools/call plus tasks/*.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
augment
|
Callable[[CallToolRequestParams], bool] | None
|
Per-request augmentation predicate over the validated
|
None
|
clock
|
Clock
|
Source of the current UTC time, used for the wire timestamps and TTL deadlines. Inject a fixed clock for deterministic tests. |
_utc_now
|
default_ttl_ms
|
int | None
|
Retention for recorded tasks, in milliseconds, stamped
as |
None
|
store
|
TaskStore | None
|
Task persistence. Defaults to a per-process |
None
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Source code in src/mcp/server/tasks.py
181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 | |