tasks
SEP-2663 client-side tasks surface.
When a server augments a tools/call into a task — a CreateTaskResult in
place of the CallToolResult — this module finishes the flow, twice over.
TasksExtension is the transparent path SEP-2663 advises ("existing code
returning a fixed shape ... can transparently drive the polling flow internally
and surface only the final, completed result"): Client.call_tool polls
tasks/get until the task reaches a terminal status and surfaces only the
final result. The free functions — get_task, wait_task, update_task,
cancel_task — are the manual path, typed over the public ClientSession,
for callers that take the CreateTaskResult themselves (via
session.call_tool(..., allow_claimed=True)) and drive tasks/* by hand.
The polling loop itself is one pure function (run_task_driver, private) so it
stays testable with plain closures; both paths run it.
DEFAULT_POLL_INTERVAL_SECONDS
module-attribute
DEFAULT_POLL_INTERVAL_SECONDS = 1.0
Poll cadence when neither the snapshot nor the CreateTaskResult carries pollIntervalMs.
SEP-2663 makes the hint optional and only says clients SHOULD honor it when present; one second is the SDK's conservative default in its absence.
TaskError
Bases: Exception
Base for the typed SEP-2663 task-outcome errors.
A task that ends anywhere other than completed surfaces as one of three
subclasses — TaskFailedError, TaskCancelledError,
TaskInputRequiredError — so except TaskError handles any non-completion.
Source code in src/mcp/client/tasks.py
64 65 66 67 68 69 70 | |
TaskFailedError
The task reached failed: a JSON-RPC error occurred during execution (SEP-2663).
Carries the JSON-RPC error inlined on tasks/get as code/message/data,
plus the snapshot's optional statusMessage diagnostic.
Source code in src/mcp/client/tasks.py
73 74 75 76 77 78 79 80 81 82 83 84 85 86 | |
__reduce__
Pickle via the constructor args (args holds MCPError's, which do not round-trip).
Source code in src/mcp/client/tasks.py
84 85 86 | |
TaskCancelledError
Bases: TaskError
The task reached cancelled before producing a result (SEP-2663).
Source code in src/mcp/client/tasks.py
89 90 91 92 93 94 95 96 97 98 99 100 | |
__reduce__
Pickle via the constructor args (args holds the formatted message, which does not round-trip).
Source code in src/mcp/client/tasks.py
98 99 100 | |
TaskInputRequiredError
Bases: TaskError
The task reached input_required, which the polling loop does not drive yet.
SEP-2663's in-task input loop (fulfil inputRequests via tasks/update) is
a deferred follow-up in this SDK. Drive it manually: fetch the snapshot with
get_task and answer its inputRequests with update_task.
Source code in src/mcp/client/tasks.py
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 | |
__reduce__
__reduce__() -> (
tuple[type[TaskInputRequiredError], tuple[str]]
)
Pickle via the constructor args (args holds the formatted message, which does not round-trip).
Source code in src/mcp/client/tasks.py
119 120 121 | |
run_task_driver
async
run_task_driver(
task_id: str,
initial_interval_ms: int | None,
*,
get_task: Callable[[str], Awaitable[GetTaskResult]],
sleep: Callable[[float], Awaitable[None]]
) -> CallToolResult
Poll a task to its final CallToolResult (the private engine behind both paths).
Polls tasks/get (via get_task) until the task reaches a terminal status.
Between polls it honors the SEP-2663 pollIntervalMs hint: each non-terminal
snapshot sleeps its own poll_interval_ms, falling back to
initial_interval_ms (the CreateTaskResult's hint, when the caller holds
one), then to DEFAULT_POLL_INTERVAL_SECONDS.
The loop deliberately imposes no round cap or deadline of its own: SEP-2663
tasks represent unbounded server-side work, so how long to wait is the
caller's policy — cancel via an enclosing anyio cancel scope, or bound each
tasks/get round with the session read timeout the get_task closure
carries.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
task_id
|
str
|
The task to poll. |
required |
initial_interval_ms
|
int | None
|
|
required |
get_task
|
Callable[[str], Awaitable[GetTaskResult]]
|
Sends one |
required |
sleep
|
Callable[[float], Awaitable[None]]
|
Awaits the given number of seconds between polls (injectable for deterministic tests). |
required |
Raises:
| Type | Description |
|---|---|
TaskFailedError
|
The task reached |
TaskCancelledError
|
The task reached |
TaskInputRequiredError
|
The task reached |
RuntimeError
|
The server violated SEP-2663 — a |
Source code in src/mcp/client/tasks.py
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 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 175 176 177 | |
get_task
async
get_task(
session: ClientSession,
task_id: str,
*,
read_timeout_seconds: float | None = None
) -> GetTaskResult
Fetch one SEP-2663 tasks/get snapshot.
One request, one typed parse: the returned GetTaskResult carries result
when the task completed, error when it failed, and neither for a
non-terminal status.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
ClientSession
|
The session to send on. |
required |
task_id
|
str
|
The task id a |
required |
read_timeout_seconds
|
float | None
|
Per-request read timeout; defaults to the session's. |
None
|
Raises:
| Type | Description |
|---|---|
MCPError
|
|
Source code in src/mcp/client/tasks.py
180 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 | |
wait_task
async
wait_task(
session: ClientSession,
task: str | CreateTaskResult,
*,
read_timeout_seconds: float | None = None
) -> CallToolResult
Poll an SEP-2663 task to a terminal status and return its final CallToolResult.
The manual counterpart of the transparent TasksExtension flow, raising the
same typed errors. Pass the CreateTaskResult and its pollIntervalMs hint
seeds the polling cadence; pass a bare task id and a client that reconnected
— or restarted with only the persisted id — resumes a task it no longer
holds the CreateTaskResult for.
The wait itself is unbounded (how long to keep polling is the caller's
policy — cancel via an enclosing anyio cancel scope);
read_timeout_seconds bounds each tasks/get round, not the whole wait.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
ClientSession
|
The session to poll on. |
required |
task
|
str | CreateTaskResult
|
The |
required |
read_timeout_seconds
|
float | None
|
Per-request read timeout for each |
None
|
Raises:
| Type | Description |
|---|---|
TaskFailedError
|
The task reached |
TaskCancelledError
|
The task reached |
TaskInputRequiredError
|
The task reached |
RuntimeError
|
The server violated SEP-2663 — a |
MCPError
|
A |
Source code in src/mcp/client/tasks.py
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 | |
update_task
async
update_task(
session: ClientSession,
task_id: str,
input_responses: dict[str, Any],
*,
read_timeout_seconds: float | None = None
) -> None
Answer an SEP-2663 task's in-task input requests (tasks/update).
input_responses maps keys of the snapshot's inputRequests to their
answers; servers should ignore responses for keys the task never issued
(SEP-2663).
The server acknowledges with an empty result, which is swallowed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
ClientSession
|
The session to send on. |
required |
task_id
|
str
|
The task id a |
required |
input_responses
|
dict[str, Any]
|
Answers keyed by the snapshot's |
required |
read_timeout_seconds
|
float | None
|
Per-request read timeout; defaults to the session's. |
None
|
Raises:
| Type | Description |
|---|---|
MCPError
|
|
Source code in src/mcp/client/tasks.py
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 | |
cancel_task
async
cancel_task(
session: ClientSession,
task_id: str,
*,
read_timeout_seconds: float | None = None
) -> None
Request cancellation of an SEP-2663 task (tasks/cancel).
Cancellation is cooperative and may never take effect: SEP-2663 lets the
server finish the task anyway, and in this SDK the work has always finished
before a tasks/cancel can arrive. The server acknowledges with an empty
result, which is swallowed — follow with get_task to see the status that
actually resulted.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
ClientSession
|
The session to send on. |
required |
task_id
|
str
|
The task id a |
required |
read_timeout_seconds
|
float | None
|
Per-request read timeout; defaults to the session's. |
None
|
Raises:
| Type | Description |
|---|---|
MCPError
|
|
Source code in src/mcp/client/tasks.py
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 | |
TasksExtension
Bases: ClientExtension
SEP-2663 Tasks as a client extension.
Declares io.modelcontextprotocol/tasks and claims the task resultType on
tools/call: a CreateTaskResult is resolved by polling tasks/get to the
final CallToolResult, exactly as wait_task does by hand.
Source code in src/mcp/client/tasks.py
318 319 320 321 322 323 324 325 326 327 328 329 | |