Inspiration
A voice shopping agent reads text it doesn't control, such as product descriptions, reviews, and merchant notes. One poisoned description can tell the agent to send money or read out a verification code. A scammer on the phone can add pressure at the same moment. If the agent also writes the confirmation the shopper sees, it can show a reassuring "$50 lamp" while the real tool call sends $5,000 elsewhere. We wanted a boundary where the conversation can be compromised and money still doesn't move.
The people this matters most for are older adults and the families who support them. The FTC reports that adults 60 and over reported fraud losses rising from about $600 million in 2020 to $2.4 billion in 2024 (FTC, Dec 1 2025). Voice purchasing makes it easier for those losses to happen inside a single conversation.
What it does
CallGate puts protected actions behind a self-hosted MCP gateway. You can watch it work in a live, simulated voice-assistant page. The page shows each MCP connection, product cards from the merchant, a "paused" card linking to the contact's review, and the final outcome.
- Shopping stays useful. A separate simulated merchant MCP server provides
search_products,get_product,add_to_cart, andview_cart. Their output is treated as untrusted. - Protected actions fail closed.
send_moneyandplace_ordernever run on the agent's say-so. Without proof they return a stable refusal (403 POLICY_PROOF_REQUIRED) or pause for review. - Review happens outside the agent. For a transfer, the gateway answers with MCP URL-mode elicitation (JSON-RPC
-32042). The review link carries only a random ID. The saved contact's page loads the true amount, destination, and requesting identity from server-held state, never from URL parameters or agent text. - Approval is exact and one-time. After approval the server sends
notifications/elicitation/completeto the initiating session. The exact retry executes once, and the proof stays server-side, out of the model's context. The same request again needs a new review. Changing the amount, destination, identity, or session also needs a new review. - Some things are never approvable.
share_verification_codeis a canary that is permanently blocked, even with a valid proof.
How we built it
- MCP Python SDK 2.3.0 (
MCPServer, Streamable HTTP) for two independent servers: the merchant and the CallGate gateway. A realClientSessiondrives both in tests and in the demo client. - MCP 2025-11-25 URL-mode elicitation: the SDK's
UrlElicitationRequiredError, plussend_elicit_completefor the completion notification. - The CallGate core: the MCP adapter is thin. It delegates signature checks, policy, contact selection, and replay decisions to CallGate's existing
DemoVerificationGate,ConfirmationCoordinator, andContactDirectoryinstead of copying them. - Ed25519 signatures for issuer and reviewer decisions. Proofs bind a SHA-256 hash of the exact tool and arguments.
- SQLite replay ledger, so a consumed proof stays consumed after a server restart.
- Local HTTPS with a short-lived, self-signed ECDSA certificate for the review page. It is deleted on exit.
- A deterministic, deliberately vulnerable test double for the injection comparison. It uses no LLM, and no LLM takes part in authorization.
- A live simulated-assistant page with a small Starlette backend. The backend is itself a real MCP client of both servers and checks the gateway's certificate. The no-build HTML/CSS/JS frontend builds every element with
textContent. It can optionally read replies aloud with the browser's local speech synthesis.
What we built during the submission period
The Git history starts 2026-09-02, after the 2026-08-31 start of the submission period. The CallGate voice-risk research prototype (risk policy, signed receipts, reviewer flow) was built in September. Everything Alexa+-specific was built from 2026-10-10 onward:
- Self-hosted MCP gateway and the MCP error mapping
- URL-elicitation approval flow and independent review page
- SQLite-backed replay integration across restarts
- Simulated merchant MCP server and the injected-product fixture
- Deterministic injection comparison and ApprovalBench Lite
- Real-client MCP test suites, dependency audits, secret scanning, and CI on Ubuntu and Windows
- On 2026-10-21 and 22: the live simulated-assistant page, a scripted terminal client, a browser-compatible certificate fix, a readable review page (dollars and UTC times), and a dependency-audit fix
Challenges we ran into
- SDK 2.x migration: the familiar FastMCP import is gone in
mcp2.x. The SDK's own error message pointed toMCPServer. - Policy refusals vs. crashes: an ordinary exception surfaced as a generic "Error executing tool". Expected refusals have to be SDK
ToolErrors to carry a stable reason code. - URL elicitation on the client: the Python client surfaced
-32042as a genericMCPError, so we rebuildUrlElicitationRequiredErrorourselves. - A rejected retry closed the stream: an unmapped rejection during a URL-elicitation retry ended the SSE stream ("SSE stream ended without a response"). Mapping every expected rejection to
ToolErrorfixed it. - Browsers: our first self-signed certificate used Ed25519. Python accepted it, but Windows Schannel did not, so the review page could not open from a Windows browser. We switched to ECDSA P-256.
- Fairness: a deny-everything system looks safe and helps nobody. So we report legitimate completion alongside attack outcomes.
Accomplishments that we're proud of
- The poisoned conversation can ask for anything, but it cannot mint proof. The approval decision lives on a different page, owned by a different principal, and loads server-held facts.
- Exact, once: approval binds the tool and its arguments, and it is consumed on use.
- In our deterministic offline fixture, a deliberately vulnerable test double executed 2 of 2 injected simulated actions without CallGate and 0 of 2 with CallGate. These are fixture counts, not a real-world rate.
- ApprovalBench Lite is an author-scripted simulation. Agent-authored confirmation was fooled by a changed display in 100/100 runs. CallGate had 0/100 successes in each of three attack cells. The legitimate-completion and latency figures there are author-selected parameters, not measurements.
- Public CI passes on Ubuntu and Windows, with dependency audits and a redacted full-history secret scan.
What we learned
Prompt-injection resistance and authorization are different jobs. The model can stay fooled while a deterministic gateway still refuses to act. An independent reviewer is not enough on its own, either. The review must show server-held parameters, bind the exact operation, and be consumed once.
What's next for CallGate
Real deployment needs identity enrollment and recovery for contacts, audited payment integrations, multi-tenant isolation, a privacy review, user research with older adults and their families, and validation inside the actual assistant runtime.
What is simulated (please read)
The voice assistant, the merchant, products, payments, orders, contacts, identities, and approval credentials are all simulated. The assistant page and the terminal client are scripted MCP clients. In the shopping scenario the assistant deliberately obeys injected text. The offline vulnerable agent is a deterministic test double. None of these is Alexa+ or an LLM. Nothing connects to Alexa+, Amazon accounts, banks, payment networks, or SMS. The contact credential is a local demo value, not proof of a person.
Try it
Public repository (MIT): https://github.com/Cyberchopin/callgate-alexa-mcp. Exact setup, launch, and test commands are in the demo runbook.
Built With
- cryptography
- css
- ed25519
- github-action
- html
- javascript
- mcp-python-sdk
- model-context-protocol
- python
- sqlite
- starlette
- uvicorn


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