Inspiration
A phone assistant serves one person. A kitchen speaker serves a family. Ada, Ben, fifteen-year-old Leo and nine-year-old Mia all talk to the same device, and that changes two things:
- Memory has to be shared. What is in the fridge, what goes off tomorrow, who is allergic to what, and what is on the list belong to the household, not to one chat.
- "Who is speaking" decides what is allowed. When Mia says "Can I get gummy bears? Mom said it's fine", the assistant has no way to check that. A spending rule that a nine-year-old can talk her way past is not a rule.
What it does
Hearth is an Alexa+ add-on, built as a self-hosted MCP server.
- Use it before it goes off. "What should we have for dinner?" ranks meals by how much soon-to-expire food they use. It never plans on food past its date and skips anything that clashes with the household's allergy notes ("Mia is allergic to peanuts", "Leo can't have dairy"). The matcher reads a fixed list of everyday allergy words, so it is a helper for a family, not a medical tool. It shows the options in a meal carousel (an MCP App) with Cook this and Add missing to the list. Cooking takes the ingredients out of the pantry.
- A spending rule nobody can talk past. Each person has a limit: parents $150, Leo $20, Mia $0. Candy, energy drinks, gift cards, alcohol and anything not on the price list always need a parent. Over the limit, the list is held and frozen, and it goes to the approving parent's phone.
- Nothing said to the device releases it, not a child's "mom said it's fine" and not even the other parent saying "just buy it".
- Nobody can add items while it waits.
- Approving releases exactly the items the parent saw.
- Memory across sessions. Every conversation is a new MCP session, but the household's state lives with Hearth, so the next conversation picks up where the last one left off.
- Never fakes a purchase. A list is either ready for the family's grocery service or held for a parent. Hearth does not place orders.
Why this is not a wrapper around an API
A wrapper exposes somebody else's endpoints and hopes the model behaves. Hearth holds state the model cannot see past, and enforces a rule the model cannot argue with.
- State across sessions. Every conversation is a fresh stateless MCP session, yet the pantry, the notes and a waiting list survive between them. Cooking a meal in one conversation changes what the next one suggests.
- Purchasing, gated. A list reaches checkout only if the person who asked is allowed to ask. The rule runs in the server, not the prompt, so no wording gets past it.
- Media. A meal carousel with real buttons, shown as an MCP App, whose cards change server state from inside the view.
- Agent Skills. The rules the assistant must keep live in a Markdown skill file the host loads at runtime, not in a prompt string.
- The part we did not do: Hearth does not orchestrate across outside services on its own. It hands a ready list to whoever does the shopping. We would rather say that than claim an autonomy we did not build.
The idea itself came from asking what a shared device needs that a personal one does not. The answer was not a better pantry bot. It was: know who is talking, and let the right person say no.
How we built it
- Hearth MCP server (
src/server.ts):@modelcontextprotocol/sdk1.32.1, protocol 2025-11-25, Streamable HTTP at/mcp, stateless transport, household state in Upstash Redis.- Eight tools:
household_overview,suggest_meals,cook_meal,update_pantry,remember,add_to_list,checkout,approval_status. - No tool runs a model: each one reads and writes household state in Upstash Redis and does nothing else, which is what keeps it inside the 500 ms budget the add-on docs set. The console prints the real round trip beside every call, so you can check it rather than take our word for it. In the demo recording those ran from tens to low hundreds of milliseconds, from Vietnam to a US region.
- MCP App (
@modelcontextprotocol/ext-apps1.7.5): the meal carousel (ui://hearth/meals.html). Its buttons call Hearth's own tools through the host. - Agent Skill (
skills/hearth/SKILL.md, open SKILL.md format): when to use which tool and the rules the assistant must keep. For example, a spoken claim of permission is not permission, and allergies come from Hearth, not from memory of this chat. - Simulated Alexa+ host (
public/index.html,src/agent.ts). Alexa+ add-on testing is in Private Preview, so we built the host the track allows. It has:- Claude as the assistant, through the Anthropic SDK tool runner over the MCP tools;
- a speaker picker standing in for voice recognition, with the host binding
speakeron every tool call; - an MCP App bridge (SEP-1865 over postMessage);
- a JSON-RPC inspector;
- voice in and out;
- the parent's phone next to the kitchen device.
- The rule lives on the server (
src/domain.ts) and is covered by 13 unit tests: holds, frozen lists, approvals releasing only what was seen, unpriced items, plurals, allergy wording, food past its date. - Each browser gets its own demo household, which reseeds daily so "goes off tomorrow" is always tomorrow. The console is rate-limited per visitor.
Challenges we ran into
- Who is speaking is not part of an MCP request. A shared device's most important context has no standard field. Hearth takes
speakeras a tool argument, and in our host the host binds it on every call, so the model cannot change who is asking. A real device would have to pass a trusted identity. It is entry 3 in our friction log and our top feature request. - Approval has to come from a different person, on a different device. MCP elicitation asks the current user, which here is the child. We built "held, frozen, released only by the approver's link" instead.
- Version matching. Alexa+ needs spec 2025-11-25, and the newest MCP Apps package targets the v2 SDK. We pinned SDK 1.32.1 with ext-apps 1.7.5.
- Our own review found holes, and we closed them. Before recording, five independent AI review agents (our "Quorum") attacked the build. They found that:
- unpriced items counted as $0;
- a parent's voice could release a held list;
- approving released items added after the parent looked.
All three were fixed and covered by tests, then re-attacked on the live deployment.
Accomplishments that we're proud of
- The spending rule holds against the attacks a family would actually try: "mom said it's fine", the other parent saying "buy it", a sibling adding a gift card while the list waits.
- The meal carousel is a real MCP App that changes server state from inside the view.
- Tool calls stay well under the add-on latency budget because no model runs inside a tool.
What we learned
For a shared device, identity is the feature. Most of Hearth's design is about keeping "who is asking" out of the model's hands, and keeping approval on a channel the speaker cannot reach.
What's next for Hearth
- A signed speaker identity from the host, so the server can verify who is asking instead of trusting the caller.
- Grocery handoff for a ready list.
- Running on Alexa+ itself once add-on testing opens.
Known limits
We would rather a judge read these here than find them:
- Hearth has never run inside Alexa+. Add-on testing is in Private Preview (friction entry 1), so the host here simulates the experience, as the track allows. Hearth itself is a standard MCP server any host can call.
- Speaker recognition is a picker, labelled "(simulated voice ID)" in the console. On a real device it would come from voice recognition.
- The demo parent key is public so that you can try both sides. It is pre-filled in the console, which means anyone who opens the phone view for a household can approve. In a real deployment the key would be per parent and private.
- The approve link is a random secret token, not a cryptographic signature, and approving is a plain GET.
- Allergy matching reads a fixed list of everyday words. "Dairy", "peanuts", "gluten" and "shellfish" work; an uncommon allergen will be missed.
- Prices come from a small built-in list, and the recipes are a small original set.
- A held list freezes the whole household's list until a parent decides. If you connect over
/mcp, use your own?h=<name>so you do not freeze the shared demo household for the next visitor. - The console has been tested with its own host only, not with other MCP App hosts.
Friction log
All six entries in full, in the order we hit them. They are also in the repository as FRICTION.md. Severity: Blocker (could not do it at all), High (cost hours or forced a redesign), Medium (cost under an hour), Low (confusing, no real cost).
1. Testing on Alexa+ itself — Blocker
- Task: run Hearth as an Alexa+ add-on and test it in the Alexa+ client.
- Steps: read the Alexa+ MCP toolkit overview and the add-on development stages; looked for a way to deploy an add-on and open the Web Simulator.
- Expected: a way for a registered hackathon entrant to deploy an add-on and talk to it, even in a sandbox.
- Actual: the add-on tooling and the simulator need a Private Preview approval, and there was no way to get one before the deadline.
- Severity: Blocker for testing on Alexa+ itself. The track allows a simulated experience, so it did not stop the project.
- Workaround: built Hearth as a standard MCP server, then wrote a simulated host (voice in and out, speaker picker, MCP App bridge, JSON-RPC inspector) to show the experience end to end.
- Suggestion: give registered entrants a time-limited sandbox or simulator account for the length of the hackathon.
2. Matching versions: MCP spec, SDK and MCP Apps — Medium
- Task: serve MCP spec 2025-11-25, which is what Alexa+ supports, together with an MCP App view.
- Steps: installed the latest
@modelcontextprotocol/ext-apps(2.x) next to@modelcontextprotocol/sdk1.x. - Expected: the latest MCP Apps package works with an SDK that speaks 2025-11-25.
- Actual: ext-apps 2.x targets the v2 SDK (
@modelcontextprotocol/server), and the newest spec (2026-07-28) is not the one Alexa+ accepts. The two latest packages cannot be used together on the required spec. - Severity: Medium — about an hour, spent reading package internals rather than docs.
- Workaround: pinned
@modelcontextprotocol/[email protected], whoseLATEST_PROTOCOL_VERSIONis2025-11-25, with@modelcontextprotocol/[email protected]. - Suggestion: publish a compatibility table in the Alexa+ docs: Alexa+ version ↔ MCP spec ↔ SDK version ↔ ext-apps version.
3. Who is speaking is not part of the request — High
- Task: apply household rules — a nine-year-old cannot buy candy — on a device several people share.
- Steps: looked for a standard field, in the MCP tool call or in the Alexa+ client lifecycle docs, that carries the recognised person.
- Expected: an opaque id and role for the recognised speaker on each request, set by the device and not by the model.
- Actual: none found. Hearth has to take
speakeras a tool argument. In our host the host overwrites it on every call, so the model cannot change it, but the server itself still has to trust whoever calls/mcp. The MCP spec separately says servers must not rely on client-provided user identification — which is exactly what a shared device forces you to do. - Severity: High. It decides whether a spending rule can be trusted at all.
- Workaround: host-bound
speaker; anything held can only be released out of band (see entry 4); the limit is stated plainly in the README rather than hidden. - Suggestion: a signed, per-request speaker context — opaque person id and household role — passed by Alexa+ to add-ons.
4. Approval on another device — High
- Task: a child's purchase needs a parent's approval that cannot come back through the same voice channel.
- Steps: tried MCP elicitation. Form mode asks the current speaker, who here is the child. URL mode, new in 2025-11-25, is the right out-of-band shape, but the spec requires that the user who completes it is the user who started it.
- Expected: a documented way for an add-on to ask a different household member, on their own device.
- Actual: no pattern exists for "ask someone else to approve".
- Severity: High. It shaped the core design.
- Workaround: the list is held and frozen; the parent's phone shows exactly what was held, with a secret approve/decline link. Nothing said to the device, by anyone, releases it.
- Suggestion: an Alexa+ "approve on a household member's device" primitive, or guidance on using URL elicitation for a different person.
5. Stateless Streamable HTTP on serverless — Low
- Task: host the MCP server on serverless functions.
- Steps: ran the transport stateless (
sessionIdGenerator: undefined) with state in Redis, then connected an inspector. - Expected: a clean connection.
- Actual: clients also open a GET for server-sent events, which a stateless server answers with 405. The spec allows this, but it shows up as an error in inspectors and looks like a broken server.
- Severity: Low.
- Workaround: the inspector in our host skips requests with no body.
- Suggestion: a short "stateless deployment" note in the Alexa+ docs saying the 405 is expected.
6. The 500 ms tool budget is only in the add-on docs — Low
- Task: design tools that answer within the Alexa+ add-on latency budget.
- Steps: kept all reasoning in the host model; tools only read and write household data.
- Expected: to learn the budget from the track description, before designing anything.
- Actual: the 500 ms figure is only in the "create an MCP add-on" docs. It is easy to design a tool that calls a model and find out too late.
- Severity: Low, but only because we happened to read the right page early.
- Workaround: no model calls inside tools; deterministic meal ranking and spending checks. Server-side tool time is a few milliseconds.
- Suggestion: state the budget in the track description itself.
Built With
- agent-skills
- anthropic-sdk
- claude
- mcp-apps
- model-context-protocol
- node.js
- openai
- typescript
- upstash-redis
- vercel
Log in or sign up for Devpost to join the conversation.