io.github.homeassistant-ai/ha-mcp
Comprehensive Model Context Protocol server for managing Home Assistant through AI assistants.
Versions
3.4.3latest7.5.0Tools 16
ha_test_schedule ha_test_union_details ha_test_raises_tool_error ha_get_logs Get Home Assistant logs from various sources. **Sources:** - "logbook" (default): Entity state change history with pagination - "system": Structured system log entries (errors, warnings) via system_log/list - "error_log": Raw log text (home-assistant.log on container/pip installs; HA Core's journald stream on Supervisor-backed installs) - "supervisor": Add-on container logs (requires slug = add-on slug) - "system_service": HA-Supervisor-managed system service logs (requires slug ∈ {supervisor, host, core, dns, audio, cli, multicast, observer}) - "logger": Effective log level per integration via logger/log_info (confirms logger.set_level changes took effect) **Prefer source='system' for triage.** It returns HA's own deduplicated system_log entries with counts, first_occurred and full tracebacks; of those only the tracebacks are unrecoverable from the structured error_log summary — they are present in the raw text, so structured=False gets them back. Its counts also run since each error first occurred, while structured error_log counts only what is inside the fetched window (reported as window_start/window_end; Supervisor-backed installs read a capped journald slice). Use error_log with structured=True for entries below system_log's WARNING+ ~50-entry cap, or for the per-component rollup. **Shared params:** limit, search (keyword filter on entries/lines; matches integration domain for source='logger') **Order:** order='newest' (default) returns most-recent first; order='oldest' returns chronological-first. Applies to all time-ordered sources (logbook, system, error_log, supervisor, system_service); ignored for source='logger' and for error_log with structured=True. For raw-text sources (error_log, supervisor, system_service) it sets the read direction of the most-recent window. **Logbook params:** hours_back, entity_id, end_time, offset, compact (default True — strips attribute dicts to save context) **System/error_log params:** level (ERROR, WARNING, INFO, DEBUG, CRITICAL) **error_log params:** structured, top_n. In structured mode `search` matches the message and logger name only, whereas on the raw path it matches the whole line; `limit`/`order` do not apply, and issues are ranked by count, then severity, then recency. **Supervisor params:** slug = add-on slug, e.g. "core_mosquitto" (use ha_get_app() to list installed slugs) **System-service params:** slug = service name. The slug "supervisor" here means the Supervisor service's own logs, NOT an add-on with that name — the source param disambiguates.
ha_eval_template Evaluate Jinja2 templates using Home Assistant's template engine. This tool allows testing and debugging of Jinja2 template expressions that are commonly used in Home Assistant automations, scripts, and configurations. It provides real-time evaluation with access to all Home Assistant states, functions, and template variables. **When NOT to use this for automation/script logic:** Templates have legitimate uses (notification bodies, dynamic `data.*` values, debugging existing templates), but `condition:` / `trigger:` positions and action service names are better expressed as native HA constructs: native constructs are schema-validated at config load and surface structural errors loudly, whereas equivalent template logic only errors at runtime — and a template that renders a non-truthy value is silently treated as false. Prefer: - `condition: numeric_state` over `{{ states('x') | float > N }}` - `condition: state` over `{{ is_state(...) }}` - `condition: time` / `condition: sun` over `now().hour` / `is_state('sun.sun', ...)` - Native `for:` field on state/numeric_state triggers and state conditions over `{{ now() - X.last_changed > timedelta(...) }}` duration math - `choose` action over templated `service:` / `action:` strings See `ha_get_skill_guide` (best-practices skill) for the full anti-pattern list. **When to use (reach for this tool, don't compute it yourself):** Any one-shot question whose answer is DERIVED from current HA state — an average/sum/min/max across sensors, a count of entities matching a condition, a boolean comparison, or a rendered message with live values. One render call beats fetching N states and doing the math yourself, and it is the canonical way to *test* a template before embedding it. This is for one-shot answers and template testing only — NOT for putting templates into automation logic; for `condition:` / `trigger:` positions native constructs win. - "average temperature across the bedroom sensors" -> `{{ ([states('sensor.a'), states('sensor.b')] | map('float', 0) | sum) / 2 }}` - "how many lights are on" -> `{{ states.light | selectattr('state', 'eq', 'on') | list | count }}` NOT for a plain single-entity value ("what's the state of X") — that is `ha_get_state` / `ha_search`; rendering `{{ states('X') }}` there is over-use. **Parameters:** - template: The Jinja2 template string to evaluate - timeout: Maximum evaluation time in seconds (default: 3) - report_errors: Whether to return detailed error information (default: True) **Common Template Functions:** **State Access:** ```jinja2 {{ states('sensor.temperature') }} # Get entity state value {{ states.sensor.temperature.state }} # Alternative syntax {{ state_attr('light.bedroom', 'brightness') }} # Get entity attribute {{ is_state('light.living_room', 'on') }} # Check if entity has specific state ``` **Numeric Operations:** ```jinja2 {{ states('sensor.temperature') | float(0) }} # Convert to float with default {{ states('sensor.humidity') | int(0) }} # Convert to integer with default {{ (states('sensor.temp') | float(0) + 5) | round(1) }} # Math operations ``` **Time and Date:** ```jinja2 {{ now() }} # Current datetime {{ now().strftime('%H:%M:%S') }} # Format current time {{ as_timestamp(now()) }} # Convert to Unix timestamp {{ now().hour }} # Current hour (0-23) {{ now().weekday() }} # Day of week (0=Monday) ``` **Conditional Logic (for display strings — not for `condition:` positions):** ```jinja2 {{ 'Day' if now().hour < 18 else 'Night' }} # Ternary operator {% if is_state('alarm_control_panel.home', 'armed_away') %} Alarm is armed {% else %} Alarm is disarmed {% endif %} ``` **Lists and Loops:** ```jinja2 {% for entity in states.light %} {{ entity.entity_id }}: {{ entity.state }} {% endfor %} {{ states.light | selectattr('state', 'eq', 'on') | list | count }} # Count on lights ``` **String Operations:** ```jinja2 {{ states('sensor.weather') | title }} # Title case {{ 'Hello ' + states('input_text.name') }} # String concatenation {{ states('sensor.data') | regex_replace('pattern', 'replacement') }} ``` **Device and Area Functions:** ```jinja2 {{ device_entities('device_id_here') }} # Get entities for device {{ area_entities('living_room') }} # Get entities in area {{ device_id('light.bedroom') }} # Get device ID for entity ``` **Common Use Cases (legitimate template positions):** **Dynamic Service Data:** ```jinja2 # Dynamic brightness based on time {{ 255 if now().hour < 22 else 50 }} # Message with current values "Temperature is {{ states('sensor.temp') }}°C, humidity {{ states('sensor.humidity') }}%" ``` **Examples:** **Test basic state access:** ```python ha_eval_template("{{ states('light.living_room') }}") ``` **Test a string expression (e.g. for a notification body):** ```python ha_eval_template("{{ 'Day' if now().hour < 18 else 'Night' }}") ``` **Test mathematical operations:** ```python ha_eval_template("{{ (states('sensor.temperature') | float(0) + 5) | round(1) }}") ``` **Test entity counting:** ```python ha_eval_template("{{ states.light | selectattr('state', 'eq', 'on') | list | count }}") ``` **IMPORTANT NOTES:** - Templates have access to all current Home Assistant states and attributes - Use this tool to test templates before using them in automations or scripts - Template evaluation respects Home Assistant's security model and timeouts - Complex templates may affect Home Assistant performance - keep them efficient - Use default values (e.g., `| float(0)`) to handle missing or invalid states **For template documentation:** https://www.home-assistant.io/docs/configuration/templating/
ha_get_app ha_manage_app ha_real_tool ha_test_dict_param ha_test_list_param ha_test_int_param ha_test_two_params ha_test_coerced_dict ha_test_union_param ha_manage_custom_tool Create and run a custom tool in a sandbox, or manage saved custom tools. ⚠️ **LAST RESORT** — search for existing tools first. **Modes** (mutually exclusive): - Provide ``code`` + ``justification`` to execute custom code - Set ``run_saved`` to re-run a previously saved tool by name - Set ``list_saved=True`` to list all saved tools **Available functions in sandbox:** - ``api_get(endpoint)`` — GET request to HA REST API - ``api_post(endpoint, data)`` — POST request to HA REST API - ``ws_send(message)`` — send a HA WebSocket command (e.g. registry lookups, ``render_template``, dashboard ops). ``message`` must include a ``"type"`` field; the MCP server adds ``id`` and handles auth. - ``call_tool(name, args)`` — call a registered MCP tool - ``delete_saved_tool(name)`` — remove a previously saved custom tool by name. Returns ``{"deleted": True, "name": name}`` or ``{"error": ...}``. Use ``api_get``/``api_post`` for REST operations not covered by existing tools. Use ``ws_send`` when the operation is only available over the Home Assistant WebSocket API (most registry CRUD, template rendering, and Lovelace operations). Use ``call_tool`` when an existing tool already does what you need. Use ``delete_saved_tool`` to clean up saved tools you no longer need. Saved tools persist across server restarts when ``CODE_MODE_SAVED_TOOLS_PATH`` is set (the addon sets this by default to ``/data/saved_tools.json``). Example — check repairs (no built-in tool for this): ```python repairs = await api_get("/repairs/issues") repairs ``` Example — list areas via WebSocket: ```python result = await ws_send({"type": "config/area_registry/list"}) result.get("result", []) ``` Example — chain existing tools: ```python result = await call_tool("ha_search", {"query": "light", "limit": 5}) data = result.get("data", result) lights = data.get("results", []) for e in lights: await call_tool("ha_call_service", { "domain": "light", "service": "turn_off", "entity_id": e["entity_id"]}) {"turned_off": len(lights)} ``` Example — delete an obsolete saved tool: ```python delete_saved_tool("old_movie_mode") ``` Args: code: Python code to execute. Last expression is the return value. justification: Why no existing tool works (required with code). save_as: Save the tool under this name for reuse (alphanumeric/underscores, max 64 chars). run_saved: Name of a previously saved tool to re-run. list_saved: Set True to list all saved tools.
ha_manage_backup Polymorphic backup tool. See the tool description for the routing matrix.
Permissions 5
network medium filesystem low shell high database medium env_vars low