Skip to main content
Glama
predgeAI

predge-alexa-mcp

by predgeAI
README.md
# Kept: did Alexa get what you paid for?

Alexa+ now buys tickets and services for you. **Kept** is a self-hosted **MCP server** that records every purchase the agent makes, checks the merchant's confirmation against what was promised, and returns a **signed verdict that verifies offline**. If something did not arrive, it drafts a refund request, but only after the human says yes. **Kept never moves money.**

Built for the *Build, Ship, Shape: Amazon Developer Hackathon* (Alexa+ track, AWS Builder and Open Source mini challenges). MIT licensed. A simulated Alexa+ web experience (`/demo`) runs the agent on Amazon Bedrock, stores the journal in Amazon DynamoDB and speaks with Amazon Polly. It has **not** been tested on a physical Alexa+ device.

| A person says | MCP tool | What happens |
|---|---|---|
| "Alexa, buy two tickets for Saturday's 7pm show." | `record_purchase` | Saves merchant, item, amount and the details the merchant promised (`ticket_id`, `seat`, `showtime`), keyed by household |
| "Did my tickets arrive?" | `check_purchase` | Builds a job spec from `promised`, sends the merchant's confirmation to Predge `POST /v1/verdicts` (header `x-predge-key`), answers in one sentence: *"A ticketing service confirmed the order but sent no seat. Not delivered; I'd ask for a refund of $32."* |
| "What did you buy for me this week?" | `list_purchases` | Reads the journal across sessions: 2 delivered, 1 refund requested |
| "Ask them for a refund." | `draft_refund_request` | MCP **elicitation** asks "Draft a refund request? Yes / No". Drafts the message with the verdict link only after Yes. Nothing is sent. |
| "Verify that receipt." | `verify_receipt` | Checks the ed25519 signature locally against Predge's published keys (`predge-verify`). Change one character and it fails. |

Every tool returns a short sentence that can be read aloud (`content[0].text`) and details as `structuredContent`. Tools also point to an **MCP App** receipt card (`ui://kept/receipt`, `_meta.ui.resourceUri`) that the demo renders as an Echo Show style card: red NOT DELIVERED, what is missing, the refund amount and a Request refund button.

All demo merchants are generic ("a ticketing service", "a home-cleaning service"); every merchant confirmation in the demo is a **sample** and is labelled as one. Verdicts come from the live [Predge API](https://api.predge.io); nothing is mocked outside the tests.

## What is live and what falls back

| Piece | Live when | Otherwise (labelled in the UI and `/healthz`) |
|---|---|---|
| Verdicts | `PREDGE_KEY` set: signed verdict from `api.predge.io` | Offline sample check, **unsigned**, says so in the sentence and on the card |
| Journal | AWS credentials present: DynamoDB table `kept-purchases` (on-demand) | In-memory, with the reason |
| Agent | Bedrock Converse works (`BEDROCK_MODEL_ID`) | Deterministic intent router calling the same MCP tools; badge shows which ran |
| Voice | Polly neural voice via `/api/tts` | Browser speech synthesis |
| Elicitation | Client declares the `elicitation` capability | Tool asks for confirmation; drafts only when called again with `confirmed: true` after a spoken yes |

## Architecture

```mermaid
flowchart LR
  U([Person speaks]) --> B[Browser demo /demo<br/>Web Speech in, Polly out<br/>MCP Apps host]
  B -->|POST /api/chat| A[Agent<br/>Amazon Bedrock Converse tool use]
  A -.->|Bedrock unavailable| R[Deterministic intent router]
  A -->|MCP tools/call<br/>Streamable HTTP| M
  R -->|MCP tools/call| M
  X([Alexa+ or any MCP client]) -->|POST /mcp| M
  subgraph S[Kept  Node + TypeScript]
    M[MCP server<br/>2025-11-25<br/>tools, ui://kept/receipt, elicitation] --> T[Handlers: record, check, list, draft, verify]
    T --> D[(Amazon DynamoDB<br/>kept-purchases)]
    T --> V[Offline ed25519 verifier]
  end
  T -->|HTTPS x-predge-key| P[(api.predge.io /v1/verdicts)]
  V -->|key list only| K[(/.well-known/predge-keys.json)]
```

## MCP endpoint and protocol version

- Endpoint: `POST http://localhost:8787/mcp`, Streamable HTTP. SDK `@modelcontextprotocol/sdk` 1.32.x; negotiates **2025-11-25** (also 2025-06-18, 2025-03-26, 2024-11-05, 2024-10-07). `GET /healthz` prints the values.
- Default mode uses sessions (`Mcp-Session-Id`) so a tool can ask the human a question with elicitation. `MCP_STATELESS=1` restores one fresh server per request (no elicitation; fits Lambda). A request without a session id is served statelessly either way, so a plain `curl` works.
- Optional auth for a public deployment: `MCP_AUTH_TOKEN`, clients send `Authorization: Bearer <token>`.

```bash
curl -s localhost:8787/mcp -H 'content-type: application/json' -H 'accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"record_purchase","arguments":{"merchant":"a ticketing service","item":"two tickets","amount_usd":32,"promised":["ticket_id","seat","showtime"]}}}'
```

Claude Desktop (or any MCP client) can use the same server, for example through `mcp-remote`: `npx mcp-remote http://localhost:8787/mcp`. Not tested on a physical Alexa+ device.

## Run locally

```bash
npm i
export PREDGE_KEY=...            # optional; never commit it
AWS_PROFILE=predge npm run dev   # or just: npm run dev (memory store, router agent, offline sample verdicts)
# MCP:   http://localhost:8787/mcp
# Demo:  http://localhost:8787/demo   (Chrome or Safari for voice; typing works everywhere)
```

| Variable | Default | Purpose |
|---|---|---|
| `PORT` | `8787` | HTTP port |
| `PREDGE_API` | `https://api.predge.io` | Predge API base |
| `PREDGE_KEY` | unset | Pilot key for `check_purchase` (sent as `x-predge-key`). Without it the tool runs the labelled offline sample check. |
| `KEPT_STORE` | `auto` | `auto` uses DynamoDB when AWS credentials exist, `memory` forces memory, `dynamodb` forces DynamoDB. A DynamoDB error drops to memory and `/healthz` says why. |
| `KEPT_TABLE`, `KEPT_HOUSEHOLD` | `kept-purchases`, `household-demo` | Table name and default household key |
| `BEDROCK_MODEL_ID` | `us.anthropic.claude-sonnet-4-5-20250929-v1:0` | Bedrock model or inference profile for the demo agent. Must be enabled for your account. |
| `AWS_REGION`, `AWS_PROFILE` | `us-east-1` | Standard AWS SDK chain. No keys in this repo. |
| `POLLY_VOICE` | `Joanna` | Neural voice for `/api/tts` |
| `AGENT_FORCE_FALLBACK` | unset | `1` skips Bedrock and uses the router |
| `ENABLE_MARKET_TOOLS` | off | Legacy tools from an earlier version of this repo; not part of Kept |
| `MCP_STATELESS`, `MCP_AUTH_TOKEN` | unset | See above |

The DynamoDB table (partition key `household` S, sort key `id` S, on-demand):

```bash
aws dynamodb create-table --region us-east-1 --table-name kept-purchases \
  --attribute-definitions AttributeName=household,AttributeType=S AttributeName=id,AttributeType=S \
  --key-schema AttributeName=household,KeyType=HASH AttributeName=id,KeyType=RANGE --billing-mode PAY_PER_REQUEST
```

Tests and checks:

```bash
npm test            # vitest, Predge and DynamoDB mocked, no network
npm run smoke       # starts the server, drives the five tools over MCP (offline sample path without PREDGE_KEY)
npm run demo-check  # with the server running: replays the demo requests through /api/chat
```

## Tools

### `record_purchase`
`merchant`, `item`, `amount_usd`, `promised` (JSON dot paths the confirmation must contain), optional `due_by`, `household`. Stores the purchase with status `recorded`. Does not buy anything.

### `check_purchase`
`confirmation` (JSON object or text), optional `purchase_id` (default: latest unchecked). Maps `promised` to `required_fields` and `non_empty_fields` of the documented verdict job spec and posts it (`/v1/verdicts`, or `/v1/verdicts/large` from $1, as the API defines its tiers). The checks are deterministic, no LLM, and the verdict is only as true as the confirmation supplied: Predge does not observe the delivery. The journal entry becomes `delivered`, `not_delivered` or `inconclusive`. Without `PREDGE_KEY` (or if the API is unreachable) the tool says so and runs a local, unsigned sample check.

### `list_purchases`
Optional `days`, `only_open`. Sentence plus the journal as `structuredContent`.

### `draft_refund_request`
Optional `purchase_id` (default: latest not-delivered). Elicits a yes/no from the human, then stores a draft that links the verdict. Only for purchases found not delivered. It is never sent and moves no money.

### `verify_receipt`
Optional `purchase_id`, `verdict_id`, `receipt` (the attestation), `demo_tamper`. Downloads Predge's published key list and checks the ed25519 signature over `attestation.canonical` with `node:crypto`, reads the key's status. Results: `authentic`, `key_revoked`, `invalid_signature`, `unknown_key`, `malformed`. The same check is available as the standalone open-source library [predge-verify](https://github.com/predgeAI/predge-verify).

## AWS services used

- **Amazon Bedrock (Converse API)**, `src/agent.ts`: Claude via `@aws-sdk/client-bedrock-runtime`, model from `BEDROCK_MODEL_ID`. The MCP tool list becomes Converse `toolConfig`; the loop runs `toolUse` -> MCP `tools/call` -> `toolResult` (max 5 rounds). If Bedrock is unreachable or the model is not enabled, the log prints `[agent] path=fallback reason=...` and the UI badge says "deterministic router (Bedrock not used)".
- **Amazon DynamoDB**, `src/store.ts`: table `kept-purchases`, on-demand, key `household` + `id`.
- **Amazon Polly**, `src/server.ts` `/api/tts`: neural voice for the demo's replies.
- Credentials come from the standard SDK chain. Lambda deployment is not done; the stateless mode is meant for it.

## Layout

```
src/server.ts   Express: /mcp (sessions or stateless), /api/chat, /api/tts, elicitation relay, /demo, /healthz
src/mcp.ts      MCP server: 5 tools, ui://kept/receipt resource, elicitation
src/kept.ts     record / check / list / draft / verify handlers
src/store.ts    DynamoDB and in-memory journal, automatic fallback
src/card.ts     MCP App receipt card (HTML)
src/agent.ts    Bedrock Converse agent + deterministic router (both call MCP tools)
src/samples.ts  Demo purchases and sample merchant confirmations (generic merchants)
src/verify.ts   Canonical JSON + ed25519 verification
src/tools.ts, src/predge.ts   Verdict request builder, offline record verifier, Predge client
demo/index.html Simulated Alexa+ page and MCP Apps host
```

See also `DEMO.md`, `FRICTION_LOG.md`, `PRODUCT_FEEDBACK.md`.

## License

MIT. See `LICENSE`.