-
-
Approval queue: the owner sees exactly what will run. Approval is bound to a SHA-256 hash of the arguments, single-use, expires in 5 min.
-
Dependency graph: the routine waits for the lights and thermostat. If a prerequisite fails or is denied, its dependents are skipped.
-
System and MCP: two role-bound sessions on MCP 2025-11-25 over Streamable HTTP, 17 tools, and live JSON-RPC traffic.
-
Policy inspector: ordered rules, default deny. Dry-run any action against the live state and see which rule matched and why.
-
Receipt and audit trail: what was asked, what policy decided, who approved and what ran, in a hash-chained log that verifies.
-
Agent workspace: a local model plans the request; each action gets a policy decision (allow, approval, deny), a risk level and a status.
-
SentryFlow: the AI proposes, deterministic policy decides what runs. A self-hosted MCP server with a simulated Alexa+ home.
Inspiration
Voice assistants are learning to act: unlock doors, change the thermostat, place orders. Today, the same model that interprets a request often decides whether the resulting action is safe. That means a prompt injection, a misheard sentence or a hallucinated tool call can become a real-world action. We wanted an assistant where the AI can propose anything, but only deterministic, testable code decides what actually runs, and the owner always has the final say on anything sensitive.
What it does
SentryFlow is a permission-aware execution layer for AI agents. It's a self-hosted MCP server (spec 2025-11-25, Streamable HTTP), with a web app that simulates the Alexa+ experience for a smart home.

- Plan: a natural-language request ("I'm leaving for college: turn off the lights, set eco mode, order air filters… never unlock a door without my approval") becomes a structured plan, drafted by a local model (Ollama, qwen2.5:3b).
- Validate: every proposed action is treated as untrusted. Strict schemas apply, unknown tools are rejected, and fields like
"approved": trueare stripped. - Decide: a deterministic, versioned policy engine returns ALLOW, REQUIRE_APPROVAL or DENY, with a risk level. The owner's own "never…" rules can only make it stricter.
- Approve: sensitive actions wait for the owner. Each approval is bound to a SHA-256 hash of the exact arguments, works once, and expires after 5 minutes. Only an owner session can grant it; the agent can't approve itself.
- Execute: everything is re-checked right before it runs (arguments, dependencies, current state, policy version, approval). Nothing runs twice.
- Audit: every event goes into an append-only, hash-chained log, and every request gets a human-readable receipt.
How we built it
- MCP server: Node.js + Express with the official MCP TypeScript SDK, exposing 17 tools with precise schemas and annotations. Bearer tokens give two roles (agent and owner). The server checks request origins and returns 401 without
WWW-Authenticate, per the Alexa+ MCP checklist. - Web client: React + Vite, with the same MCP SDK in the browser. The UI is a genuine MCP client with two sessions, and it shows the live JSON-RPC traffic.
- Local AI: Ollama with qwen2.5:3b and structured JSON output, plus a transparent rule-based fallback. No paid APIs.
- Safety core: zod schemas, a JSON policy (first match wins, default deny), canonical hashing for approval binding, and a mutex for exactly-once execution.
- Testing: 110 automated tests, mostly adversarial: prompt injection, approval replay, argument edits after approval, expiry, policy and state changes between approval and execution, concurrent execution, audit tampering, and MCP protocol errors. GitHub Actions CI runs them on every push.
Challenges we ran into
- The local model misread "never… disable security" as an instruction and proposed disarming the alarm. The policy denied it, which proved the design, and we added a regression test using that exact model output.
- Our own adversarial test showed that a
__proto__key in model output slipped past schema validation. We now reject such keys before parsing. - When the server restarted, browser MCP sessions went dead. We implemented the spec's re-initialize-on-404 behaviour, with a single shared reconnect.
Accomplishments that we're proud of
- The model is provably not the security boundary: injected "pre-approved" claims, invented tools and smuggled arguments all fail deterministically.
- Approvals can't be replayed, reused for another action, or carried over to edited arguments.
- It's a real MCP client/server system, and every status on screen comes from live server state.
What we learned
MCP makes the integration surface clean and portable. The hard problem is authorization around the tools. Treating model output exactly like untrusted user input, and re-checking at execution time rather than at plan time, removed whole classes of bugs.
What's next for SentryFlow
- OAuth 2.1 + Protected Resource Metadata, to onboard as a real Alexa+ add-on
- Native Alexa voice confirmation as the approval channel
- MCP Apps (
ui://) approval cards for Echo Show - Policy packs for other domains (finance, DevOps, scheduling)
All devices are simulated. Nothing controls real Alexa or Ring hardware.
Built With
- alexa
- express.js
- github-actions
- mcp
- model-context-protocol
- node.js
- ollama
- qwen
- react
- typescript
- vite
- vitest
- zod


Log in or sign up for Devpost to join the conversation.