Inspiration

Every big life event arrives with a hidden invoice: not money, but paperwork. A birth triggers 15–40 separate actions scattered across the Social Security Administration, the health insurer, and the employer's HR portal — each with its own login, its own forms, its own deadlines (add a dependent within 30 days or wait for open enrollment), and its own ways of spelling the same name. Humans have always been the integration glue between these portals: re-typing identical data, making format mistakes, missing windows.

When we read the WebMCP spec, one sentence rewired how we think: websites can hand agents structured capabilities instead of forcing them to screenshot pixels. The paperwork layer is the biggest unclaimed prize for that idea — so we built the app we wished existed: say it once, and the whole bundle gets done, with the human still sovereign.

What it does

Type one sentence — "We had a baby girl on June 3rd at General Hospital."

  • One interview, no forms: the hub derives the facts and materializes an action bundle across three independent portals, with deadline math computed live (DOB June 3 + 30 days → insurer window closes July 3).
  • Each portal owns a tool surface: walking into a page dynamically registers its tools and walking away unregisters them (AbortController). The agent only ever sees the tools for the page it's on.
  • Drafts fill from one canonical profile — and a reconciliation engine catches cross-portal conflicts (the hospital's "Okafor-Reyes, Amara" vs. the profile's "Amara Okafor-Reyes"; "Jun 03, 2026" vs. "2026-06-03"), explains them in plain language, and standardizes only on the human's say-so.
  • Nothing files without a human click. Every write tool is a two-call gate: the first call opens an on-page dialog showing every field about to be filed; the second call executes only if the human actually approved — enforced in code, not in a prompt. Replay a confirmation id → unknown_confirmation_id. Let the agent approve itself → human_approval_missing. File twice → already_submitted.
  • Everything ends in a receipt ledger: payload digest → confirmation number → timestamp → human-approval flag.
  • The showpiece: pt_cascade_update. The family moves mid-flow; one call re-derives the whole situation — deadlines recomputed, every pending draft re-filled across all three portals — while filed actions stay untouched, because receipts preserve what was actually submitted.

How we built it

A zero-dependency, zero-build static SPA — classic scripts, no framework, no backend, deployable anywhere (it even runs from file://).

  • mcp.js — the WebMCP layer: registerTool with readOnlyHint annotations, per-portal AbortSignal lifecycles, and a guarded executor (activity-rail logging, ~1.5K output budgets, throws → typed errors agents can self-correct from).
  • portals.js — three fictional portals (SSA, Meridian Health, employer HR) with schemas, strict in-code validation whose errors are written for the agent, the two-call ConfirmGate, and the reconciliation engine.
  • hub.js — the one-sentence interview resolver and the hub tool surface (pt_start_life_event_interview, pt_get_action_bundle, pt_reconcile_field_conflicts, pt_get_receipt_ledger, pt_cascade_update).
  • store.js / bundle.js — canonical profile, dependency-aware bundle, deadline math, event-sourced receipts — plus a store subscription so the page the human is watching re-renders live when the agent mutates state through the protocol.

26 tools across 4 surfaces; 13 annotated read-only; 3 gated writes. A /diag.html page probes the runtime (API presence, live registration, getTools() visibility, abort lifecycle) — 11/11 checks pass in Chrome 150 with #enable-webmcp-testing.

Challenges we ran into

  • The runtime diverges from the spec text. In the real Chrome build, modelContext.registerTool() returns undefined, not a Promise — our .then() chain silently died at boot. We caught it only by testing against the actual enabled runtime, and now handle both styles. That bug alone justified building a diagnostics page.
  • Tool identity is page-scoped. Re-entering a portal risks a synchronous Duplicate tool name throw; abort-then-re-register had to be proven clean (it is — verified via getTools()).
  • The "shared live app" requirement is real work. When the agent conducts the interview through the protocol, the human's screen must react — we wired store-driven re-rendering so the bundle materializes on screen as the tool call lands.
  • Timezones and dates. "Jun 03, 2026" vs. "2026-06-03" needed part-wise date comparison, not toISOString() gymnastics that silently shift days across UTC offsets.
  • Making the gate genuinely binding. The tempting shortcut is a confirm dialog the agent can talk its way past. We made approval a server-side-of-the-page fact: the finalize call checks the human's actual click in code, and we proved the negative paths on camera.

Accomplishments that we're proud of

  • The full judge experience runs through the runtime's own executeTool: interview → surface swap → gated filing (pending → human approval → submitted) → reconciliation → cascade → ledger. Not a scripted path — the protocol path.
  • Receipts as evidence: every filing carries a payload digest and confirmation number; the ledger reads like an audit trail, because that's what it is.
  • Honest degradation: without a WebMCP runtime the chip says so, the human buttons drive the same code paths, and the app still demonstrates every flow.
  • A dark, full-height activity rail that narrates every tool call — the human and the agent visibly share one live artifact.

What we learned

  • Protocol depth is a design material, not a checkbox. Dynamic surfaces, annotations, and elicitation-shaped gates changed the architecture, not just the API calls.
  • Human-in-the-loop belongs in code. A prompt saying "ask first" is a suggestion; a gate that physically cannot finalize without the human's click is a guarantee.
  • Test in the real judging environment early. The single most dangerous bug we fixed was invisible in every browser except the one with the flag enabled.
  • Agents need agent-legible errors: strict in-code validation with messages written for self-correction turns failures into progress.

What's next for Paper Trail

  • Move and bereavement event packs — the bundle engine takes new event types without architecture changes.
  • Native requestUserInteraction elicitation as a progressive enhancement of the two-call gate.
  • An accessibility operator mode — the same tool surfaces are a better substrate than the visual DOM; narration and pacing ride the protocol for free.
  • Handoff export — the remaining human-only actions (notary visits, in-person verifications) as a calendar file, because a good agent knows what it can't do.

Built With

Share this project:

Updates