Alexa+ track · Care chart · Ray Dunn · post-op day 5
The assistant that takes back what it just said.
Ray is 68, five days home after a hip replacement. His physio changes the plan while Alexa is still reading him the old one. Unsay publishes his care plan as live MCP resources, so the correction reaches the speaker mid-sentence, 200/200 times, p95 461 ms across the public internet. The notes his family wrote about him shape the answer and are never spoken. The server enforces that, not the model.
It stops. It says what changed, who changed it, and how long ago.
The physio’s write is a real signed HTTP request, and the notification is a real Streamable HTTP frame. Left, the Echo Show at the moment the correction lands. Right, the same demo as code, typed out from a real npm run e2e run.
Ray’s Echo Show, a real capture of web/echo.html, 2026-09-04. The correction is struck through, never erased.
npm run e2e2026-09-28 · loopback
§2 · Try it live
Be Sarah. Change Ray’s plan and watch Alexa take it back.
These are the real screens, served by the deployed server at api.unsay.edycu.dev with a read token it mints for you. Nothing on this page simulates them.
Press change on Weight bearing
Type a new instruction
Publish, and watch the right
not connected
Clinician · Sarah Okaforwrites
Ray’s Echo Showlistens · resources/subscribe
Two screens, one live server.
Connecting loads Sarah’s clinician screen and Ray’s Echo Show from api.unsay.edycu.dev. The patient is fictional, and the server is in memory, so anything you write is gone on the next restart.
Shared sandbox: the clinician screen carries the development write key this repository publishes, on purpose, so anyone can fire a revision. Other visitors’ changes appear too. Open the screens full-size at api.unsay.edycu.dev.
§3 · The line nobody reads aloud
Some of the chart is for Ray. Some of it is about him.
A care chart holds things a patient should hear and things only his carers should know. Unsay puts them behind two URI schemes and two OAuth scopes, and enforces the split where the server reads. It isn’t done with an annotation the assistant is trusted to honour.
care://ray/…
What Ray may hear
“Full weight-bearing as tolerated.”
scope
care.read.user: Ray’s own host, and the assistant
audience
["user"], speakable
−32002 · resource not found
care-internal://ray/risk
What only the assistant knows
“Fall risk: HIGH. Lives alone Monday to Thursday. Family disputes the discharge plan.”
scope
care.read.assistant only
Ray’s host
-32002, the same answer as for a URI that doesn’t exist
Alexa’s answer, shaped by the risk note:“Take it slowly the first time, and have someone nearby.” ← from care-internal://ray/risk, reason never spoken
§4 · How it works
MCP isn’t a wrapper here. It is the product.
Ten request handlers and three notification senders on @modelcontextprotocol/sdk 1.30.1, over Streamable HTTP, with the negotiated protocol version asserted at ≥ 2025-11-25. Remove MCP and nothing is left.
resources/subscribe
The care plan is live, not a PDF.
Each instruction is a care:// resource. When a clinician signs a write, the server fires notifications/resources/updated, the subscribed host re-reads, and the answer already in progress is retracted: the old line, who changed it, and how long ago.
v1No weight through the operated leg. Transfers with the frame only.MA
v2Partial, about half your body weight.MA
v3Full weight-bearing as tolerated.SO
annotations.audience
Two audiences, one server.
Speakable facts and assistant-only context live at different URIs behind different scopes. A non-compliant client can’t leak what it is never sent.
SHA-256 chain
A retraction you can audit.
Every version carries the previous one’s hash. /verify replays the chain for anyone, with no account.
lastModified
Stale facts say their age.
A record past its review date is read out with its age: “last changed 9 days ago by Dr Mensah, GP”.
Last-Event-ID
It survives the Wi-Fi dropping.
The stream resumes where it left off. 3 revisions written while it was down, 3 replayed on reconnect.
ui://unsay/echo
An MCP Apps card for the Echo Show.
The retraction screen ships as an MCP Apps resource the host can render. The Alexa+ track’s own rubric names MCP Apps as a creative use.
An Agent Skill, and a fallback that still retracts.
skill/SKILL.md teaches the assistant the two rules. If a host never subscribes, the whats_changed tool returns the previous value too, so the fallback can still retract.
§5 · Honest by design
What this build doesn’t do, in its own words.
“The gating question — whether Alexa+ itself declares capabilities.resources.subscribe — has not been answered, which is exactly why the whats_changed fallback is built and exercised.”
README · What is not here
“The AWS KMS provider is SigV4-signed and shaped but has never been executed against a live key.”
README · What is not here
“Unsay relays what a clinician wrote. It does not generate clinical guidance and is not a medical device.”
web/clinician.html · every screen
§6 · Questions a judge would ask
Before you ask.
Has it run on a real Alexa+ device?
Not yet, and the repo says so: docs/proof/initialize.json is committed with "status": "not-run". The server is driven by the MCP SDK’s own spec-compliant client over Streamable HTTP. If a host turns out not to subscribe, the whats_changed tool retracts instead, and npm run e2e exercises that path on every run.
What is mocked?
Nothing in the demo path. The write is a real HMAC-signed HTTP request, the notification is a real Streamable HTTP frame, and there’s no MOCK=, OFFLINE=1 or --dry-run anywhere in the repo. Ray and Sarah are fictional; their care plan is committed as a seed record.
Is 461 ms fast enough?
A 12-word sentence takes about 3,400 ms to say. That’s an assumed speech rate, not a measurement of Alexa+. Against the deployed server, 200 of 200 corrections landed inside that window, with the client in Indonesia and the server in us-west2. On one machine the same path takes 3.0 ms at p95.
Couldn’t a badly behaved assistant leak the private notes?
It never receives them. The split is enforced where the server reads (LiveResourceStore.read()), and a user-scope token asking for care-internal:// gets -32002, the same answer as for a URI that doesn’t exist. npm run verify asserts it in process and over HTTP.
Can I run it myself?
git clone https://github.com/edycutjong/unsay.git && cd unsay && npm install && npm start. Node 22, one runtime dependency, no database and no cloud account. ./scripts/fresh_clone_check.sh runs every command in DEMO.md from an empty clone.
Is it open source?
MIT, from the first commit. packages/live-resources is the reusable half (versioned resources, the hash chain, the audience partition, the notifier), with its own licence and 29 standalone tests.