Skip to main content
Glama
domoai-mcp

domoai-mcp

by FernanMoreno

DomoAI

Universal agentic domotics runtime with a semantic device model, multi-adapter composition and one general MCP interface.

Development environment

This project uses uv and Python 3.12.

uv sync
uv run pytest
uv run ruff check .
uv run mypy src

The runtime dependencies include the MCP Python SDK, Pydantic, Home Assistant HTTP/WebSocket clients, aiomqtt for the optional Zigbee2MQTT adapter, JSON Schema validation and OR-Tools. Local SQLite persistence uses Python's standard library. Development tools are installed through uv's default dev dependency group.

To add or update a dependency, edit pyproject.toml and regenerate the lockfile:

uv lock
uv sync

Related MCP server: matter-controller-mcp

Local MCP server

The semantic MCP server can be launched over stdio. Without Home Assistant settings it uses the deterministic fixture:

uv run domoai-mcp

Example host configuration:

{
  "mcpServers": {
    "domoai": {
      "command": "uv",
      "args": ["run", "domoai-mcp"],
      "cwd": "/path/to/DomoAI"
    }
  }
}

The same command can be registered in Claude Code, Codex or another compatible MCP client.

Shared network gateway

For multiple agents to operate the same home, run one long-lived gateway process and point every MCP-compatible client at its single Streamable HTTP URL. This is the recommended boundary for Codex, Claude, Gemini, OpenCode and other clients; it gives them one registry, scheduler, approval store and physical authority instead of one runtime per client:

uv run domoai-mcp-gateway

The bind and URL are configured with DOMOAI_MCP_HOST, DOMOAI_MCP_PORT, DOMOAI_MCP_PATH and DOMOAI_MCP_PUBLIC_URL. A non-loopback bind requires DOMOAI_MCP_CLIENT_TOKEN_FILE and an HTTPS public URL. See deploy/README.md for native, WSL, Windows and Docker deployment, including the Home Assistant/MQTT stack and the external Windows KNX Virtual topology.

The shared gateway fails closed when no concrete provider is configured; it does not silently fall back to the deterministic simulator. The explicit local stdio fixture remains available for development and tests. Authenticated MCP clients can inspect domotics://runtime for active providers, writable routes and sanitized authority status; this resource is descriptive and never grants physical execution permission.

Each client uses the same URL with its own bearer token. Tokens authenticate an agent but do not grant human consent; sensitive mutations still require the existing operator approval and safety gates. Kubernetes is not required for a single home and active-active gateway replicas are intentionally unsupported.

Unified MCP surface

The single domoai-mcp server exposes discovery, state, energy context, policy-aware plan validation/execution and the proposal-only OR-Tools tools validate_scenario, optimize_scenario and explain_solution through the same MCP session. Register exactly one server in Claude Code, Codex or any other compatible MCP client that supports local stdio:

{
  "mcpServers": {
    "domoai": {
      "command": "uv",
      "args": ["run", "domoai-mcp"],
      "cwd": "/path/to/DomoAI"
    }
  }
}

OR-Tools remains an internal proposal/validation/explanation layer. It cannot execute a device, approve a plan or call an adapter, and there is no second public OR-Tools MCP endpoint.

The portable optimize-home-energy skill routes every DomoAI operation through one mcp role. Its reference workflow is validated locally with deterministic in-process fixtures:

uv run pytest -q tests/contract/test_skill_contract.py
uv run pytest -q tests/integration/test_energy_skill_workflow.py

The workflow uses the same connection for semantic reads, proposals, explanations and plan validation. The published v4 skill hands every mutation to commit_or_schedule_bundle; it never calls an adapter directly. Sensitive bundles pause for explicit operator approval. The local catalog also contains EV, thermal-comfort, solar, battery, night and vacation procedures plus read-only device-diagnostics and commission-new-device; all nine contracts are validated offline with load_core_catalog().

For energy-aware scenarios, the portable v4 procedure reads a complete typed context through mcp.get_energy_context before calling the proposal-only optimizer. The context aligns tariffs and solar forecasts to a fixed horizon and may include one battery profile. CP-SAT returns cost, peak-import and solar-self-consumption evidence plus per-slot energy balance; it never calls a physical adapter. Context failure, revision mismatch, infeasibility or solver timeout stops before validation and execution. The deterministic provider and focused acceptance commands are covered by the repository contract and integration tests.

One-time solar profile for live energy data

OMIE tariffs and Open-Meteo forecasts are collected automatically whenever the energy context is requested. Only the physical installation metadata needs to be supplied once. Copy the example, replace its placeholder values with the inverter or installer data, and point the runtime at it:

cp config/solar-profile.example.json config/solar-profile.json
export DOMOAI_ENERGY_LIVE=1
export DOMOAI_TARIFF_PROVIDER=omie
export DOMOAI_SOLAR_PROVIDER=open_meteo
export DOMOAI_SOLAR_PROFILE_PATH=config/solar-profile.json
uv run domoai-mcp

The profile is strict, versioned and credential-free. It must contain real installation values before using the result for optimization; the example's Madrid values only document the shape. The older individual DOMOAI_SOLAR_* variables remain available as a mutually exclusive compatibility fallback.

Physical battery dispatch is software-qualified when its server-owned binding is configured. It becomes hil-qualified only with matching complete inverter evidence. Set DOMOAI_BATTERY_DISPATCH_PRODUCTION=1 together with DOMOAI_BATTERY_HIL_EVIDENCE_PATH only after the opt-in HIL run has passed; the runtime fails closed otherwise. A deterministic test run never certifies real hardware.

Dispatchable battery control is opt-in through a complete, server-owned canonical profile:

export DOMOAI_BATTERY_DISPATCH_PROFILE_PATH=config/dispatchable-battery-profile.json
uv run domoai-mcp

The profile is strict v1 JSON and must contain the canonical device, actuator, feedback, SOC and capacity evidence. A mapping declaration alone never enables physical dispatch; live energy mode, the profile and the runtime safety gates are all required.

Universal Provider SDK

Future Home Assistant, inverter and MQTT integrations must translate their source-specific identities and payloads into the Provider SDK v1 boundary before reaching the semantic runtime. The SDK reuses DomoAI's canonical DeviceType, Capability and SourceRef models and separates providers into telemetry and command roles:

external provider
      ↓
ProviderManifest + DeviceDescriptor + Measurement
      ↓
ProviderRegistry (stable order, safe diagnostics)
      ↓
canonical runtime / StateStore / MCP / OR-Tools

Provider commands carry only bounded semantic parameters and an idempotency key. They do not bypass PlanService, policy validation or AdapterPort. The first concrete implementation is HomeAssistantProvider. It reuses the authenticated REST/WebSocket client, groups entities by Home Assistant device_id when registry metadata is available, and exposes only explicit entity/capability metric mappings. It is the single Home Assistant integration path: the provider is registered in ProviderRegistry and wrapped by HomeAssistantProviderAdapter, so DeviceRegistry, StateStore, plan execution and MCP keep one semantic path and one Home Assistant client. See docs/adapter-sdk.md and docs/contracts.md for the public boundary.

For a known MQTT, REST/OpenAPI, Modbus, KNX or Matter product, use a declarative connector recipe: docs/documentation/README.md is the complete documentation index, and docs/connector-recipes.md is the compact technical reference. The recipe generator creates the provider package and its manifest; deployment supplies endpoint and credentials through the normal connection control plane. The agent still sees only normalized devices, capabilities, state and semantic commands.

Typed Modbus, KNX and Matter profiles compile into the existing native adapters with explicit register, group-address or endpoint/cluster/attribute allowlists. Use the five-method custom provider SDK only for protocols or product behavior that cannot be represented by a supported recipe; secret-free examples are in examples/connectors/. Local OAuth readiness is checked with uv run domoai-admin oauth preflight --env-file deploy/gateway.env; external application registration and physical device qualification remain operator gates.

Live Home Assistant runtime

Para desarrollo local sin hardware, el laboratorio virtual reproducible está en dev/lab/README.md y su arranque mínimo cubre Mosquitto/fake Zigbee2MQTT y PyModbus. Home Assistant, Matter Server y KNX Virtual/ETS permanecen como perfiles manuales opt-in.

La ruta recomendada para operar ese laboratorio es el runner explícito:

uv run domoai-lab up
uv run domoai-lab status
uv run domoai-lab smoke

El smoke usa únicamente fixtures locales de Home Assistant, MQTT/Zigbee2MQTT, Modbus, Matter y KNX; no inventa gateways, tokens ni commissioning. Los smoke tests live siguen separados y requieren sus servicios y variables DOMOAI_* reales.

The composition root selects a direct adapter for one source or a composite runtime for two or more complete source configurations. The explicit fixture entry point is reserved for tests and local development. Configure Home Assistant with:

export DOMOAI_HOME_ASSISTANT_URL="http://home-assistant.local:8123"
export DOMOAI_HOME_ASSISTANT_TOKEN="<long-lived-access-token>"
export DOMOAI_HOME_ASSISTANT_MAPPING_PATH="config/home-assistant-mappings.json"
export DOMOAI_DATABASE_PATH="data/domoai.sqlite3"
uv run domoai-mcp

The domoai-mcp launcher requires an explicit live provider configuration and fails closed otherwise. The deterministic stdio fixture is available only for protocol tests or local development with DOMOAI_RUNTIME_MODE=fixture.

The provider path is the configured Home Assistant runtime. The URL/token pair is required and an optional strict v1 mapping document can make energy roles explicit:

{
  "schema_version": "v1",
  "metric_mappings": {
    "sensor.pv_power": {"power": "energy.pv.power"},
    "sensor.grid_power": {"power": "energy.grid.power"}
  }
}

The runtime authenticates REST service calls, persists plans, outcomes and redacted audit events in SQLite, and runs the adapter event consumer in the background. Supported write mappings currently include light/switch power and toggle operations, light brightness, cover position/open/close/stop and climate target temperature. An incomplete URL/token pair is rejected before startup. Tokens are read as secret configuration and are never included in device, command, outcome or audit payloads.

The Provider SDK path can be exercised independently of the runtime factory:

provider = HomeAssistantProvider(
    HomeAssistantClient(base_url, token),
    metric_mappings={
        "sensor.pv_power": {"power": "energy.pv.power"},
        "sensor.battery_soc": {"battery": "battery.soc"},
    },
)

Only mapped sensor capabilities become canonical energy metrics. The client also reads Home Assistant's enabled entity registry over WebSocket when state payloads do not include device_id; registry identity is preserved when provided, never inferred from names or areas.

The provider path is covered by deterministic fixtures. The opt-in live provider-runtime smoke validates the same route against a real Home Assistant instance without executing commands:

uv run pytest -q tests/integration/test_home_assistant_provider_smoke.py

It requires a real URL/token pair and keeps the token outside the repository.

Live Zigbee2MQTT runtime

The native Zigbee2MQTT adapter is opt-in and supports the bounded v1 profile: light/switch power, light brightness, temperature, humidity and occupancy. Configure it alongside Home Assistant or another source:

export DOMOAI_ZIGBEE2MQTT_URL="mqtt://mqtt-broker.local:1883"
export DOMOAI_ZIGBEE2MQTT_BASE_TOPIC="zigbee2mqtt"
export DOMOAI_MQTT_TIMEOUT_SECONDS="5"
export DOMOAI_MQTT_USERNAME="domoai"
export DOMOAI_MQTT_PASSWORD="<mqtt-password>"
uv run domoai-mcp

Generic MQTT/ESP devices use a separate strict declarative mapping:

export DOMOAI_GENERIC_MQTT_URL="mqtt://mqtt-broker.local:1883"
export DOMOAI_GENERIC_MQTT_MAPPING_PATH="config/generic-mqtt.json"

For read-only Home Assistant MQTT Discovery preview/persistence, configure its broker independently; persistence still requires explicit operator confirmation:

export DOMOAI_GENERIC_MQTT_DISCOVERY_URL="mqtt://mqtt-broker.local:1883"
export DOMOAI_GENERIC_MQTT_DISCOVERY_PREFIX="homeassistant"

Zigbee2MQTT may run alongside Home Assistant or another configured source. The adapter consumes Zigbee2MQTT bridge/device topics and publishes only mapped device /set commands through the existing plan, policy and executor boundary. Pairing, removal, OTA, groups, bridge administration and arbitrary MQTT publishing are not exposed.

Live Matter Server runtime

The native Matter adapter uses Matter Server as the controller boundary and connects to its compatible WebSocket endpoint. Configure it alongside Home Assistant, Zigbee2MQTT or another source:

export DOMOAI_MATTER_SERVER_URL="ws://matter-server.local:5580/ws"
export DOMOAI_MATTER_TIMEOUT_SECONDS="5"
uv run domoai-mcp

The adapter validates the server schema range before discovery, preserves node:<node_id>/endpoint:<endpoint_id> source references and exposes only the bounded v1 light/switch power and brightness profile plus read-only temperature, humidity and occupancy state. Commissioning, fabric management, OTA, groups, vendor clusters and arbitrary attribute operations remain outside the agent-facing boundary. Live Matter smoke tests are opt-in; fixture tests need no Matter server or hardware.

Live KNX/IP runtime

The native KNX adapter uses an explicit mapping file rather than inferring devices from arbitrary group traffic. Its bounded v1 profile supports light and switch power, light brightness, and read-only temperature, humidity and occupancy. Configure it alongside the other physical sources:

export DOMOAI_KNX_GATEWAY_HOST="knx-gateway.local"
export DOMOAI_KNX_CONFIG_PATH="config/knx.json"
export DOMOAI_KNX_TIMEOUT_SECONDS="5"
uv run domoai-mcp

The mapping file declares each entity, semantic capability, state group address, command group address and DPT. Unknown fields, malformed addresses, unsupported DPTs and writable sensor mappings are rejected at startup. KNX/IP tunnelling is optional and can coexist with the other configured adapters; fixture tests use an in-memory transport and require no gateway or hardware. P2 adds a bounded, review-before-write ETS XML/.knxproj importer and xknx-backed IP Secure/Data Secure configuration through opaque FileSecretStore references. Secure profiles fail closed and never fall back to ordinary tunnelling.

The management API also supports external Home Assistant OAuth Authorization Code + PKCE, encrypted token refresh/revocation and explicit migration from a long-lived token. DomoAI remains an OAuth client, not an authorization server. Repair plans are deterministic and typed; only retry runs without confirmation, while configuration, trust and credential actions require an operator confirmation.

Adding another connector

Connector authors can start from a complete, safe scaffold:

uv run domoai-admin connector create \
  --connector-id my_connector \
  --display-name "My Connector" \
  --protocol my-protocol \
  --output ./my-connector
uv run domoai-admin connector check ./my-connector

The scaffold supplies the five provider entry points and an explicit provider_adapter_not_implemented check until the real protocol adapter is added. Setup fields are declared once in the provider manifest; the generic management UI renders them from GET /api/v1/connection-providers, so adding a connector does not require adding another UI page. See docs/connection-provider-sdk.md.

Live Modbus TCP runtime

The native Modbus adapter uses an explicit v1 mapping of unit IDs, register areas, zero-based PDU offsets and scalar encodings. It supports light/switch power, light brightness, and read-only temperature, humidity and occupancy. Configure it alongside the other physical sources:

export DOMOAI_MODBUS_HOST="modbus-controller.local"
export DOMOAI_MODBUS_PORT="502"
export DOMOAI_MODBUS_CONFIG_PATH="config/modbus.json"
export DOMOAI_MODBUS_PROFILE_CATALOG_PATH="data/modbus-profiles"
export DOMOAI_MODBUS_TIMEOUT_SECONDS="5"
export DOMOAI_MODBUS_POLL_INTERVAL_SECONDS="5"
uv run domoai-mcp

The mapping is strict and does not scan or infer devices. Unknown fields, ambiguous 40001-style addresses, unsupported encodings, writable sensors and unsafe commands are rejected. Modbus TCP is opt-in and can coexist with Home Assistant, Zigbee2MQTT, Matter Server and KNX. RTU/ASCII, TLS, scanning, vendor function codes and arbitrary register reads/writes are outside v1. Fixture tests use an in-memory transport and require no controller or hardware.

Multi-adapter identity and routing

The runtime follows the Home Assistant device/entity distinction: one physical source device may expose multiple source entities, while DomoAI presents one canonical device with capability-level routes. Stable source identifiers and connections preserve identity across name or area changes; an explicit canonical_id is required to link contributions from different adapters. Commands are resolved to one exact source entity before execution. Ambiguous, unknown or unavailable routes fail closed, so the runtime never silently sends a command to another protocol or entity.

No live gateway, broker or controller is required for this behavior. The deterministic multi-adapter fixture covers composition, partial failure, topology, exact routing and zero-write safety:

uv run pytest -q tests/contract/test_multi_adapter_runtime.py \
  tests/integration/test_multi_adapter_runtime.py \
  tests/performance/test_multi_adapter_targets.py

Verified local validation

On 2026-08-17 the repository passed the unit, adapter, discovery, plan, MCP-contract, optimization, performance, Home Assistant execution, KNX and Modbus fixture, runtime composition, OMIE and Open-Meteo provider scenarios covered by the repository test suite. The Home Assistant classic-adapter smoke passed against the local Docker lab; the local Zigbee2MQTT and Modbus smokes passed; and the read-only OMIE and Open-Meteo public-network smokes passed with opt-in configuration. Matter discovery and KNX/IP remain optional because they require a commissioned Matter node or a reachable KNX gateway and mapping.

The local launch command is:

uv run domoai-mcp

The quality gates are:

uv run pytest -q
uv run ruff check .
uv run mypy src
uv lock --check

The latest full-suite result is recorded in the SHA-tagged CI evidence artifact (ci-evidence-${GITHUB_SHA}), rather than hardcoded here. The skips are opt-in Matter Server, KNX/IP and other live cases without their external node, gateway or service configuration; deterministic fixture coverage remains enabled. No live gateway or hardware result is claimed by this run; the Home Assistant inverter HIL smoke remains opt-in and was not executed because its credentials and hardware were unavailable. The FastMCP compatibility seam keeps the known pydantic_settings incomplete-field warning out of the MCP contracts without globally suppressing warnings.

Universal adapter and public contract guidance lives in docs/adapter-sdk.md and docs/contracts.md. The current connector coverage, including the distinction between native connectors, Home Assistant mediation, internal extensions, fixtures and external qualification, is recorded in docs/adapter-coverage.md. The versioned program status and external gates are listed in docs/program-status-2026-09-06.md.

Available Tools

20 tools
cancel_recurring_scheduleB
Destructive

Cancel a recurring schedule; stops all future occurrences.

Inputs: schedule_id (required). Returns: object

cancel_scheduled_planA
Destructive

Cancel a pending scheduled plan before it executes.

Inputs: plan_id (required). Returns: object

commit_or_schedule_bundleC
Destructive

Commit one validated bundle through the runtime-owned physical execution and scheduling boundary.

Inputs: members (required), scenario_id (required), bundle_digest (required). Returns: object

discover_devicesB
Read-only

Read or refresh the canonical semantic device inventory.

Inputs: types, area_id, refresh. Returns: object

execute_planB
Destructive

Execute a previously validated plan after runtime safety checks.

Inputs: plan_id (required), validation_digest (required), dry_run, approval_id, +1 more. Returns: object

explain_solutionA
Read-only

Explain a typed optimization result without changing runtime state.

Inputs: result (required). Returns: object

get_energy_contextB
Read-only

Read a complete canonical energy context for one requested horizon.

Inputs: horizon (required). Returns: object

get_stateC
Read-only

Read bounded semantic state snapshots for selected devices.

Inputs: devices (required), allow_stale, capabilities. Returns: object

inspect_commissioningA
Read-only

Read the shared, non-authoritative commissioning report for future battery and EV bindings.

Inputs: refresh, asset_types. Returns: object

list_audit_eventsA
Read-only

Query the bounded, filterable audit trail of runtime decisions.

Inputs: limit, since, event_type, subject_id. Returns: object

list_recurring_schedulesA
Read-only

List active recurring schedules and their next occurrence.

Inputs: no named parameters. Returns: object

list_scheduled_plansA
Read-only

List plans currently pending their scheduled execution time.

Inputs: no named parameters. Returns: object

optimize_scenarioA
Read-only

Compute a deterministic proposal without executing physical commands.

Inputs: scenario (required), validate_proposal. Returns: object

request_approvalA
Destructive

Issue a server-authoritative approval grant for a plan requiring confirmation. A trusted host may inject an authenticated operator principal. The optional operator_token exists only for explicitly enabled local/dev compatibility mode; the caller never supplies the recorded operator identity. The returned approval_id is single-use and bound to the plan's current validation digest.

Inputs: plan_id (required), validation_digest (required), bundle_digest, operator_token, +1 more. Returns: object

reschedule_planA
Destructive

Request a temporal revision for a pending plan. The legacy generic mutation is fail-closed; a changed time must be validated and approved again before admission.

Inputs: plan_id (required), execute_at (required), schedule_revision, validation_digest. Returns: object

schedule_planA
Destructive

Schedule a previously validated/approved plan to execute at a future time, instead of immediately. The plan still goes through every existing safety check when its time arrives.

Inputs: plan_id (required), execute_at (required), validation_digest (required), approval_id, +1 more. Returns: object

schedule_recurring_planA
Destructive

Schedule a plan's commands to run repeatedly at a fixed local time, optionally restricted to specific weekdays. Creating a standing automation is its own authority act, distinct from running the commands once: if the template plan currently requires confirmation, an approval_id from request_approval is required to create the schedule at all. Every occurrence is still independently revalidated against live state before it executes; an occurrence requiring confirmation at run time is skipped and audited, never auto-approved, and recurrence continues to its next scheduled time. An optional expires_at bounds how long the automation stays active.

Inputs: plan_id (required), timezone (required), time_of_day (required), expires_at, +2 more. Returns: object

validate_commandB
Read-only

Validate one semantic command without invoking an adapter.

Inputs: command (required), agent_request_id. Returns: object

validate_planC
Read-only

Validate a semantic plan and return its policy decisions and digest.

Inputs: plan (required), mode. Returns: object

validate_scenarioB
Read-only

Validate an optimization scenario against canonical devices and capabilities.

Inputs: scenario (required). Returns: object

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    B3.2/5.0

    Scored across 20 tools

    Disambiguation3/5

    Most tools target distinct lifecycle stages, but execute_plan, schedule_plan, and commit_or_schedule_bundle have overlapping boundaries since 'bundle' is never clearly separated from 'plan.' The validate_* and schedule/cancel pairs are otherwise reasonably distinguishable with the help of their descriptions.

    Naming Consistency4/5

    The set consistently uses snake_case verb_noun names such as get_state, validate_plan, list_scheduled_plans, and cancel_recurring_schedule. The main deviation is commit_or_schedule_bundle, which combines two actions in one name, but overall the pattern is predictable.

    Tool Count3/5

    At 20 tools, the server sits in the heavier range, and the plan/bundle/schedule/recurring variations add conceptual surface area even if the domain is broad. Most tools earn their place for a full energy-management workflow, but the count is borderline and would benefit from consolidation.

    Completeness4/5

    The surface covers the core lifecycle well: validation, approval, execution, one-time scheduling, recurring schedules, cancellation, audit, and optimization scenarios. Minor gaps exist, such as no direct way to retrieve a single plan/approval or inspect raw capabilities, but agents can work around these.

    Maintenance

    ActivityMaintained
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • F
      license
      A
      quality
      D
      maintenance
      Enables control and management of smart home devices across multiple rooms through specialized MCP servers. Supports lights, thermostats, fans, and ovens with room-specific rules and automatic persistence.
      5
      -
    • A
      license
      Not graded
      quality
      D
      maintenance
      A powerful MCP server that enables AI assistants to discover, commission, and control Matter-compatible smart home devices through a standardized interface.
      20 npm
      9
      MIT
    • A
      license
      B
      quality
      D
      maintenance
      Enables control of ECHONETLite home automation devices like air conditioners and sensors via MCP, supporting HVAC management and real-time monitoring.
      14
      1
      MIT