--- id: mcp-tasks-elicitation-apps title: MCP Tasks, Elicitation and Apps code: MT subtitle: Explicit scope, MRTR elicitation, durable Tasks, Apps UI resources and safe cancellation on MCP 2026-07-28. hue: 280 sources: phases/13-tools-and-protocols/12-mcp-roots-and-elicitation, phases/13-tools-and-protocols/13-mcp-async-tasks, phases/13-tools-and-protocols/14-mcp-apps, phases/13-tools-and-protocols/29-mcp-reliability-cancellation-and-flow-control source_hash: 79e5ebd870162891 --- ## MRTR elicitation round trip type: flow from: phases/13-tools-and-protocols/12-mcp-roots-and-elicitation/docs/en.md, phases/13-tools-and-protocols/12-mcp-roots-and-elicitation/code/main.py 1. **Client**: sends `tools/call` id 1 with `elicitation: {"form": {}}` in `_meta` capabilities. 2. **Server**: returns `resultType: "input_required"` with `inputRequests.delete_choice`. 3. **Server**: signs `requestState` with principal, method, `argumentsDigest`, `candidateIds`, nonce, expiry. 4. **Host**: renders the form. The user picks accept, decline or cancel. 5. **Client**: retries `tools/call` with a fresh id 2, `inputResponses` and the same `requestState`. 6. **Server**: verifies the state, re-authorizes the workspace and re-checks containment. 7. **Server**: claims the nonce once, deletes, returns `resultType: "complete"`. ## Task lifecycle on a stateless core type: architecture from: phases/13-tools-and-protocols/13-mcp-async-tasks/docs/en.md, phases/13-tools-and-protocols/13-mcp-async-tasks/code/main.py ```arch node | client | 1,1 | Client | declares tasks extension | client node | call | 2,1 | tools/call | resultType task | process node | store | 3,1 | Durable store | persist before return | store node | worker | 4,1 | Worker | advance_worker | process node | working | 1,2 | working | statusMessage | node | input | 2,2 | input_required | inputRequests | node | done | 3,2 | completed | nested result | node | stop | 4,2 | cancelled or failed | error on failed | edge | client | call | _meta edge | call | store | taskId edge | store | worker | lease edge | working | input | worker edge | input | working | tasks/update edge | working | done | worker edge | working | stop | tasks/cancel ``` ## Task methods and signals type: command-table from: phases/13-tools-and-protocols/13-mcp-async-tasks/docs/en.md, phases/13-tools-and-protocols/29-mcp-reliability-cancellation-and-flow-control/docs/en.md | Command | What it does | |---|---| | `tasks/get` | Full snapshot. Outer `resultType` is always complete | | `tasks/update` | Sends `inputResponses`. Returns an empty ack | | `tasks/cancel` | Records cancel intent. The ack is not a final status | | `subscriptions/listen` | POST with a response SSE stream of task snapshots | | `notifications/tasks` | Full snapshot tagged with the listen request id | | `notifications/cancelled` | Request cancel on stdio only. Never for a Task | | Close the response stream | Request cancel on Streamable HTTP | | `tasks/status`, `tasks/result`, `tasks/list` | Removed. Method not found | ## Drive a durable task type: annotated-code lang: python from: phases/13-tools-and-protocols/13-mcp-async-tasks/code/main.py ```python service = TaskService(directory) # file-backed TaskStore created = service.dispatch(make_request(1, "tools/call", {"name": "generate_report", "arguments": {"size": "large"}})) task_id = created["result"]["taskId"] # resultType task, working service = TaskService(directory) # new instance reloads it service.advance_worker(task_id) # working to input_required service.dispatch(make_request(3, "tasks/get", {"taskId": task_id})) service.dispatch(make_request(4, "tasks/update", {"taskId": task_id, "inputResponses": {"approve_outline": {"action": "accept", "content": {"approved": True}}}})) service.advance_worker(task_id) # stage 2 to completed body, headers = make_http_request(5, "tasks/get", {"taskId": task_id}) headers["Mcp-Name"] # equals params.taskId ``` ## Idempotency, buffers and reconnect type: annotated-code lang: python from: phases/13-tools-and-protocols/29-mcp-reliability-cancellation-and-flow-control/code/main.py ```python ledger = MutationLedger(Path(directory) / "mutations.sqlite3") ledger.charge("acct-7", 1200, rpc_id=41, idempotency_key="checkout-7") ledger.charge("acct-7", 1200, rpc_id=42, idempotency_key="checkout-7") ledger.executions # one execution, same receipt classify_retry(side_effect=True, idempotency_key=None) # unsafe buffer = BoundedSseBuffer(3) # capacity at least two buffer.push_final(response) # final response never dropped reconnect_plan(previous_subscription_id, affected_resources, unsafe_mutation_was_in_flight=True) # sendLastEventId False retry_delay_ms("", attempt) # capped at 8000 ms ``` ## Elicitation answers type: comparison from: phases/13-tools-and-protocols/12-mcp-roots-and-elicitation/docs/en.md, phases/13-tools-and-protocols/12-mcp-roots-and-elicitation/code/main.py | Factor | accept | decline | cancel | |---|---|---|---| | Meaning | User submitted | User refused | User dismissed | | Server action | Validate content, continue | Complete refusal result | Stop safely | | Nonce | Consumed with the delete | Consumed, no delete | Not consumed | | Retry later | No | No | Yes, until expiry | | Result | `deleted: True` | `deleted: False` | `deleted: False` | ## Numbers and defaults type: defaults from: phases/13-tools-and-protocols/13-mcp-async-tasks/code/main.py, phases/13-tools-and-protocols/12-mcp-roots-and-elicitation/code/main.py, phases/13-tools-and-protocols/14-mcp-apps/code/main.py, phases/13-tools-and-protocols/29-mcp-reliability-cancellation-and-flow-control/code/main.py | Setting | Default | Note | |---|---|---| | Protocol version | 2026-07-28 | In `_meta` on every request | | Task `ttl_ms` | 900_000 | Measured from creation | | Task `poll_interval_ms` | 1_000 | Minimum suggested poll delay | | `server/discover` `ttlMs` | 300_000 | `cacheScope` public | | `tools/list` `ttlMs` | 60_000 | Sorted by tool name | | `requestState` expiry | 300 | Seconds after the form | | `ReplayStore` `max_entries` | 1_000 | Full store gives -32023 | | Backoff cap | 8_000 | 250 ms times 2 per attempt | ## Errors and failures type: troubleshooting from: phases/13-tools-and-protocols/12-mcp-roots-and-elicitation/docs/en.md, phases/13-tools-and-protocols/13-mcp-async-tasks/docs/en.md, phases/13-tools-and-protocols/14-mcp-apps/docs/en.md, phases/13-tools-and-protocols/29-mcp-reliability-cancellation-and-flow-control/docs/en.md | Symptom | Cause | Fix | |---|---|---| | `-32602` | Missing or non-string protocol version | Send the version in `_meta` | | `-32022` | Unsupported version string | Read `supported` and `requested` data | | `-32021` | Missing form or tasks or ui capability | Declare it in this request | | `-32020` | Header and body disagree | Match `Mcp-Method` and `Mcp-Name` | | Task id gives not found | Handle sent before the store could read it | Persist before return | | Mutation runs twice | New JSON-RPC id used as dedupe | Add a durable idempotency key | | Hung work never ends | Progress resets every deadline | Keep an absolute maximum timeout | | UI says stopped, worker runs | Cancel ack read as final | Poll the Task to a terminal status | ## Apps rules type: concepts from: phases/13-tools-and-protocols/14-mcp-apps/docs/en.md, phases/13-tools-and-protocols/14-mcp-apps/code/main.py - **Extension**: `io.modelcontextprotocol/ui`, declared by both peers on each request. - **Binding**: `_meta.ui.resourceUri` on the tool in `tools/list`, before any call. - **Resource**: `ui://notes/timeline.html` with MIME `text/html;profile=mcp-app`. - **CSP**: empty `connectDomains`, `resourceDomains`, `frameDomains`, `baseUriDomains` by default. - **Bridge**: View sends `ui/initialize`, then `ui/notifications/initialized` after the host answers. - **Fallback**: no ui capability gives the tool without `_meta.ui` and a text result. - **Cache key**: `ui://` URI, server identity and version, content digest, principal if private. ## Scope rules type: tips from: phases/13-tools-and-protocols/12-mcp-roots-and-elicitation/docs/en.md - Roots are deprecated. Put scope in a `workspaceUri` argument or a resource URI. - Enforce three layers: authorization, containment and an OS sandbox. - Normalize the URI first. Then compare path components, not string prefixes. - Never use form mode for passwords, API keys, tokens or payment data. - URL mode `accept` means the user agreed to open the URL. It is not proof of completion. - An empty `"elicitation": {}` means form support. URL-only does not. ## Questions type: qa from: phases/13-tools-and-protocols/13-mcp-async-tasks/docs/en.md, phases/13-tools-and-protocols/14-mcp-apps/docs/en.md, phases/13-tools-and-protocols/29-mcp-reliability-cancellation-and-flow-control/docs/en.md - **Who decides that a call becomes a Task?** The server. The client only declares the extension. - **Where is the final task result?** Under `result` in the next `tasks/get` snapshot. - **Input before or after task creation?** Before: MRTR retry. After: `tasks/update`. - **Does `ui/initialize` start an MCP session?** No. It is the iframe bridge handshake only. - **Is an `isError: true` tool result a failed Task?** No. The Task is `completed`. - **Resume a dropped stream with `Last-Event-ID`?** No. Open a new listen request and refetch.