Tend
by Kavaykhurana
README.md
# Tend
**A family caregiving MCP server for Alexa+.** Tend gives Alexa+ (or any MCP host) a shared, self-hosted record of caring for an aging parent: medication doses, daily check-ins, appointments and who's driving, handoff notes between siblings, and alerts that reach everyone's phone.
> "Alexa, did Mom take her morning pills?"
> "She took her Lisinopril at 8:07, but her Metformin and vitamin D haven't been logged yet."

- **Self-hosted MCP server**: [MCP spec 2025-11-25](https://modelcontextprotocol.io/specification/2025-11-25) over **Streamable HTTP**, built on the official MCP TypeScript SDK v2. 11 tools, 1 prompt, 1 [MCP Apps](https://apps.extensions.modelcontextprotocol.io/) UI resource.
- **Glanceable card for screens.** On Echo Show-class displays (or any MCP Apps host), `get_care_summary` renders a pill-organizer card. Missed doses can be logged by tapping them.
- **Alexa+ simulator.** A web Echo Show with voice in and out. Its agent is an MCP client driven by **Amazon Bedrock** (Converse API), so you can demo the full Alexa+ loop today.
- **One SQLite file per household**, no cloud required. Built on Node's built-in `node:sqlite`.
## Why
63 million Americans are family caregivers, an increase of 20 million in ten years ([AARP & NAC, *Caregiving in the U.S. 2025*](https://www.aarp.org/press/releases/2025-07-24-new-report-reveals-crisis-point-for-americas-63-million-family-caregivers.html)). Most coordinate over group texts: *"Did Mom take her pills?" "Who's taking her to cardiology Thursday?"* Missed doses matter. About half of patients with chronic conditions don't take long-term medication as prescribed ([WHO](https://iris.who.int/handle/10665/42682)).
The parent being cared for often already has an Echo in the kitchen, and talking to it is easier for them than an app. Amazon's caregiving subscription, Alexa Together, [is no longer offered](https://www.aboutamazon.com/news/devices/alexa-together-is-helping-bridge-the-miles-between-families). Tend brings the everyday coordination layer to Alexa+ as an open, self-hosted MCP server that the family owns:
- **Mom** says *"I just took my blood pressure pill"* or *"I'm feeling dizzy today"*.
- **Priya**, two states away, asks *"How has Mom been this week?"*, and Alexa+ answers from the record.
- **Sam** says *"I'll drive her to physical therapy"*, and everyone's screen shows it.
- A low-mood check-in or an `alert_family` call pushes a notification to every caregiver's phone.
## Tools
| Tool | What it does | Example utterance |
|---|---|---|
| `get_care_summary` | Today's doses (taken, due, missed, upcoming), check-ins, next week's appointments, alerts, notes. Renders the **MCP Apps card**. | "How's Mom doing today?" |
| `log_dose` | Logs a dose as taken or skipped. Matches loosely by name or purpose; picks the open dose closest to now. | "I took my blood sugar pill." |
| `add_medication` / `stop_medication` | Manage the daily schedule. History is kept. | "Add vitamin D, 1000 IU, at 9 am." |
| `check_in` | Mood 1–5 plus a note. Mood ≤ 2 alerts the family automatically. | "I'm feeling a bit dizzy." |
| `add_appointment` / `set_appointment_driver` | Appointments, and who is taking them. | "This is Priya, I'll drive Mom to cardiology." |
| `leave_note` | Handoff notes between caregivers. | "Tell everyone the walker arrives Friday." |
| `get_history` | Adherence per medication with the exact missed doses, mood trend, notes, alerts. | "How has Mom been this week?" |
| `alert_family` / `resolve_alerts` | Alerts every caregiver; pushes to phones through a webhook. | "Alert the family, Mom fell." |
Plus the `daily_briefing` prompt: a 60-word spoken morning briefing.
Tools carry MCP annotations (`readOnlyHint`, `destructiveHint`, `idempotentHint`), and every result returns both a speakable sentence and `structuredContent`. The server's `instructions` tell the host to keep answers short for voice, never give medical advice, and route emergencies to emergency services first.
## How it works
```mermaid
flowchart LR
subgraph Home["Family's devices"]
A["Alexa+ / Echo Show<br/>(or the simulator)"]
H["Any MCP host<br/>(MCP Inspector, desktop agents)"]
end
subgraph Tend["Tend (self-hosted, Node.js)"]
M["/mcp<br/>Streamable HTTP<br/>MCP spec 2025-11-25"]
T["11 tools + prompt<br/>+ ui://tend/care-card.html"]
S[("SQLite<br/>node:sqlite")]
SIM["Alexa+ simulator<br/>MCP client + Amazon Bedrock"]
end
P["Caregivers' phones<br/>(ntfy or any webhook)"]
A -- "tools/call" --> M
H -- "tools/call" --> M
SIM -- "MCP client" --> M
M --> T --> S
T -- "alerts" --> P
SIM <-- "Converse API<br/>tool use" --> B["Amazon Bedrock"]
```
- `src/server.js` is the MCP surface: tools, prompt, and the MCP Apps resource. A fresh `McpServer` and stateless `NodeStreamableHTTPServerTransport` are created per request, so the server scales horizontally and has no session state to lose.
- `src/store.js` holds the domain logic: dose slots, grace windows (a dose is *due* 30 min before and *missed* 60 min after its time), adherence, and alerting.
- `ui/card.js` + `ui/card.css` make up the care card. The same file renders inside the MCP Apps iframe and in the simulator. The MCP Apps resource is one self-contained HTML document: the ext-apps browser bundle is inlined at serve time, so there's no bundler and no CDN.
- `src/agent.js` is the simulator's brain. It discovers Tend's tools **over MCP** (it's a real MCP client, like Alexa+), hands them to a model on **Amazon Bedrock** through the Converse API, and executes the model's `toolUse` requests as MCP `tools/call`.
## Quick start
Requires Node.js 22.13 or later (developed and tested on Node 26).
```bash
git clone https://github.com/Kavaykhurana/tend.git
cd tend
npm install
npm run seed # a two-week demo household, relative to today
npm start
```
- MCP endpoint: `http://127.0.0.1:3000/mcp`
- Alexa+ simulator: `http://127.0.0.1:3000/` (voice works in Chrome, Edge and Safari; typing works everywhere)
### Connect an MCP host
Use the [MCP Inspector](https://github.com/modelcontextprotocol/inspector):
```bash
npx @modelcontextprotocol/inspector
```
Choose transport **Streamable HTTP** and URL `http://127.0.0.1:3000/mcp`. If you set `TEND_TOKEN`, add the header `Authorization: Bearer <token>`.
Any agent that supports remote MCP servers over Streamable HTTP can use the same URL. Hosts that support MCP Apps render the care card inline.
### Turn on the simulator's voice agent (Amazon Bedrock)
The simulator's chat calls Amazon Bedrock. The quickest route is a **Bedrock API key**:
1. In the AWS console, switch to **US East (N. Virginia)** and open **Amazon Bedrock**, then **API keys**, then **Generate long-term API keys**.
2. Copy the key into `.env` as `AWS_BEARER_TOKEN_BEDROCK=<key>`, alongside `AWS_REGION=us-east-1`.
3. Run `npm start`.
Standard AWS credentials (an `~/.aws` profile, SSO, or environment variables) also work. The principal needs `bedrock:InvokeModel` on the model (default `us.amazon.nova-pro-v1:0`, set with `BEDROCK_MODEL_ID`).
Without credentials, everything except simulator chat still works: the MCP server, the card, and tap-to-log. The chat explains what is missing.
## Configuration
Put these in `.env` (see `.env.example`) or the environment.
| Variable | Default | Purpose |
|---|---|---|
| `PORT` / `HOST` | `3000` / `127.0.0.1` | Where to listen. Localhost binds get DNS-rebinding protection automatically. |
| `TEND_TOKEN` | *(none)* | Requires `Authorization: Bearer <token>` on `/mcp` and `/api/*`. **Set this whenever Tend is reachable beyond localhost.** |
| `TEND_ALLOWED_HOSTS` | *(none)* | Comma-separated hostnames to accept when `HOST=0.0.0.0` (for example your domain). |
| `TEND_DB` | `tend.db` | SQLite file for the household. |
| `TEND_CARE_RECIPIENT` | `Mom` | How the family refers to the person being cared for. |
| `ALERT_WEBHOOK_URL` | *(none)* | Receives a plain-text POST for every alert. Works as-is with [ntfy](https://ntfy.sh): `https://ntfy.sh/<your-private-topic>`. |
| `TZ` | system | The household's time zone. Dose times are local wall-clock times. |
| `AWS_BEARER_TOKEN_BEDROCK` | *(none)* | Amazon Bedrock API key for the simulator's voice agent (or use any standard AWS credentials). |
| `AWS_REGION` / `BEDROCK_MODEL_ID` | `us-east-1` / `us.amazon.nova-pro-v1:0` | Simulator voice agent. |
| `TEND_DEMO_TIME` | *(none)* | Pins the household clock to a time of day, e.g. `09:40`, for demos and recordings. |
## Self-hosting on the internet
Alexa+ and other cloud hosts need an HTTPS URL. Run Tend behind any TLS reverse proxy or tunnel:
```bash
HOST=0.0.0.0 TEND_ALLOWED_HOSTS=tend.example.com TEND_TOKEN=$(openssl rand -hex 24) npm start
```
Then register `https://tend.example.com/mcp` with the bearer token in your MCP host.
## Safety and privacy
- The family's data stays in one SQLite file on hardware they control. Tend calls no third-party service unless you configure an alert webhook or the Bedrock simulator.
- Tend records what the family enters; it does not give medical advice, check interactions, or change doses. The server instructions tell hosts so, and route emergencies to emergency services before `alert_family`.
- The bearer token is compared in constant time. Localhost binds validate `Host`/`Origin` headers. The simulator's tool proxy only exposes `get_care_summary` and `log_dose`. The card renders family-entered text with DOM nodes, never `innerHTML`.
## Tests
```bash
npm test
```
`node:test` covers dose-slot logic, adherence history, and alerts against a fixed clock. It also runs an end-to-end MCP session over Streamable HTTP, asserting the negotiated protocol `2025-11-25`, the tool list, input validation, bearer auth, and the MCP Apps resource. Finally, it runs the simulator's Bedrock tool-use loop against a stubbed Converse client.
## Recording the demo video
`npm run demo` (macOS, with Google Chrome and ffmpeg) produces `demo/out/tend-demo.mp4`, a narrated walkthrough under 3 minutes:
1. It starts a private Tend instance on a fresh demo database, with the clock pinned to 9:40 am.
2. It drives the simulator through a scripted family morning with Playwright, using real Bedrock replies.
3. It voices the narration and every speaker with the built-in macOS `say` command.
4. It mixes the audio and video with ffmpeg.
Your own `tend.db` is not touched. To change the story, edit `SCRIPT` in `demo/record.mjs`.
## Project layout
```
src/index.js entry point (env, webhook, listen)
src/app.js Express app: /mcp, simulator, /api/*
src/server.js MCP server: tools, prompt, MCP Apps resource
src/store.js SQLite schema and caregiving logic
src/agent.js simulator agent: MCP client + Amazon Bedrock Converse
src/seed.js demo household
demo/ scripted, narrated demo-video recorder
ui/ care card (shared by MCP Apps view and simulator)
web/ Alexa+ simulator page
test/ node:test suite
```
## License
[MIT](LICENSE)
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues