-
-
System diagram: two processes, one gate
-
Propose-confirm-execute sequence
-
The agent proposes dimming the living room light to 50%. It waits at awaiting_approval, confirmation_token null, until a person acts.
-
After the person clicks Confirm, the client shows Confirmed and the agent calls execute_action on the light as a real MCP server call.
-
The kitchen plug proposal was declined. read_audit_log shows one entry: the light set to 50% by actor user, approved via its proposal.
-
execute_action called directly with no confirmation_id, from the MCP Inspector CLI: the server returns isError true and nothing changes.
-
npm test on the server: 7 test suites, 41 tests, all passing.
Source code (MIT): github.com/guptachetan1995/alexa-plus. Live MCP server: http://16.176.3.215:3000/mcp on AWS EC2. It speaks MCP over Streamable HTTP, so a browser tab shows HTTP 400; connect an MCP client instead, for example npx -y @modelcontextprotocol/inspector --cli http://16.176.3.215:3000/mcp --method tools/list (details).
Inspiration
Smart home owners run dozens of connected devices through fragmented vendor apps — a separate download, login, and interface per brand. There is no unified way to discover what a home can do, no consistent interface across brands, and no single place to see the state of the house. Sequencing an action across two brands means jumping between two apps.
Handing that to an agent fixes the fragmentation and creates a worse problem: an agent that can unlock your front door is an agent that can unlock your front door by mistake. Most demos answer this with a confirmation dialog the agent itself can decide to skip.
What it does
Smart Home Agent is a self-hosted MCP server that exposes a whole smart home through one interface, plus a simulated Alexa+ web client that drives it. The agent can discover devices, read their state, check the home's automation policy, and compose multi-device scenes. What it cannot do — structurally, not by convention — is change anything.
Every mutation goes through a propose → confirm → execute lifecycle: the agent calls propose_action (or compose_scene), which creates a proposal with its expected outcome, rationale, and policy check but changes no state. The conversation stops on a "Confirmation needed" panel with Confirm and Decline. Only if the person clicks Confirm does the server mint a one-time confirmation token — minted by exactly one function, reachable through exactly one owner-only REST route, never through any tool the agent can call. execute_action accepts that token once, for that exact device, action and parameters, re-checks the automation policy, and only then writes an audit entry recording the person as the actor.
Try to skip any of that and the call is refused with a narrated reason, not a silent no-op: no token, an unknown token, an unapproved proposal, a replayed token, a token approved for a different action, or a policy denial at execute time — each covered by its own test.
How we built it
- MCP server (
server/) — Express 5 + the official@modelcontextprotocol/sdk, speaking Streamable HTTP at spec revision2025-11-25on/mcp(POST, GET, DELETE, with full session-id issuance, reuse and termination). Eight tools: four read-only (list_devices,get_device_state,check_automation_policy,read_audit_log) and two propose/execute pairs (propose_action/execute_action,compose_scene/execute_scene). - Simulated Alexa+ client (
client/) — a React conversation UI with no bundler and no build step (React loads from a CDN import map). It is a genuinely separate project — its ownpackage.json, and it imports nothing fromserver/; it reaches the server only over HTTP, which the entry'sverify.shenforces with a cross-import check. - The approval boundary —
ProposalStore.approve()is the only code in the system that ever sets a confirmation token, and its only caller is the owner-onlyPOST /proposals/:id/approveREST route. No MCP tool wraps it. That is what makes "only a person can authorize a change" a property of the architecture rather than a promise in a prompt. - Deterministic by default — the planner walks a scripted conversation, so the demo and the tests run identically with no LLM and no API key.
Challenges we ran into
Two dependency gaps cost real debugging time: createMcpExpressApp() ships no CORS handling, so a cross-origin browser client fails with a bare "Failed to fetch" whose real cause (a preflight 404 and a missing Access-Control-Expose-Headers: Mcp-Session-Id) is invisible in a Node integration test, since Node's fetch isn't subject to CORS. And Jest 30 renamed --testPathPattern to --testPathPatterns as a hard error with no deprecated alias, so every still-current Jest tutorial fails on first run.
AWS services used
- Amazon Bedrock (Converse API, tool-use loop, model
amazon.nova-micro-v1:0) is an optional planner (PLANNER=bedrock) that replaces the scripted turn sequence with real model reasoning. Every tool call still goes through the same MCP client, and every change still needs the person's Confirm: the model has no path to mint a confirmation token. Proven with a live Converse call from AWS CloudShell that returned a real tool-use decision (HTTP 200). - AWS EC2 hosts the MCP server itself at
http://16.176.3.215:3000/mcp(t3.micro, Amazon Linux 2023,ap-southeast-2).
This entry is also in the Open Source mini-challenge: a new, MIT-licensed project created inside the submission window.
What's next
- An HTTPS endpoint and the OAuth 2.1 + PKCE onboarding flow a real Alexa+ add-on needs.
- A real Alexa+ surface in the loop (the Alexa+ MCP Toolkit or an Agent Skill) in place of the simulated client.
- Persistence for devices, proposals and the audit log, which live in memory today.
Built With
- alexa
- amazon-bedrock
- amazon-ec2
- amazon-web-services
- css
- eslint
- express.js
- html
- javascript
- jest
- json-rpc
- mcp
- model-context-protocol
- node.js
- npm
- react
- streamable-http
- supertest
- zod
Log in or sign up for Devpost to join the conversation.