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:registerToolwithreadOnlyHintannotations, per-portalAbortSignallifecycles, 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-callConfirmGate, 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()returnsundefined, 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 namethrow; abort-then-re-register had to be proven clean (it is — verified viagetTools()). - 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
requestUserInteractionelicitation 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
- css3
- html5
- javascript
- localstorage
- mcp
- webmcp

Log in or sign up for Devpost to join the conversation.