Chapter 16

Add Progress, Cancellation, Notifications, and Cache

A production client differs from a demo in that it manages the imperfect scenarios. It bounds time, cancels unneeded work, shows progress, updates discovery from notifications, and caches results by server hints. Each of these mechanisms seems minor until it turns out that without it the system either hangs or serves stale data.

The cache starts with an honest key - it must include the authorization context, or private data leaks between users.

cacheKey = authorizationContext + method + normalizedParams

if result.cacheScope === "public":
  shared cache is allowed only for genuinely common data
if result.cacheScope === "private":
  cache isolated by token/tenant/user
expiresAt = receivedAt + result.ttlMs
list_changed => invalidate the affected collection

Cancellation also takes discipline: the signal reaches the server, it stops sending progress, but a side effect already committed does not roll itself back.

host aborts user request
  ├─ stdio: send notifications/cancelled for request id
  └─ HTTP: abort request stream

server checks signal between expensive steps
server stops emitting progress
domain side effect is not rolled back automatically

Bringing all four mechanisms together is helped by a table - why each and where its main caveat is.

Mechanism · Why · Main caveat

  • Progress - UX and a watchdog for a long call; Not a durable task history
  • Cancellation - Stop unneeded computation; Doesn't undo an already committed side effect
  • List-changed - Invalidate the discovery cache; Modern HTTP requires a listen subscription
  • ttlMs - A freshness hint; 0 means stale, not "no caching"
  • cacheScope - A public/private reuse boundary; The server is responsible for an honest classification

The caveats here matter more than the mechanisms themselves. Progress is UX and a watchdog, not a durable task history. Cancellation stops the computation but doesn't undo a committed side effect. list_changed invalidates the discovery cache but, in modern HTTP, requires a listen subscription. And ttlMs: 0 means "the data is stale", not "caching is forbidden".

Cancel is not rollback. If a tool sent a payment, an email, or a deploy, cancelling the protocol request doesn't bring the system back. Idempotency, commit status, and reconciliation are designed at the domain level - the protocol is powerless here by definition.

Progress and cancellation are not yet real long work. When an operation needs extra input or runs for minutes, explicit mechanisms come into play: MRTR and tasks.

Links