Skip to main content
Glama
abhinv98

Couch Call MCP Server

by abhinv98
README.md
# Couch Call

A free household play-along for live sports on Alexa+: when the game hits a big moment, the room calls it out loud, the play settles it, and a family leaderboard carries over all season.

![Couch Call on an Echo Show: "Third and seven. Run or pass?" with each player's call on the board](docs/screens/board-open-echo-show.png)

Watch the demo: YOUTUBE_URL

## Quickstart (mock mode, no AWS needed)

Needs Node 22 or newer and pnpm 10.

```sh
pnpm install
pnpm seed
pnpm dev
```

Open http://localhost:3000. The Couch Call MCP server runs on http://127.0.0.1:3001/mcp and the test bench (the Alexa+ stand-in) on port 3000. Mock mode uses a rule-based host instead of Bedrock and a silent clip instead of Polly. Type "start couch call for the game" in the "Type instead" box.

### Real voice and Bedrock

1. Sign in with the AWS CLI: `aws login --profile <your-profile>`. The profile needs Amazon Bedrock access to Nova 2 Lite and Amazon Polly in its Region.
2. Copy `.env.example` to `.env.local` and set `AWS_PROFILE`, `AWS_REGION`, `BEDROCK_MODEL_ID` (default `global.amazon.nova-2-lite-v1:0`) and `POLLY_VOICE` (default `Joanna`).
3. Run `pnpm dev:real`. Bedrock and Polly are called only from the test bench's server routes, with the named profile's credentials. If your shell exports `AWS_BEARER_TOKEN_BEDROCK`, the scripts remove it so the profile is used.

## Why it matters

- Live sports are watched together, by a room full of people on one couch.
- Alexa+ is already in those rooms, on Echo Show and Fire TV.
- Amazon reports that customers engage 6 times more with services integrated with Alexa+ ([Alexa+ developer blog, July 2026](https://developer.amazon.com/alexaplus/blogs/2026/07/alexa-plus-new-ways-to-build-experiences)).

## The core loop

1. The household starts a game night; the game feed begins.
2. A big moment comes up: "Third and seven. Run or pass?" and a 9-second window opens.
3. Everyone calls it out loud; each call is recorded until the snap locks the window.
4. The play resolves the moment; correct callers score, with an underdog bonus and a streak multiplier.
5. Points roll into a family season table that survives restarts.

## Architecture

```mermaid
flowchart LR
  subgraph Bench["Test bench (Next.js, port 3000)"]
    UI["Browser: MCP client, voice and typing, device frames, dev panel"]
    Converse["/api/converse: Bedrock Converse (Nova 2 Lite)"]
    TTS["/api/tts: Polly neural, cached"]
  end
  subgraph Server["Couch Call MCP server (port 3001)"]
    HTTP["Streamable HTTP /mcp: sessions, SSE ids, Origin check"]
    Tools["8 tools, session resource, board view"]
    Engine["Replay feed, moment detector, resolver, scoring"]
    DB[("SQLite, write-through")]
  end
  Skill["skill/couch-call-host/SKILL.md"]
  UI -- "JSON-RPC: tools/call, resources/subscribe" --> HTTP
  HTTP -- "notifications/resources/updated, elicitation" --> UI
  UI -- "messages and tool results" --> Converse
  Converse -- "toolUse" --> UI
  UI --> TTS
  Skill -. "system prompt" .-> Converse
  HTTP --> Tools --> Engine --> DB
  UI -- "ext-apps AppBridge" --> Board["ui://couch-call/board in the Fire TV and Echo Show frames"]
```

The MCP server is the hackathon entry. The test bench plays the part of Alexa+: it runs a real MCP client in the browser, asks Bedrock what to do with the tools, speaks with Polly, and renders the MCP Apps board inside two device frames.

## Where the required tech is called

Generated by `pnpm tech-map` (also in `docs/where-tech-is-called.md`).

<!-- tech-map:start -->
| What | Where | Detail |
|---|---|---|
| McpServer creation | `packages/server/src/mcp/server.ts:39` |  |
| Streamable HTTP transport | `packages/server/src/mcp/http.ts:77` |  |
| Origin check (403) | `packages/server/src/mcp/http.ts:151` |  |
| Event store (SSE ids, Last-Event-ID replay) | `packages/server/src/mcp/eventStore.ts:4` |  |
| Event store (SSE ids, Last-Event-ID replay) | `packages/server/src/mcp/http.ts:79` |  |
| registerTool | `packages/server/src/mcp/tools.ts:59` | find_games |
| registerTool | `packages/server/src/mcp/tools.ts:108` | add_player |
| registerTool | `packages/server/src/mcp/tools.ts:155` | make_call |
| registerTool | `packages/server/src/mcp/tools.ts:229` | get_recap |
| registerTool | `packages/server/src/mcp/tools.ts:247` | end_game_night |
| registerAppTool | `packages/server/src/mcp/tools.ts:80` | start_game_night |
| registerAppTool | `packages/server/src/mcp/tools.ts:179` | get_scoreboard |
| registerAppTool | `packages/server/src/mcp/tools.ts:207` | get_season_standings |
| registerAppResource | `packages/server/src/mcp/server.ts:67` |  |
| resources/subscribe handler | `packages/server/src/mcp/server.ts:71` |  |
| notifications/resources/updated send | `packages/server/src/mcp/server.ts:27` |  |
| Elicitation request | `packages/server/src/mcp/tools.ts:137` |  |
| Elicitation request | `packages/server/src/mcp/tools.ts:264` |  |
| Browser MCP client connect | `apps/simulator/lib/bench.ts:241` |  |
| Bedrock ConverseCommand | `apps/simulator/app/api/converse/route.ts:83` |  |
| Polly SynthesizeSpeechCommand | `apps/simulator/app/api/tts/route.ts:34` |  |
| SKILL.md loaded as the system prompt | `apps/simulator/app/api/converse/route.ts:17` |  |
| SKILL.md loaded as the system prompt | `apps/simulator/app/api/converse/route.ts:33` |  |
<!-- tech-map:end -->

## MCP 2025-11-25 features used

| Feature | Where |
|---|---|
| Streamable HTTP on one endpoint, `/mcp` (POST, GET, DELETE), negotiated `protocolVersion` 2025-11-25 | `packages/server/src/mcp/http.ts`, `packages/server/src/mcp/server.ts` |
| Stateful sessions with `Mcp-Session-Id`; SSE event ids and `Last-Event-ID` replay | `packages/server/src/mcp/http.ts`, `packages/server/src/mcp/eventStore.ts` |
| Origin validation: 403 for an Origin outside ALLOWED_ORIGINS, before any MCP handling | `packages/server/src/mcp/http.ts` |
| Tools with `outputSchema`, `structuredContent` and a short text content | `packages/server/src/mcp/tools.ts`, `packages/shared/src/index.ts` |
| Tool `icons` (new in 2025-11-25) and annotations | `packages/server/src/mcp/icons.ts`, `packages/server/src/mcp/tools.ts` |
| Input and request errors as tool results with `isError` (SEP-1303), never JSON-RPC errors | `packages/server/src/mcp/tools.ts` |
| Resource template `couch://session/{nightId}` with `resources/subscribe` and `notifications/resources/updated` | `packages/server/src/mcp/server.ts` |
| Elicitation: single-select enum with a default (`add_player`) and a boolean with a default (`end_game_night`) | `packages/server/src/mcp/tools.ts` |
| MCP Apps: `registerAppTool`, `registerAppResource`, `ui://couch-call/board` as one HTML file | `packages/server/src/mcp/tools.ts`, `packages/server/src/mcp/server.ts`, `packages/server/ui/` |
| Agent Skill for the host persona | `skill/couch-call-host/SKILL.md` |

## Latency

Every tool call is answered locally with no LLM or network call inside the handler. Amazon's limit is a 500 ms round trip.

<!-- latency:start -->
Measured 2026-10-21 by `pnpm latency`: a real MCP client over Streamable HTTP against the server on 127.0.0.1, 200 calls per tool, round trip in ms. Machine: Apple M2, Node v26.7.0. Amazon's limit is 500 ms; our target is p95 under 50 ms.

| Tool | n | p50 | p95 | max |
|---|---|---|---|---|
| find_games | 200 | 1.9 | 2.3 | 4.1 |
| start_game_night | 200 | 2.0 | 2.6 | 6.8 |
| add_player | 200 | 1.8 | 2.9 | 7.6 |
| make_call | 200 | 1.9 | 2.9 | 6.7 |
| get_scoreboard | 200 | 1.7 | 2.3 | 7.8 |
| get_season_standings | 200 | 1.8 | 2.4 | 6.8 |
| get_recap | 200 | 1.9 | 2.2 | 6.1 |
| end_game_night | 200 | 2.2 | 3.6 | 8.5 |
<!-- latency:end -->

## Testing it as a judge

No login and no AWS account are needed in mock mode.

```sh
pnpm install
pnpm seed      # household with Riya, Kabir and Meera, plus last week's night
pnpm dev       # MCP server on 3001 with dev controls, test bench on 3000
```

- Replay game id: `gulls-vs-foxes-q4` (a fictional 4th quarter: Harbor City Gulls vs Ridgeview Foxes, 64 plays).
- In the test bench, type: "start couch call for the game", then "Riya says pass, Kabir says run", "who's winning the season", "what did I miss", "end the night", "yes".
- Dev controls (also in the bench's dev panel, Replay tab):

```sh
curl -X POST http://127.0.0.1:3001/dev/pause
curl -X POST http://127.0.0.1:3001/dev/next-moment
curl -X POST http://127.0.0.1:3001/dev/resume
curl -X POST http://127.0.0.1:3001/dev/speed -H 'content-type: application/json' -d '{"speed":10}'
curl -X POST http://127.0.0.1:3001/dev/reset
```

- Any MCP client can connect to `http://127.0.0.1:3001/mcp`; the MCP Inspector's origin (port 6274) is allowed.
- Checks: `pnpm test` (unit and integration), `pnpm e2e` (Playwright, mock mode), `pnpm latency`, `pnpm tech-map`.

## Honest limits

- This is a simulator, not real Alexa+. The Alexa+ Category SDK, MCP Toolkit and Web Simulator are partner-only previews, so the test bench stands in for Alexa+ with a real MCP client, Bedrock and Polly.
- Voice ID is replaced by "Name says X" and by tap chips for each player.
- A live feed adapter is designed (the `FeedSource` interface in `packages/server/src/engine/feed.ts`), but only the replay feed is built.

## Beyond the hackathon

The engine is a moment detector plus a resolver, so other events fit the same loop with a new event pack: basketball (free throws, the final possession), cricket overs (boundary or dot ball) and award shows (who wins the category).

## License

MIT. See [LICENSE](LICENSE).