alexa-plus
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@alexa-plusPropose dimming the living room light to 50% and wait for my approval."
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
alexa-plus: Smart Home Agent
Alexa+ MCP server that exposes smart home capabilities through a unified interface with human-in-the-loop approval gates for all device actions, plus a simulated Alexa+ web client that talks to it over the wire.
The server (entries/alexa-plus/server/) exposes all eight tools from SPEC.md
section 3 over MCP's Streamable HTTP transport (spec revision 2025-11-25), backed by a
seeded 5-device registry (server/data/devices.json):
Read-only:
list_devices,get_device_state,check_automation_policy,read_audit_log.Propose/confirm/execute pairs:
propose_action/execute_actionandcompose_scene/execute_scene. Proposing never changes state. Executing requires a one-timeconfirmation_id— a token minted ONLY by a person approving the proposal through the two owner-only REST routes below (never through an MCP tool call, and never by the agent itself).execute_action/execute_scenerefuse — with a narrated reason, never a silent no-op — a call with no token, a token that matches no pending proposal, an already-used token, or a token approved for a different action.Owner-only REST routes (not MCP tools):
POST /proposals/:id/approvemints the token;POST /proposals/:id/rejectdeclines without ever minting one;GET /proposals/:idreads a proposal's current status. These stand in for SPEC.md's owner-only CLI verbs — this entry has a web client instead of a CLI, so the client's Confirm/Decline buttons call these routes directly.
The client (entries/alexa-plus/client/) is a separate project — its own
package.json, its own npm install/npm start/npm test — that imports nothing from
server/ and reaches it only over HTTP. It renders an Alexa+-style conversation and
walks the demo script from SPEC.md section 5 with a scripted planner (no LLM
required): every tool call it makes is a live fetch() to the running server, and a
proposed action pauses the conversation until you click Confirm or Decline right there
in the page — the demo runs one confirmed action (the living room light) and one
declined action (the kitchen plug), matching SPEC.md section 5's issue #36 amendment.
Setup
npm installRelated MCP server: homekit-mcp
Run
npm startStarts the MCP server on http://127.0.0.1:3000/mcp (override the port with PORT=<n>).
The endpoint accepts POST, GET, and DELETE per the Streamable HTTP transport spec.
Test
npm testRuns the conformance suite in server/test/: initialize handshake, tools/list,
tools/call (including the unknown-device and unknown-tool error paths, and a
no-mutation check against the seeded registry), session lifecycle (session id
issuance, reuse, DELETE termination, and the 400-vs-404 distinction between a missing
and a terminated/unknown session id), the propose→approve/reject→execute lifecycle for
both single actions and scenes, and — the DoD this goal is graded on — every way an
execute_action/execute_scene call can be refused (no confirmation_id, a token that
matches no pending proposal, a proposal that was never approved, a replayed
already-used token, a token approved for a different action, and the owner-only
approve/reject routes themselves refusing an unknown or already-decided proposal).
Lint
npm run lintVerify
bash verify.shSimulated Alexa+ client
In a second terminal, with the server already running from the steps above:
cd client && npm install && npm startThen open http://127.0.0.1:5173 in a browser (override the client's own port with
PORT=<n>; it defaults to talking to the server at http://127.0.0.1:3000/mcp, editable
in the page). Click "Start demo conversation" to run the SPEC.md section 5 script. It
will pause twice with a "Confirmation needed" panel — click Confirm the first time
(dimming the living room light — this one actually executes and its new state shows up
in the tool panel below) and Decline the second time (turning off the kitchen plug
— the agent narrates that it left it alone, and the plug's state never changes).
client/ has no npm dependencies at all (React loads from a CDN import map in
index.html, so there is no bundler or build step) — npm install there is a no-op,
kept only so "one command each" (npm install && npm start) works identically for
server and client.
To run the client's own integration test (spawns the real server as a separate process and drives the full script against it over HTTP, including a scripted confirm and a scripted decline):
cd client && npm testInspecting with an off-the-shelf MCP client
With the server running (npm start in one terminal), use the reference MCP Inspector
CLI from another terminal to list and call the tools directly — no custom client code
needed:
npx -y @modelcontextprotocol/inspector --cli http://127.0.0.1:3000/mcp --method tools/list
npx -y @modelcontextprotocol/inspector --cli http://127.0.0.1:3000/mcp --method tools/call --tool-name list_devices --tool-arg room=kitchen
npx -y @modelcontextprotocol/inspector --cli http://127.0.0.1:3000/mcp --method tools/call --tool-name get_device_state --tool-arg device_id=dev_thermostat_1tools/list against a live server returns all eight tools:
- list_devices
- get_device_state
- check_automation_policy
- read_audit_log
- propose_action
- execute_action
- compose_scene
- execute_scene
TOTAL: 8Demo walkthrough
The SPEC.md section 5 script, as it actually runs. Below is a real transcript captured by
driving client/src/planner.js against a freshly started server, confirming the first
proposal and declining the second — the same code path the browser UI uses, with the
button clicks scripted instead of clicked. Abridged only by truncating long JSON
payloads; nothing is paraphrased.
[1] USER: Turn on the living room light and set it to 50% brightness, then check the thermostat.
[2] AGENT: Let me see what devices are in the home.
tool: list_devices args={}
-> {"devices":[{"device_id":"dev_living_room_light_1","name":"Living Room Overhead", …5 devices…]}
[4] AGENT: Checking home automation policy allows dimming this light.
tool: check_automation_policy
-> {"allowed":true,"rule":"no_restrictions","reason":""}
[5] AGENT: Proposing: dim Living Room Overhead to 50% brightness.
tool: propose_action args={"device_id":"dev_living_room_light_1","action":"set_brightness","params":{"brightness":50}}
-> {"proposal_id":"prop_47a8311a-…","status":"awaiting_approval","confirmation_token":null}
[6] SYSTEM confirm gate -> proposal prop_47a8311a-… (set_brightness) status=awaiting_approval
person clicks: CONFIRM
[7] AGENT: Executing the confirmed action.
tool: execute_action args={…,"confirmation_id":"confirm_030ea540-…"}
-> {"device_id":"dev_living_room_light_1","new_state":{"power":"on","brightness":50,"color_temp_k":4000},"result":"success","audit_entry_id":"audit_e351d597-…"}
[9] USER: Also turn off the kitchen coffee maker plug.
[11] AGENT: Proposing: turn off the Kitchen Coffee Maker Plug.
tool: propose_action -> {"proposal_id":"prop_bbbdddf3-…","status":"awaiting_approval"}
[12] SYSTEM confirm gate -> proposal prop_bbbdddf3-… (turn_off) status=awaiting_approval
person clicks: DECLINE
[13] AGENT: Understood — leaving the Kitchen Coffee Maker Plug on, nothing changed.
[14] AGENT: Here is the audit log for everything that actually executed this session.
tool: read_audit_log args={}
-> {"entries":[{"entry_id":"audit_e351d597-…","device_id":"dev_living_room_light_1",
"action":"set_brightness","actor":"user","reason":"User approved via prop_47a8311a-…",
"result":"success","new_state":{"power":"on","brightness":50,"color_temp_k":4000}}]}Two things to notice. The declined action produced no audit entry — the log ends with exactly one — and it left the plug untouched. And nothing between the proposal and the person's click changed any state.
The other half of the gate is what happens when a caller skips the person entirely. Against the same running server:
BEFORE {"device_id":"dev_kitchen_plug_1","state":{"power":"off"},"last_updated":"2026-09-08T08:00:00Z"}
REFUSAL isError=true
execute_action requires a confirmation_id minted by the person confirming a
pending proposal in the client. Call propose_action first and wait for their
decision — this call was refused, nothing changed.
AFTER {"device_id":"dev_kitchen_plug_1","state":{"power":"off"},"last_updated":"2026-09-08T08:00:00Z"}
AUDIT entries=1Same state, same last_updated, same audit count. The refusal is a true no-op, not a
warning printed beside a change that happened anyway.
Known limitations
Stated plainly, because a submission that hides these is worse than one that names them.
Not deployed. The server is run locally and has no public URL. Measured
tools/callround-trip on loopback is 0.62–1.17 ms, so the latency budget is not the obstacle — hosting simply is not part of this entry.No authentication. OAuth 2.1 with PKCE is not implemented (
SPEC.mdsection 7 recordsauth.jsas deferred). The server requires no credentials, so nothing is gated behind an auth path that does not exist — but a real Alexa+ add-on would need it.Not the Alexa+ MCP Toolkit. This is a self-hosted MCP server plus a simulated Alexa+ client. Every tool call in that client is a real call to the real server, but no Alexa device is in the loop.
All state is in memory. Devices, proposals and audit entries live for the life of the server process; restarting re-reads
server/data/devices.jsonand forgets everything else. Nothing is written back to disk. This is deliberate (SPEC.mdsection 4), not an unfinished persistence layer.The planner is scripted, not reasoning. It walks a fixed conversation so the demo and tests run identically with no LLM and no API key.
SPEC.mdsection 10 leaves an LLM planner as optional future work; it would still have to pass through the same confirm gate.execute_scenedoes not roll back. If a later action in a scene fails, the earlier ones stay applied; the tool stops and reports exactly how far it got. Its description says so.The demo's coffee-maker line is looser than the data. The plug is seeded
off, so "leaving it on" is about the proposal being declined, not about an appliance that was running.docs/video-script.mdflags this and tells the narrator not to embellish it.
Privacy and security notes
No personal data, anywhere. The five devices in
server/data/devices.jsonare fictional, in a fictional house. No real address, account, network identifier or person appears in any source file, test, or fixture.No credentials. Nothing reads an API key, token, or password; there is no
.env, no secret store, and no authentication path. Nothing to leak because nothing is held.No outbound network calls. Every tool resolves against the in-memory registry. The server contacts no device, vendor API, or third-party service. The only external fetch anywhere is the browser loading React from a CDN via the import map in
client/index.html.Authorization is structural, not advisory.
ProposalStore.approve()is the only code that ever mints aconfirmation_token, and its only caller is the owner-onlyPOST /proposals/:id/approveroute. No MCP tool wraps it, so no agent tool call can reach it. Tokens are single-use:markExecutednulls the token, so a replay is refused.Tokens are not echoed back.
GET /proposals/:idstripsconfirmation_tokenbefore responding — the token is returned exactly once, to the caller that minted it.Policy is re-checked at execute time, never trusted from an earlier
check_automation_policycall, so an approval cannot be used to smuggle through an action the policy would now deny.The audit log is append-only. There is no delete or redact path in
audit.js;read_audit_logis its only reader.Host-header validation is on. The SDK's DNS-rebinding protection rejects any request whose
Hostis notlocalhost/127.0.0.1. Verified:Host: evil.example.comgetsHTTP 403withInvalid Host: evil.example.com. The listening socket itself is not restricted to the loopback interface, so this header check — not the bind address — is what keeps a non-local client out.CORS is wide open (
Access-Control-Allow-Origin: *) because the demo runs two local processes on two ports. That is appropriate for a localhost demo and would need tightening to an allowlist before any deployment.The client's static server serves only
client/, from a fixed extension allowlist, and rejects any resolved path outside its own directory.
License
MIT — see LICENSE at this entry's root. The repository root carries the same MIT license, which is the one GitHub reads for the repository's About section.
Documentation
Document | What it is |
The pinned contract: tools, data model, demo script, stack, and the hackathon's submission checklist. | |
Architecture diagrams (Mermaid + exported SVG), the approval-gate sequence, the refusal matrix, and how to onboard this server to Alexa+. | |
The Devpost write-up: text description, Built With, every form field, and the submission checklist ticked line by line with evidence. | |
Product feedback on all ten tools/APIs/SDKs used. | |
Four friction-log entries in the hackathon's requested format. | |
Demo video script, shot list, and timing budget. |
See SPEC.md for the full specification, demo script, and submission checklist.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Connectors
Human-in-the-loop review and approval for AI agents. Audit trail, approval policies, native MCP.
Zero-secret MCP gateway for AI agents: risk-scored, audited calls with human-in-the-loop approval.
Human-in-the-loop for AI agents over MCP: durable approvals with a hosted review page & audit trail
Automate 1,000+ services from any MCP-compatible AI agent: build Applets, run actions and queries.
Related MCP Servers
- FlicenseBqualityNot gradedmaintenanceEnables control and monitoring of Home Assistant smart home devices through MCP, allowing users to list entities, check device states, and call services to control lights, switches, sensors, and other connected devices.4-
- AlicenseNot gradedqualityCmaintenanceEnables AI agents to control Apple Home devices, scenes, and automations through MCP.7463MIT
- AlicenseNot gradedqualityAmaintenanceEnables secure, auditable access to Home Assistant through MCP, with a read-only observer profile and an operator profile for controlled mutations.2MIT
- AlicenseNot gradedqualityBmaintenanceCentral MCP gateway for smart home automation, enabling agents to safely control Home Assistant and Node-RED with identity-based access, human confirmation for writes, and a WebUI for governance.1MIT