Cheat sheets

MCP Tasks, Elicitation and Apps

Explicit scope, MRTR elicitation, durable Tasks, Apps UI resources and safe cancellation on MCP 2026-07-28.

  • Concepts and terms
  • Tasks and commands
  • Annotated code
  • Diagrams
  • Comparisons
  • Defaults and numbers
  • Troubleshooting
  • Questions and tips

Source

MRTR elicitation round trip#

1Clientsends tools/call id 1 with elicitation: {"form":{}} in _meta capabilities.2Serverreturns resultType: "input_required" withinputRequests.delete_choice.3Serversigns requestState with principal, method,argumentsDigest, candidateIds, nonce, expiry.4Hostrenders the form. The user picks accept, declineor cancel.5Clientretries tools/call with a fresh id 2,inputResponses and the same requestState.6Serververifies the state, re-authorizes the workspaceand re-checks containment.7Serverclaims the nonce once, deletes, returnsresultType: "complete".

Task lifecycle on a stateless core#

_metataskIdleaseworkertasks/updateworkertasks/cancelclientClientdeclares tasksextensionprocesstools/callresultType taskstoreDurable storepersist beforereturnprocessWorkeradvance_workerworkingstatusMessageinput_requiredinputRequestscompletednested resultcancelled orfailederror on failed_metataskIdleaseworkertasks/updateworkertasks/cancelclientClientdeclares tasksextensionprocesstools/callresultType taskstoreDurable storepersist before returnprocessWorkeradvance_workerworkingstatusMessageinput_requiredinputRequestscompletednested resultcancelled or failederror on failed

Task methods and signals#

CommandWhat it does
tasks/getFull snapshot. Outer resultType is always complete
tasks/updateSends inputResponses. Returns an empty ack
tasks/cancelRecords cancel intent. The ack is not a final status
subscriptions/listenPOST with a response SSE stream of task snapshots
notifications/tasksFull snapshot tagged with the listen request id
notifications/cancelledRequest cancel on stdio only. Never for a Task
Close the response streamRequest cancel on Streamable HTTP
tasks/status, tasks/result, tasks/listRemoved. Method not found

Drive a durable task#

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

Apps rules#

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#

  • 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.

Idempotency, buffers and reconnect#

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.executionsone 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("<client>", attempt)capped at 8000 ms

Elicitation answers#

Factoracceptdeclinecancel
MeaningUser submittedUser refusedUser dismissed
Server actionValidate content, continueComplete refusal resultStop safely
NonceConsumed with the deleteConsumed, no deleteNot consumed
Retry laterNoNoYes, until expiry
Resultdeleted: Truedeleted: Falsedeleted: False

Numbers and defaults#

SettingDefaultNote
Protocol version2026-07-28In _meta on every request
Task ttl_ms900_000Measured from creation
Task poll_interval_ms1_000Minimum suggested poll delay
server/discover ttlMs300_000cacheScope public
tools/list ttlMs60_000Sorted by tool name
requestState expiry300Seconds after the form
ReplayStore max_entries1_000Full store gives -32023
Backoff cap8_000250 ms times 2 per attempt

Errors and failures#

SymptomCauseFix
-32602Missing or non-string protocol versionSend the version in _meta
-32022Unsupported version stringRead supported and requested data
-32021Missing form or tasks or ui capabilityDeclare it in this request
-32020Header and body disagreeMatch Mcp-Method and Mcp-Name
Task id gives not foundHandle sent before the store could read itPersist before return
Mutation runs twiceNew JSON-RPC id used as dedupeAdd a durable idempotency key
Hung work never endsProgress resets every deadlineKeep an absolute maximum timeout
UI says stopped, worker runsCancel ack read as finalPoll the Task to a terminal status

Questions#

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.