CaseChaser for Alexa+
README.md
# CaseChaser for Alexa+
**Ask Alexa what the insurance company promised, when the refund is due, and whether they kept their word. Say "chase it" and a disclosed AI assistant phones them, writes down the new promise, and escalates when it breaks. Offers and denials come back to you as a question; nothing about your money is decided by an assistant.**
This is a self-hosted **MCP server** (Streamable HTTP, MCP specification 2025-11-25 and later; the bundled SDK negotiates 2026-07-28) that gives Alexa+ voice access to a household's open cases: insurance claims, refunds, warranty repairs, lost deliveries, billing disputes. Calls are placed through the CALL-E phone-agent API; the case logic (commitment ledger, escalation ladder, ten call-suppression reasons, masked evidence pack) is the `casechaser` package included in this repository.
```text
"Alexa, what did Example Home Insurance promise about my claim?"
-> case_status -> "They committed to issue the reimbursement by 9 September. Their words: 'You should see the payment within five business days.'"
"Alexa, chase my Example Broadband refund."
-> chase_case -> "Call placed. Outcome offer made. They made an offer: 'We can offer a goodwill credit of 60 dollars instead of the refund.' Do you accept or decline?"
"Decline, I want the full refund."
-> record_decision -> "Recorded. Example Broadband case EXAMPLE-REFUND-0002 is open. Next chase will carry your decision."
```
## Quick start (no real calls)
```bash
uv sync
uv run python server.py # Streamable HTTP at http://127.0.0.1:8000/mcp, fixture mode, two demo cases seeded
uv run pytest # 4 end-to-end tests through a real MCP client
```
Point any MCP client at `http://127.0.0.1:8000/mcp`. For Alexa+, register the server URL as a self-hosted MCP server in your Alexa+ developer settings (expose it over HTTPS with a tunnel or a host; see Deployment). The server ships `instructions` telling the assistant to read the `say` field aloud verbatim and never to accept or decline offers itself.
Fixture mode runs the real CALL-E client against a local fake of the Calls API with seven terminal-call fixtures, so every voice path (commitment, broken promise, offer, denial, customer action, identity refusal, resolved) can be exercised without a phone call or an API key.
## Tools
| Tool | Voice intent | Returns |
| --- | --- | --- |
| `list_cases` | "what cases are open" | one spoken sentence per case plus structured fields |
| `case_status` | "what did X promise", "when is my refund due" | status, pending commitment with the exact quote and date, broken count, escalation level, pending question, next chase date, policy holds |
| `chase_case` | "chase my X", "call them again" | places one call (fixture or live), the outcome, the new commitment or offer, and what the user must answer |
| `record_decision` | "decline", "accept", "I sent the form" | stores the decision; the next call carries it |
| `evidence_pack` | "give me the record for a complaint" | markdown with every call, quote, reference, commitment status; numbers masked |
| `add_case` | "start chasing my warranty repair with X" | creates a case |
Every tool returns a `say` field written for speech and structured fields for display.
## Safety, in code
- **Policy holds before any call**: quiet hours in the company's own time zone, two calls per day, eight total, twenty hours apart, no call while a company promise is within its grace period or while a user decision is pending, closed cases, waiting-on-customer cases, malformed or emergency numbers. `chase_case` reports the hold instead of dialling; `override_holds` exists for demos and is named in the response.
- **Hard boundaries in every task**: the agent says it is an AI assistant calling on behalf of the customer, never gives payment or identity secrets, never accepts, negotiates, or declines any amount, never changes the case or account, never mentions legal action.
- **Closed result schema**: every field required, outcome enumerated; anything else is rejected as unusable.
- **Idempotency**: one `Idempotency-Key` per case per day, so a repeated voice command cannot dial twice.
- **Live mode is opt-in**: `CASECHASER_MODE=live` plus `CALLE_API_KEY`; the default is fixture.
- **Masking**: hotlines are masked in every response and in the evidence pack.
## Live mode
```bash
cp .env.example .env # set CASECHASER_MODE=live and CALLE_API_KEY
uv run python server.py
```
Side effects in live mode: one outbound phone call per `chase_case` to the case hotline, disclosed as an AI assistant. Nothing else.
## Deployment
`Dockerfile` runs the server on `0.0.0.0:8000` in fixture mode with data under `/data`. Put it behind HTTPS (any reverse proxy or tunnel) before registering it with Alexa+. Set `CASECHASER_DATA` to a persistent volume.
## Layout
```text
server.py MCP server: tools, resource, spoken views, mode switch
casechaser/ case ledger, policy, planner, CALL-E client + fake server, engine, evidence pack
fixtures/ seven terminal CALL-E call fixtures
demo_cases.json two fictional cases seeded on first start
tests/ end-to-end tests through the MCP Python client over Streamable HTTP
```
## Product feedback
See `docs/product-feedback.md` for notes on building against the MCP Streamable HTTP transport and the Alexa+ documentation.
## Limitations
- One recipient per call; no conference calls.
- The assistant cannot pass one-time-code identity checks; such calls end as an authorisation question for the user.
- Fixture mode returns canned results; live results depend on the company answering.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues