Recall
README.md
# Recall — AI Front Desk Agent for Small Clinics
Built for the **Amazon Developer Hackathon: Build, Ship, Shape** —
**Alexa+ track** (self-hosted MCP server) + **AWS Builder mini challenge**
(Amazon Bedrock integration).
Open source under the [MIT License](LICENSE).
## What it does
Small clinics (dental, in this build) lose real revenue to three quiet
front-desk failures: missed calls that never get a callback, patients
who are overdue for a recall visit and never get reminded, and
completed visits that never get asked for a review. Recall is a
voice-accessible agent — reachable through Alexa+ via a self-hosted
MCP server — that surfaces all three and drafts the outbound message
for each one using Amazon Bedrock, so the front desk (or the clinic
owner) can just say "how does the front desk look today" and act on
what comes back.
## Architecture
```
┌─────────────────────────┐
Browser ───▶ │ frontend/app.py │ simulated Alexa+ chat UI
(demo video) │ (MCP client) │ for the demo video
└───────────┬─────────────┘
│ MCP over Streamable HTTP
▼
┌─────────────────────────┐
Alexa+ ───▶ │ app/mcp_server.py │ Streamable HTTP at /mcp
(or any MCP │ (7 tools) │ THIS is the Alexa+ track deliverable
client) └───────────┬─────────────┘
│
▼
┌─────────────────────────┐
│ app/agent_logic.py │ business logic + spoken replies
└───────┬─────────┬───────┘
│ │
▼ ▼
┌───────────────────┐ ┌──────────────────────────┐
│ app/data_store.py │ │ app/bedrock_client.py │ ← AWS Builder
│ (mock clinic data)│ │ (Bedrock Converse API) │ mini-challenge
└───────────────────┘ └──────────────────────────┘
```
Neither of us has Alexa+ Preview device access, so the demo uses the
explicitly-permitted path from the hackathon rules: a real MCP server
(the actual submission requirement) plus a simulated Alexa+ web
front-end. The front-end is itself an MCP client: a keyword router
stands in for Alexa+'s language understanding, calls the MCP server
over Streamable HTTP, and speaks the tool's reply. Every answer in the
demo is a real tool call, labeled with the tool that served it.
The server speaks both MCP protocol generations: the `initialize`
handshake up to spec **2025-11-25**, and the stateless **2026-07-28**
protocol. Both are exercised by `scripts/smoke_test.py`.
## The 7 MCP tools
| Tool | Purpose |
|---|---|
| `get_daily_frontdesk_summary` | One-line spoken summary: missed calls / recalls due / reviews pending |
| `list_missed_calls` | Missed calls not yet texted back |
| `draft_and_send_callback` | Bedrock-drafts + "sends" a callback text for one call |
| `list_due_recalls` | Patients not yet reminded whose recall is overdue or due within 14 days |
| `draft_and_send_recall` | Bedrock-drafts + "sends" a recall reminder |
| `list_pending_reviews` | Completed visits eligible for a review ask |
| `draft_and_send_review_request` | Bedrock-drafts + "sends" a review request |
Every tool returns structured JSON (`structuredContent` plus an
`outputSchema`) including a `spoken_summary` written to be read aloud
("Priya Nataraj called about an hour ago, probably about rescheduling").
The send tools only act on items that are actually pending: they refuse
anything already handled (so a repeated request doesn't text a patient
twice) and recall reminders more than 14 days before the due date. If
Bedrock can't produce a usable draft, nothing is sent, the tool returns
`ok: false` with the reason, and the item stays on the list to retry.
"Send" is stubbed (logged, not actually delivered) — swapping in
Amazon SNS or Twilio for real SMS delivery is a single-function change
in `agent_logic.py`, called out as the natural next step in `SUBMISSION.md`.
## AWS services used
| Service | How Recall uses it | Where |
|---|---|---|
| **Amazon Bedrock — Converse API** | Drafts every patient-facing message (callback texts, recall reminders, review requests). Each draft is one `bedrock-runtime` `converse()` call with a system prompt that sets the clinic's voice and SMS constraints. | [`app/bedrock_client.py`](app/bedrock_client.py) |
| **Claude on Amazon Bedrock** | Default model **Claude Haiku 4.5**, invoked through the global cross-region inference profile `global.anthropic.claude-haiku-4-5-20251001-v1:0` from `us-east-1`. Any Converse-capable model works via `BEDROCK_MODEL_ID`. | `.env` / host environment |
| **AWS IAM** | Programmatic credentials (`AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY`) for an identity allowed to call `bedrock:InvokeModel`. | `.env` / host secrets |
Not used yet: Amazon SNS (SMS delivery is stubbed, see above).
Setup notes for Bedrock, as of September 2026:
- Current Claude models must be called through an inference profile ID
(`global.` or `us.` prefix). The bare `anthropic.…` model ID is
rejected for on-demand throughput.
- Claude 3.5 Sonnet is end-of-life on Bedrock.
- Model access is enabled by default, but the first Anthropic model call in
an AWS account requires Anthropic's one-time use-case form: open the
Bedrock console, pick the model in the model catalog, and submit it.
When a draft fails, the tool's error names the cause ("I couldn't draft a
message for Jordan Alvarez (AccessDeniedException), so nothing was sent")
and the full error is in the server log:
| Reason | Usual cause |
|---|---|
| `NoCredentialsError` | No AWS keys in `.env` or the environment |
| `AccessDeniedException` | Anthropic use-case form not submitted, or the IAM identity lacks `bedrock:InvokeModel` |
| `ValidationException` | Model ID wrong: bare `anthropic.…` ID, or a retired model |
## Running it locally
```bash
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install -r requirements.txt
cp .env.example .env # Windows: copy .env.example .env
# edit .env: AWS keys (loaded automatically, no export needed)
python -m app.bedrock_client # checks Bedrock wiring; exit 0 = real draft
# Terminal 1 — the real MCP server (Alexa+ track deliverable)
python -m app.mcp_server # Streamable HTTP on http://localhost:8001/mcp
# Terminal 2 — the simulated Alexa+ front-end, which calls Terminal 1 over MCP
python -m frontend.app # http://localhost:8000
```
The server terminal logs every tool call as it happens. To drive a
different MCP server from the front-end, such as the deployed one, set
`RECALL_MCP_URL=https://<host>/mcp` in `.env`.
No AWS account handy? Set `BEDROCK_MOCK=1` in `.env` and every draft is
a clearly-labeled `[MOCK]` placeholder that counts as sent, so the whole
flow runs offline. It is off by default on purpose: without it, missing
credentials make sends fail visibly instead of quietly sending
placeholders. Record the demo and deploy with real credentials and
`BEDROCK_MOCK` unset.
Sends change the demo data. The committed dataset (`data/seed.json`) is
never modified: its dates are written relative to a fixed moment and
shifted to "now" when the runtime state (`data/state.json`, gitignored)
is created, so the demo always happens today — calls missed within the
last few hours, one recall overdue, one due in three days. Start over at
any time with:
```bash
python -m app.data_store reset
```
### Verifying the MCP server
```bash
python scripts/smoke_test.py http://127.0.0.1:8001/mcp
```
This runs a raw JSON-RPC round trip (no MCP SDK), calls all 7 tools
through the MCP SDK client, checks refusals and error paths, and
confirms both protocol generations negotiate. It exits non-zero on any
failure and prints `WARN` for drafts that came from `BEDROCK_MOCK`.
Point it at the deployed URL to verify the public server the same way.
Against a local server without AWS keys, start the server with
`BEDROCK_MOCK=1`, otherwise the send checks fail (correctly).
For a visual check, run the MCP Inspector (`npx @modelcontextprotocol/inspector`)
and connect with the Streamable HTTP transport to `http://localhost:8001/mcp`.
`mcp dev app/mcp_server.py` is not the right tool here: it launches a
separate stdio copy of the server (and requires `uv`), not the HTTP
server this project deploys.
## Deploying
Deploy the MCP server to [Render](https://render.com) with the
included Blueprint (`render.yaml`): a free Docker web service in the
Virginia region, next to Bedrock in `us-east-1`.
1. Push this repo to GitHub.
2. In Render: **New → Blueprint**, select the repo, and enter
`AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` when prompted.
3. The MCP endpoint is `https://<service>.onrender.com/mcp`;
`https://<service>.onrender.com/health` returns `{"status": "ok"}`.
Free Render instances sleep after 15 minutes without traffic, so the
first request after a quiet period takes about a minute. Each restart
also resets the demo data, dated to the restart.
Any other container host works the same way: build the `Dockerfile`
and set the variables from `.env.example` as environment secrets.
Only the MCP server needs to be publicly reachable for the submission —
the simulated front-end only needs to run locally for recording the
demo video.
## What's mocked vs real
- **Real**: the MCP server (Streamable HTTP, 7 working tools with
structured output), the simulated front-end's MCP calls to it, the
Amazon Bedrock Converse API integration, all business logic and state
changes.
- **Mocked for the demo**: the clinic's data source (`data/seed.json`
stands in for a practice-management system like Dentrix/Open Dental),
SMS delivery (drafted and logged, not actually sent), and Alexa+'s
language understanding (a keyword router in the simulated front-end).
See `SUBMISSION.md` for the drafted Devpost submission text and
product-feedback answers.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues