|
2 | 2 |
|
3 | 3 | Task-augmented execution (SEP-2663). A client declares the |
4 | 4 | `io.modelcontextprotocol/tasks` extension; the server may then answer a |
5 | | -`tools/call` with a `CreateTaskResult` (carrying a task id) instead of blocking, |
6 | | -and the client polls `tasks/get` for status and the eventual result. |
| 5 | +`tools/call` with a `CreateTaskResult` (carrying a task id) instead of the |
| 6 | +`CallToolResult`, and the client fetches the result via `tasks/get`. |
7 | 7 |
|
8 | 8 | ## Run it |
9 | 9 |
|
10 | 10 | ```bash |
11 | | -# stdio (default — the client spawns the server as a subprocess) |
| 11 | +# stdio (default) — today's stdio negotiates the legacy wire, which cannot carry |
| 12 | +# the extension capability, so this leg demonstrates graceful degradation: the |
| 13 | +# same tools/call returns a plain CallToolResult, never a task. |
12 | 14 | uv run python -m stories.tasks.client |
13 | 15 |
|
14 | | -# HTTP — the client self-hosts the server on a free port, runs, then tears it down |
| 16 | +# HTTP — the modern wire negotiates the extension; the server defers the call as |
| 17 | +# a task and the client reads the result back via tasks/get |
15 | 18 | uv run python -m stories.tasks.client --http |
16 | 19 | ``` |
17 | 20 |
|
18 | 21 | ## What to look at |
19 | 22 |
|
20 | 23 | - `server.py` `MCPServer("tasks-example", extensions=[Tasks(default_ttl_ms=...)])` — |
21 | 24 | opt in at construction. The extension advertises `io.modelcontextprotocol/tasks` |
22 | | - and serves `tasks/get` and `tasks/cancel`. |
| 25 | + and serves `tasks/get`, `tasks/update`, and `tasks/cancel` on the modern wire |
| 26 | + (legacy calls are `METHOD_NOT_FOUND`; the extension is not defined there). |
23 | 27 | - `mcp.server.tasks.Tasks.intercept_tool_call` — the server DECIDES augmentation; |
24 | 28 | the legacy `params.task` field is ignored. It augments only for a client that |
25 | 29 | declared the extension on the request, returning a flat `CreateTaskResult` |
26 | 30 | (`resultType: "task"`). |
27 | 31 | - `client.py` `Client(target, extensions={EXTENSION_ID: {}})` — declaring the |
28 | 32 | extension is what lets the server defer; `main` then reads the `CreateTaskResult` |
29 | | - and polls `tasks/get`, whose completed `DetailedTask` inlines the original |
| 33 | + and fetches `tasks/get`, whose completed envelope inlines the original |
30 | 34 | `CallToolResult`. |
31 | 35 |
|
32 | 36 | ## Scope |
33 | 37 |
|
34 | | -This is the SEP-2663 conformant *core*. The tool runs to completion inline (so a |
35 | | -task is observed as `completed` immediately), and the store is in-memory. Deferred |
36 | | -to follow-ups, each needing deeper SDK plumbing: `tasks/update` + the MRTR |
37 | | -`input_required` loop, `ToolExecution.taskSupport` gating with the `-32021` |
38 | | -required-task error, `notifications/tasks`, and SEP-2243 task routing headers. |
| 38 | +This is the core SEP-2663 surface. The tool runs to completion inline, so a task |
| 39 | +is recorded directly as `completed` (the SEP allows any initial status), and |
| 40 | +completed tasks live in a pluggable `TaskStore` (`Tasks(store=...)`, in-memory |
| 41 | +default) that enforces `default_ttl_ms`. Deferred to follow-ups, each needing |
| 42 | +deeper SDK plumbing: background execution (returning `working` tasks), the |
| 43 | +in-task `input_required`/`inputResponses` loop over `tasks/update`, |
| 44 | +`notifications/tasks`, and SEP-2243 task routing headers. |
39 | 45 |
|
40 | 46 | ## Spec |
41 | 47 |
|
|
0 commit comments