Skip to main content
Glama

AccessBell

Accessible door intelligence: the doorbell as text, on the screens a household already owns. Ring track · AWS Builder mini challenge · Alexa+ (MCP) · Amazon Bedrock

ci License: MIT

AccessBell is a companion layer on Ring for one resident at a time: a deaf or hard-of-hearing person at home who needs to know who is at the door and whether to trust it — on the TV, in Alexa, on the phone. Low-vision and limited-mobility residents use the same path (voice + remote); the first user is specific. It is not a new camera and not a caregiver surveillance product: the person who lives there stays in control.

Meet Mai. 72, lives alone, hard of hearing. The doorbell rings while she is cooking; the TV shows Someone is at the door in seconds, identifies a likely pharmacy delivery with its evidence a moment later, and she answers from the remote. That single flow is the product. (Mai is a composite of the residents this is built for — no real person's data.)

The part we are proudest of — the Gatekeeper. Anyone can say “I'm the police.” AccessBell never verifies a visitor claim from the visitor's own words, a previous user label, or an unanalysed snapshot. A claim is checked against resident-created context only — an expected window or a calendar event the resident controls — and comes out verified, unverified, or contradiction, with a risk level and a claim-specific challenge question. Unverified high-stakes claims are never softened into a friendly caption: the headline itself says Visitor claims to be police. Not verified. The gate exists because the claim at the door is a measured attack surface, not a thought experiment: the FTC's Consumer Sentinel Data Book 2024 counts 845,806 imposter-scam reports and $2.95B in reported losses, with government-impersonation losses alone at $789M (up $171M year over year) — and adults 70+ report the highest median losses of any age group (FTC, 2024).

Closest incumbent. Ring Video Descriptions already turns camera events into text (subscription, 7 languages) and Alexa+ Greetings can handle a delivery at the door. Neither adjudicates the visitor's claim: AccessBell verifies the claim against resident-created context and refuses to soften an unverified high-stakes one — with consent-gated escalation, an auditable door log, EN/VI TTS-safe captions on a TV-first surface and the same door tools exposed over MCP.

Built during the submission window

This repository is a new project for the Amazon App Dev 2026 hackathon. Its first commit is 2026-09-22, inside the submission window (2026-08-31 → 2026-10-23), and every feature described here was built and tested in that window. There is no pre-existing product to explain around. The in-app Ring simulator, the Gatekeeper rewrite, the MCP surface, the AWS CDK stack and the evidence scripts are all part of this submission.

Related MCP server: Guardian MCP

Try it

3-minute video

Public upload pending

Run locally

pip install -r requirements-dev.txt && python -m backend → http://localhost:8080 (offline demo mode: MOCK_MODE + stub)

Run on AWS

./scripts/deploy_aws.sh [region] → ECS Fargate + RDS Postgres + Cognito + EventBridge; Bedrock is the triage model in this path

Live demo instance

http://Access-Acces-v4CzPoNSYUTf-590730701.us-east-1.elb.amazonaws.com — ECS Fargate + ALB + RDS Postgres + Cognito + EventBridge in us-east-1; open demo (signed Ring simulator + Alexa+ client, 50 rpm per IP; the Ring webhook and /mcp stay token-protected). It may be scaled down after the demo window — the video points at scripts/deploy_aws.sh instead of this URL

Evaluate it in 3 minutes

pip install -r requirements-dev.txt
python -m pytest -q                        # **427 tests** (390 pass + 37 Postgres-skipped)
python scripts/eval_triage.py              # 46 labeled scenarios: category + Gatekeeper + memory + demotion/escalation
python scripts/ablation_evidence.py        # grounding gate ablation (3 arms)
python scripts/measure_latency.py          # local Tier-1 / enrichment latency (stub)
python scripts/verify_mcp.py               # boots the app, then initialize / tools/list / tools/call
python scripts/check_bedrock_access.py     # probes whether this account can actually invoke a Bedrock model
DB_URL=sqlite:///demo.db MOCK_MODE=true python -m backend &   # then:
curl -s -X POST "http://127.0.0.1:8080/api/simulate/ring" \
  -H "Content-Type: application/json" -d '{"scenario":"police_claim"}'
curl -s -X POST "http://127.0.0.1:8080/api/simulate/ring" \
  -H "Content-Type: application/json" -d '{"scenario":"med_delivery"}'

What to look at: the alert caption and its Evidence / Why line; the Gatekeeper headline and challenge on the police claim; the Ring simulator panel showing the signed v1.1 webhook result; the bilingual EN+VI caption line; the 10-foot PWA mode (one screen, large type, arrow-key focus, caption read aloud); the Alexa+ MCP client (simulated) panel running the same MCP tools as Alexa+ (initialize, 11 tools, get door alerts, send_quick_reply); the escalation countdown to a trusted contact and its one-button Stop, which is written to the audit log before anything is sent; the grounding badge (grounded / generic / demoted_insufficient_evidence); the /api/metrics timers; and Download door log — a plain-text artifact of the day's events you can keep. The narrated cut is English-only; the bilingual EN+VI proof is the caption contract and UI strings. English narration captions are in docs/accessbell-demo.srt. For release discipline, SUBMISSION_STRICT=1 python scripts/check_docs_consistency.py fails while the public video URL is still TODO.

How it works

Ring webhook v1.1 (HMAC) ─► AccessBell ─► Fire TV / PWA   (Tier 1 caption, rules only)
      or Ring simulator    │
                           ├─► Tier 2: grounded triage (Bedrock / stub) + expected windows
                           ├─► Gatekeeper: resident-schedule verification (verified / unverified / contradiction)
                           ├─► EventBridge (optional, off by default)
                           ├─► MCP server ◄──► Alexa+ ◄──► Calendar MCP
                           └─► consent escalation / share (opt-in, audited)
  1. Tier 1 (rules) — instant “Someone is at the door”; never blocked on a model.

  2. Tier 2 (LLM) — EN/VI captions with confidence + evidence, validated by a pydantic contract; specific categories without grounding evidence are demoted in code. LLM output never reaches a user unvalidated.

  3. Gatekeeper — a visitor claim (police / utility / government / pharmacy / package / family) is verified only by a resident-created expected window or calendar event. The visitor's words, a prior user label and an unanalysed snapshot are never proof. Unverified claims get a claim-specific headline, risk label and challenge question.

  4. Memory — “same as last time” reply suggestion when the last reply for that door had the same category (one tap to reuse; never automatic).

  5. Talk — door turns stored per event; a fixed-prompt EN/VI phrasebook with browser TTS. Free text is honestly labeled “no fixed-prompt translation” — never a fake translation.

  6. Protect — unanswered high-urgency events can escalate to a contact the resident chose: default OFF, durable across restarts, at-most-once, audited, revocable.

Ring integration

AccessBell consumes the Ring Partner API webhook v1.1 envelope: meta.version, meta.time, meta.request_id, meta.account_id and data.id, data.type, data.attributes.source, data.attributes.source_type, data.attributes.timestamp. It verifies X-Signature: sha256=<hmac-hex> over the raw body with a constant-time compare, dedupes retries by meta.request_id, and responds well inside Ring's 5-second delivery budget. Snapshots are fetched out-of-band by the Ring API and are not treated as analysed identity evidence.

For the demo, the same runtime path is exposed in-product: POST /api/simulate/ring builds a real v1.1 envelope, signs it with HMAC-SHA256, verifies the signature, and hands it to the same RingWebhookHandler used by /webhooks/ring. The UI panel shows the scenario and the signed result. The optional data.attributes.visitor_statement field is an AccessBell integration extension representing a door transcript; real Ring v1.1 webhooks do not carry tags, label hints or a transcript. This is stated plainly so the demo never pretends a field exists in the Ring API.

Surfaces: Fire TV / PWA web UI (SSE live captions, urgency as text + color, D-pad navigation, EN/VI, child mode, 44 px targets, signed Ring simulator) and Alexa+ via self-hosted MCP server (POST /mcp, Streamable HTTP, JSON-RPC 2.0, spec 2025-11-25+). MCP is the open standard that powers Alexa+ integrations — giving Alexa+ ambient voice access to 11 door intelligence tools:

Tool

Purpose

Tool

Purpose

get_door_alerts

Alerts by time / urgency

confirm_event

User ground-truth label

get_event_detail

One event + evidence

send_quick_reply

Leave / coming / not now

get_context_snapshot

Context inputs used

update_accessibility_prefs

Caption vs voice profile

get_daily_summary

Accessible daily brief

set_escalation

Consent safety net

add_expected_context

Door intent (“pharmacy ~10”)

list_expected_context

Read the door calendar

clear_expected_context

Revoke a window by id / label

AWS services (Documented integrations)

The AWS deployment path runs AccessBell with Bedrock as the triage model, the EventBridge bus, Cognito OAuth and generated Secrets Manager values. The stub provider is the offline test mode (deterministic, no credentials), not the product path.

Service

Role

Amazon Bedrock

Tier-2 triage through LangChain ChatBedrockConverse + structured TriageResult (Nova Lite by default). The AWS deployment runs LLM_PROVIDER=bedrock.

Amazon EventBridge

Optional outbound integration: every triaged event is published to a custom bus (EVENTBRIDGE_BUS_NAME; empty = off). CDK creates the bus + IAM.

Amazon Cognito

OAuth 2.1 resource server for /mcp (two-tier: service vs user tokens) in the CDK stack, with two app clients because Cognito does not mix client_credentials and auth-code grant.

Amazon SNS / SES

Escalation and announce transport (`NOTIFIER_PROVIDER=sns

AWS CDK (ECS Fargate + ALB + RDS Postgres)

One-command deploy with generated Secrets Manager values for API/MCP/webhook tokens; optional ACM HTTPS for a Ring callback. See docs/DEPLOYMENT.md.

Local development defaults to the deterministic stub provider so tests and demos never depend on network or credentials; switch with LLM_PROVIDER=bedrock (see .env.example). Other providers (OpenAI, Anthropic, Google, Ollama) go through the same LangChain interface — the contract is identical for every model.

Evidence and SLO (no fabricated numbers)

Path

Target

Hard rule

Webhook in → SSE alert_raw

p95 < 200 ms

rules + DB insert only

Calendar MCP context join

≤ CONTEXT_BUDGET_MS else skip

fail open, context_status=timeout

Enrich → SSE alert_enriched

p95 < 2 s

ENRICH_BUDGET_MS hard timeout; on failure keep Tier 1 and still arm escalation

MCP tools/call

p95 < 500 ms

DB-only; no LLM inside a tool

Escalation fire

timeout ± 1 s

durable pending + at-most-once claim

Measured locally with scripts/measure_latency.py (100 simulated events, stub LLM, this repo; numbers vary by machine and are not production telemetry): one local run measured tier1_ms p95 7.6 ms, enrich_ms p95 6.8 ms, and webhook_to_sse_ms p95 7.7 ms (n=100). Budgets and live histograms are in /api/metrics. --base-url replays the same fixtures against a deployed server and reads that server's histograms.

Grounding is measurable, not asserted — same fixtures, three arms (scripts/ablation_evidence.py): event_only 0.5, payload_hints 1.0 (shipped fixtures with the visitor statement), expected_window 1.0 category accuracy; and scripts/eval_triage.py runs 46 labeled scenarios covering the 5 categories, Gatekeeper outcomes, memory, grounding demotion (3 cases with an injected LLM that invents) and escalation arming (scored as if opted in; false-escalation and missed counts printed).

Accessibility is enforced, not claimed: pa11y WCAG2AA runs in CI on / and /?lang=vi with a simulated alert (zero reported issues), captions are EN+VI and TTS-safe by test, and D-pad navigation reaches every visible control. Statement and known gaps: docs/ACCESSIBILITY.md.

Security model

  • Ring payloads: HMAC-SHA256 over the raw body, constant-time compare; retries deduped by meta.request_id via hashed unique index. The in-app simulator signs and verifies the same header.

  • /api/* gated by API_TOKEN on public deploys; write endpoints rate-limited per IP.

  • Proxy headers are not trusted by default: FORWARDED_ALLOW_IPS is empty → the client IP is the socket peer; when set, it accepts exact IPs or CIDRs (the CDK stack trusts its VPC CIDR for the ALB) and * is an explicit trust-all.

  • /mcp uses OAuth 2.1 (JWKS-verified JWT, two tiers) when configured, else MCP_HTTP_TOKEN; MCP_ORIGIN allowlist enforced.

  • Deploys do not ship default secrets: the CDK stack generates Secrets Manager values for the API token, MCP token and Ring webhook secret.

  • LLM output is re-validated in code (pydantic TriageResult + grounding rule); escalation and share are the only outward actions, both opt-in, audited and citing the policy.yaml version+hash.

Deploy

docker compose up --build                        # local container, http://localhost:8080
./scripts/deploy_aws.sh [region]                  # ECS Fargate + ALB + RDS + Cognito + EventBridge (CDK)
./scripts/deploy_cloudrun.sh <project> [region]   # GCP Cloud Run (portable bonus; SQLite is ephemeral)

Secrets, scaling, go-live checklist and the portability matrix (compute / LLM / identity / data) live in docs/DEPLOYMENT.md. Simulation levels (L0 local → L3 hardware) are in docs/SIMULATION.md.

Repository layout

aws-access-bell/
├── backend/            # FastAPI app: webhook, triage, Gatekeeper, captions, MCP, escalation, brief
│   ├── main.py         # routes, SSE stream, /mcp endpoint, metrics, Ring simulator API
│   ├── ring_simulator.py # signed Ring v1.1 demo scenarios (HMAC, official envelope)
│   ├── aws_events.py   # EventBridge publisher (off by default, fail open)
│   ├── captions.py     # EN/VI strings — single source, TTS-safe
│   ├── triage.py       # Tier 1 rules + Tier 2 pipeline + grounding gate + verification
│   ├── schema.sqlite.sql / schema.postgres.sql
│   └── llm/            # stub + LangChain provider factory (Bedrock/OpenAI/Anthropic/Google/Ollama)
├── infra/              # AWS CDK: ECS Fargate, ALB, RDS, Cognito, EventBridge, Bedrock IAM
├── web/                # Fire TV / PWA UI (SSE, D-pad, EN/VI, Ring simulator)
├── mock/events/        # 7 fixture stories (Ring Partner API v1.1 envelopes)
├── prompts/triage.txt  # LLM prompt: TriageResult contract + guardrails
├── scripts/            # demo_story.py, eval_triage.py, measure_latency.py, verify_mcp.py, ablation_evidence.py, deploy_*
├── docs/               # ARCHITECTURE, DEPLOYMENT, SIMULATION, ACCESSIBILITY
│   └── accessbell-demo.srt # English captions for the narrated demo cut
└── tests/              # pytest: sqlite + postgres, triage, captions/a11y, MCP, API, escalation, ...

Environment

All variables are documented in .env.example; the full table with platform notes is in docs/DEPLOYMENT.md. The most used:

Variable

Default

Purpose

MOCK_MODE

true

Fixtures instead of live Ring

LLM_PROVIDER / LLM_MODEL / LLM_TIMEOUT_SECONDS

stub / provider default / 5

stub | bedrock | openai | anthropic | google | ollama; AWS deploys use bedrock

DB_URL / DB_PATH

sqlite:///accessbell.db

SQLite or Postgres; POSTGRES_HOST/PORT/USER/PASSWORD/DB build the DSN

PORT / WEB_DIR / MOCK_EVENTS_DIR / LOG_LEVEL / APP_ENV

8080 / web / mock/events / INFO / development

Runtime basics

AWS_REGION

us-east-1

Bedrock region; LLM_MODEL defaults to us.amazon.nova-2-lite-v1:0 for bedrock

EVENTBRIDGE_BUS_NAME

–

Publish triaged events to EventBridge; empty = off

OPENAI_API_KEY / ANTHROPIC_API_KEY / GOOGLE_API_KEY / OLLAMA_BASE_URL

–

Alternative LLM providers

RING_WEBHOOK_ENABLED / RING_WEBHOOK_SECRET / RING_SIGNATURE_HEADER

false / – / X-Signature

Webhook HMAC verification

RING_CLIENT_ID / RING_CLIENT_SECRET / RING_ACCOUNT_TOKEN / RING_API_BASE_URL

– / – / – / https://api.amazonvision.com

Ring Partner API client (optional)

MCP_HTTP_TOKEN / MCP_ORIGIN

–

MCP bearer token (demo) + allowed Origins

OAUTH_ISSUER / OAUTH_JWKS_URL / OAUTH_AUDIENCE / MCP_RESOURCE / OAUTH_REQUIRE_RESOURCE

–

OAuth 2.1 resource server (/mcp)

OAUTH_SCOPE_PREFIX / OAUTH_CLIENT_IDS / CF_ACCESS_JWT_ENABLED / MCP_401_WWW_AUTHENTICATE

– / – / false / true

Scope/client checks, Cloudflare Access tokens, Alexa+ 401 shape

CALENDAR_MCP_URL / CALENDAR_MCP_TOKEN

–

Optional Calendar MCP join (off by default)

CONTEXT_BUDGET_MS / ENRICH_BUDGET_MS

200 / 2000

Latency budgets (fail open)

QUIET_HOURS_START / QUIET_HOURS_END

–

Calmer night phrasing (UX only, never evidence)

API_AUTH_DISABLED / API_TOKEN / DEMO_TOKEN / PUBLIC_DEMO_MODE

true / – / – / false

Gate /api/* on public deploys; optional token for /api/simulate; PUBLIC_DEMO_MODE=true opens /api/* on purpose (demo instances)

RATE_LIMIT_PER_MINUTE / DEBOUNCE_SECONDS / EVENT_TTL_DAYS / PURGE_INTERVAL_S

50 / 10 / 30 / 86400

Limiter, dedupe, retention

NOTIFIER_PROVIDER / SNS_TOPIC_ARN / SES_SENDER_EMAIL / ANNOUNCE_ENABLED

log / – / – / false

Announce / escalation transport

CERTIFICATE_ARN / ALEXA_CALLBACK_URLS / LLM_API_KEY / LLM_API_KEY_SECRET_NAME

–

Deploy-only CDK inputs: ACM cert for an HTTPS Ring callback; Alexa+ auth-code callback(s); LLM key for a non-Bedrock provider (stored in Secrets Manager, never in the template)

FORWARDED_ALLOW_IPS

empty

Trusted proxy IPs/CIDRs for the real client IP; empty ignores X-Forwarded-For; * trusts any peer

Who it is for

Residents

A door they can understand — hearing, vision, mobility, language or age

Families

A calm safety net instead of always-on cameras

Builders

A privacy-first accessibility layer on Ring / Fire TV / Alexa+

Value to the Amazon ecosystem: Ring events become usable for audiences that cannot act on a chime or thumbnail; Fire TV becomes an accessibility alert surface; Alexa+ gets a working MCP add-on (11 tools); Bedrock gets a low-cost, grounded inference pattern; all of it deploys inside a customer's own AWS account. We measure success in attach, sessions and tool calls — mechanisms, not invented numbers.

Ethics & privacy

  • No face recognition, no face embeddings; snapshots are not analysed for identity and are never used as proof of a claim.

  • Escalation and sharing are OFF by default, revocable, audited — and the resident is the user.

  • Child mode is the same consent gate, not secret monitoring: the child sees the caption, a parent is contacted only when allowed and nobody answers.

  • Not a medical or EMS product; logistics and accessibility only.

Roadmap

Shipped (MVP)

Next (production path)

Ring webhook v1.1 (HMAC, dedupe) + in-app signed Ring simulator; Tier 1 + grounded Tier 2

Live Ring sandbox end-to-end

Gatekeeper verification against resident-created windows/calendar; claim-specific captions and challenges

Richer claim taxonomy and resident-defined verification rules

Memory; door-talk interpreter (fixed prompts, EN/VI)

Public HTTPS deploy + Postgres (CDK stack in infra/)

MCP server (11 tools); Calendar MCP join (optional); daily brief; share protocol

Alexa+ connection (OAuth 2.1 / PKCE, MCP_401_WWW_AUTHENTICATE=false)

Consent escalation (durable, at-most-once, audited); quiet hours; child captions

One-frame snapshot in consented share

Evidence tooling: eval (46 cases), ablation, latency, WCAG2AA CI, docs check

Richer voice history with event citations; speak-at-door replies where APIs allow

Out of scope on purpose: face recognition · location tracking of children · scraping private calendars/email · medical monitoring or EMS · 24/7 video analytics.

License

MIT — see LICENSE.

Related MCP Connectors

Related MCP Servers