Skip to main content
Glama
ambient-home-systems

Ambient Home Assistant MCP

Official
README.md
# Ambient Home Assistant MCP

Ambient Home Assistant MCP is a secure, semantic bridge that gives ChatGPT and
other MCP clients purpose-built access to Home Assistant. It is the server
foundation for the future user-facing **Ambient Home Assistant** application.

> **Phase 7 status:** v0.6.9 passed real Home Assistant validation across all 24
> read-only tools and is the stable App baseline. The v0.7.0 source introduces seven
> semantic control tools behind independent, disabled-by-default safety gates and a
> central policy/execution/audit pipeline. No automated live write is enabled.
> Deployment remains local/private until a later authentication phase is complete.

## What it is—and what it is not

The bridge is an abstraction and security layer. Over time, it can choose among
Home Assistant REST, WebSocket, and native MCP/Assist interfaces while presenting
small, semantic tools to the model.

It is **not**:

- a replacement for Home Assistant;
- an unrestricted Home Assistant administrator API;
- a generic API wrapper exposed to an LLM; or
- a reverse proxy for Home Assistant's `/api/mcp` endpoint.

## Architecture

```mermaid
flowchart TD
    C[ChatGPT or MCP client] -->|MCP| A[Ambient Home Assistant MCP]
    A --> T[Semantic tools]
    A --> P[Policy and security]
    A --> N[Normalized data and diagnostics]
    T --> H[Home Assistant client facade]
    P --> H
    N --> H
    H --> R[REST state API]
    H --> W[WebSocket registries]
    H -. selective future use .-> M[HA MCP or Assist API]
```

MCP tools never make raw HTTP requests. They depend on `HomeAssistantClient`,
which owns interface selection and immediately normalizes upstream responses.
See [the architecture decision record](docs/architecture.md).

## Capabilities

| Surface | Purpose |
| --- | --- |
| `ha_connection_status` | Reports reachability and authentication state without exposing credentials. |
| `ha_server_info` | Returns only version, time zone, and unit-system metadata. |
| `ha_get_entity` | Gets one current entity by exact entity ID with resolved location and safe attributes. |
| `ha_search_entities` | Searches current entities by name/ID and composable domain, area, floor, state, and availability filters. |
| `ha_list_areas` / `ha_get_area` | Lists compact areas or gets one area with domain counts and an optional bounded entity list. |
| `ha_list_floors` / `ha_get_floor` | Lists floors or gets one floor with area and domain aggregates. |
| `ha_domain_summary` | Summarizes observed states and availability for any entity domain. |
| `ha_get_entity_history` | Returns bounded recorded state transitions and only proven state durations. |
| `ha_get_logbook` | Returns bounded, privacy-filtered recorded logbook facts. |
| `ha_get_recent_changes` | Finds recorded state changes by time, area, floor, domain, or entity. |
| `ha_get_home_summary` | Returns a bounded whole-home snapshot containing only supported sections. |
| `ha_find_unavailable_entities` | Finds unavailable entities with optional factual duration filtering. |
| `ha_find_low_batteries` | Finds genuine numeric percentage battery sensors below a threshold. |
| `ha_get_openings` | Lists doors, windows, garage doors, and other openings by semantic class. |
| `ha_get_lights_on` | Lists compact current light entities reporting `on`. |
| `ha_diagnose_home` | Returns deterministic, evidence-backed findings with exact severities. |
| `ha_list_automations` | Lists compact current automation metadata with deterministic search. |
| `ha_get_automation` | Returns a bounded, sanitized loaded automation definition when supported. |
| `ha_find_automations_for_entity` | Finds conservative static entity/device/template references. |
| `ha_get_automation_traces` | Lists compact metadata for recent stored automation traces. |
| `ha_get_automation_trace` | Normalizes one bounded stored execution trace with nested paths. |
| `ha_find_activity_cause` | Correlates Recorder contexts, traces, static references, and timing under strict evidence rules. |
| `ha_control_light` | Controls exact light IDs with supported on/off, brightness, color-temperature, and RGB values. |
| `ha_control_fan` | Controls exact fan IDs with on/off and supported percentages. |
| `ha_control_media_player` | Performs play/pause/stop, bounded volume, and mute operations without arbitrary media URLs. |
| `ha_control_climate` | Sets supported HVAC modes and target temperatures within device and policy limits. |
| `ha_control_switch` | Controls only exact switch IDs explicitly authorized by server policy. |
| `ha_activate_scene` | Accepts exact scene IDs but remains confirmation-blocked throughout Phase 7. |
| `ha_run_script` | Runs only exact explicitly authorized scripts, without variables or raw service data. |
| `GET /health` | Reports application liveness and separate Home Assistant readiness. |

No generic service-call, administrative, lock, alarm, cover/garage, valve,
automation-trigger, or configuration-mutation tool is implemented.

## Security model

- Home Assistant tokens come only from runtime configuration and use Pydantic
  secret types.
- Logs are structured and redact bearer tokens and common credential fields.
- Raw `/api/config` data is reduced to an allowlisted model before it can reach a
  tool result.
- Detailed entity attributes use an explicit allowlist and exclude URLs, camera
  sources, tokens, credentials, coordinates, and location-bearing metadata.
- Current states are never cached. Registry metadata uses one bounded 60-second
  TTL cache to avoid repeated WebSocket authentication and registry reads.
- Historical queries use Home Assistant Recorder data, remain uncached, and are
  bounded to a 24-hour default / 7-day maximum window, 500 events, and 50
  aggregate candidate entities by default.
- Whole-home tools use one bulk current-state request plus the registry cache.
  Detail lists are bounded, raw tracker attributes are excluded, and safety text
  states only what Home Assistant reports.
- Automation definitions use Home Assistant's admin-gated `automation/config`
  WebSocket command. Stored traces use `trace/list`, `trace/get`, and
  `trace/contexts`; unavailable commands degrade only those features.
- Automation aliases, descriptions, templates, and action data are untrusted data.
  Strings and structures are bounded, secret-like values and private action content
  are redacted, Jinja is never executed, and context user IDs are never returned.
- The reference index is an in-memory TTL snapshot with explicit refresh and a
  500-automation bound. Current automation entity metadata and Recorder state
  changes remain fresh.
- MCP transport Host and Origin allowlists protect against DNS rebinding.
- Ambient policy is independent of the Home Assistant token's privilege. The
  engine supports `allow`, `deny`, and `confirm_required`, deterministic rule
  precedence, canonical targets, value limits, protected entities, and hard
  mass-action limits.
- `READ_ONLY=true` and `CONTROL_ENABLED=false` are independent hard boundaries.
  Writes require both `READ_ONLY=false` and `CONTROL_ENABLED=true`; exact policy,
  capability, value, and mass-action checks still apply afterward.
- Every executable plan crosses one central executor. Confirmation has no spoofable
  caller-supplied boolean; confirmation-required plans remain blocked.
- Exact entity IDs are the only writable targets. The server never chooses a device
  merely because a display name appears similar.
- Audit events are bounded and recursively redact credentials, webhooks, URLs,
  messages, commands, camera streams, and other secret-bearing service data.
- Compose starts directly as a non-root user with a read-only filesystem. In App
  mode, a minimal root bootstrap reads Supervisor's root-only options file and then
  drops to the non-root `ambient` user before starting the MCP server.

Never commit `.env`, Home Assistant tokens, credentials, private URLs, or
certificates. See [Security](docs/security.md) before any deployment work.

## Quick start

Requirements: Python 3.12+ and [uv](https://docs.astral.sh/uv/).

```bash
cp .env.example .env
# Edit .env and provide HOME_ASSISTANT_URL and HOME_ASSISTANT_TOKEN.
# Optional: copy policy.example.toml and set POLICY_FILE to its absolute path.
# Keep READ_ONLY=true and CONTROL_ENABLED=false unless controls are deliberately configured.
uv sync --all-extras
uv run ambient-ha-mcp
```

Standalone HTTP(S) URLs derive the standard Home Assistant `/api/websocket`
endpoint. Gateways with a distinct route may set the optional, credential-free
`HOME_ASSISTANT_WEBSOCKET_URL`. App mode sets Supervisor's documented endpoint
internally; it is not an App option.

The Streamable HTTP MCP endpoint is `http://127.0.0.1:8000/mcp`; health is at
`http://127.0.0.1:8000/health`.

Inspect the tools locally:

```bash
npx @modelcontextprotocol/inspector@latest
```

Then connect the Inspector to `http://127.0.0.1:8000/mcp`.

## Home Assistant App

The repository now contains a Home Assistant App definition for `amd64` and
`aarch64`. The currently validated and advertised baseline is
`ghcr.io/ambient-home-systems/ambient-ha-mcp:0.6.9`. Candidate images are published
from immutable version tags and verified before a separate PR is allowed to update
the App catalog version. The App never relies on `latest` for installation.

1. Add `https://github.com/ambient-home-systems/ambient-ha-mcp` as a custom App
   repository.
2. Install **Ambient Home Assistant MCP**.
3. Keep port `8000/tcp` disabled except during local MCP validation.
4. Start the App and check its log and container health.

Supervisor supplies a short-lived token and the App connects through
the documented REST and WebSocket Core proxies; no Home Assistant URL or token
appears in App options.
The v0.7.0 catalog-promotion metadata must default to `read_only: true` and
`control_enabled: false`; it is deliberately deferred until the new image exists.
After promotion, enabling controls requires changing both gates. Switches and
scripts additionally require exact entity-ID allowlists. Scene IDs may be listed for
future confirmation support, but Phase 7 never executes scenes. External policy files
remain disabled in App mode.
See [Home Assistant App installation](docs/home-assistant-app.md) for exact setup,
upgrade, rollback, troubleshooting, and security guidance. Record real-installation
results in the [sanitized Phase 6.6 validation template](docs/phase-6-6-live-validation.md).
Maintainers must follow the non-negotiable [App release procedure](docs/releasing.md).

## Development commands

```bash
uv sync --all-extras          # install
uv run ambient-ha-mcp         # run locally
uv run pytest                 # unit tests; real HA tests skip by default
uv run ruff check .           # lint
uv run ruff format --check .  # formatting check
uv run mypy                   # type check
docker build -t ambient-ha-mcp .
docker compose up --build
```

Regenerate the dependency lock after an intentional dependency change:

```bash
uv lock
```

## Docker Compose

Copy `.env.example` to `.env`, supply the two required Home Assistant settings,
and run `docker compose up --build`. Compose publishes only to host loopback.

The Docker health probe tests **application liveness**. A temporary Home Assistant
outage changes `/health` to `status: degraded`, but leaves HTTP status 200 so the
orchestrator does not restart a healthy bridge in a loop.

## Documentation

- [Architecture](docs/architecture.md)
- [Security](docs/security.md)
- [Tool contracts](docs/tools.md)
- [Development](docs/development.md)
- [ChatGPT setup and current limitations](docs/chatgpt-setup.md)
- [Home Assistant App installation](docs/home-assistant-app.md)
- [Phase 6.5 validation gate](docs/phase-6-5-validation.md)
- [Phase 6.6 live-validation template](docs/phase-6-6-live-validation.md)
- [Phase 7 safe-control validation](docs/phase-7-validation.md)

## License

MIT. See [LICENSE](LICENSE).