← Back to search

io.github.homeassistant-ai/ha-mcp

homeassistant-ai Scanned 2d ago

Comprehensive Model Context Protocol server for managing Home Assistant through AI assistants.

? Not scanned yet

Versions

3.4.3latest
first seen Jun 5, 2026
7.5.0
first seen May 19, 2026

Tools 16

ha_test_schedule
annotations: none low

monday string
ha_test_union_details
annotations: none low

entity_id string
ha_test_raises_tool_error
annotations: none low

entity_id str
ha_get_logs
annotations: none low

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.

slug string level string limit string order string top_n string offset string search string source string compact bool end_time string entity_id string hours_back string structured string
ha_eval_template
annotations: none low

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/

timeout int template str report_errors bool
ha_get_app
annotations: none low

source str
ha_manage_app
annotations: none low

slug str
ha_real_tool
annotations: none low

x int
ha_test_dict_param
annotations: none low

config dict
ha_test_list_param
annotations: none low

items list
ha_test_int_param
annotations: none low

count int
ha_test_two_params
annotations: none low

items list config dict
ha_test_coerced_dict
annotations: none low

config string
ha_test_union_param
annotations: none low

entity_id string
ha_manage_custom_tool
annotations: none low

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.

ctx Context code string save_as string run_saved string list_saved bool justification string
ha_manage_backup
annotations: none low

Polymorphic backup tool. See the tool description for the routing matrix.

ctx string name string limit string scope string action string domain string confirm string backup_id string entity_id string backup_name string older_than_days string restore_database string

Permissions 5

network medium
Server uses network capabilities via: aiohttp, fetch(), http, httpx, requests, socket, urllib, websocket
filesystem low
Server uses filesystem capabilities via: glob, open(), os, pathlib, shutil, tempfile
shell high
Server uses shell capabilities via: os.system(), spawn(), subprocess
database medium
Server uses database capabilities via: sqlite3
env_vars low
Server uses env_vars capabilities via: os.environ, os.getenv()

Scan Findings 68

low
Tool 'ha_get_app' has no annotations annotation_checker · 100%
low
Tool 'ha_manage_app' has no annotations annotation_checker · 100%
low
Tool 'ha_real_tool' has no annotations annotation_checker · 100%
low
Tool 'ha_test_dict_param' has no annotations annotation_checker · 100%
low
Tool 'ha_test_list_param' has no annotations annotation_checker · 100%
low
Tool 'ha_test_int_param' has no annotations annotation_checker · 100%
low
Tool 'ha_test_two_params' has no annotations annotation_checker · 100%
low
Tool 'ha_test_coerced_dict' has no annotations annotation_checker · 100%
low
Tool 'ha_test_union_param' has no annotations annotation_checker · 100%
low
Tool 'ha_test_schedule' has no annotations annotation_checker · 100%
low
Tool 'ha_test_union_details' has no annotations annotation_checker · 100%
low
Tool 'ha_test_raises_tool_error' has no annotations annotation_checker · 100%
low
Tool 'ha_get_logs' has no annotations annotation_checker · 100%
low
Tool 'ha_eval_template' has no annotations annotation_checker · 100%
low
Tool 'ha_manage_custom_tool' has no annotations annotation_checker · 100%
low
Tool 'ha_manage_backup' has no annotations annotation_checker · 100%
high
Hardcoded OAuth client ID in homeassistant-ai-ha-mcp-01d685d/tests/addon/test_webhook_proxy.py auth_checker · 85%
high
Hardcoded OAuth client secret in homeassistant-ai-ha-mcp-01d685d/tests/addon/test_webhook_proxy.py auth_checker · 95%
high
Hardcoded OAuth client secret in homeassistant-ai-ha-mcp-01d685d/tests/src/unit/test_oidc_entrypoint.py auth_checker · 95%
high
Hardcoded OAuth client secret in homeassistant-ai-ha-mcp-01d685d/tests/src/unit/test_config_flow.py auth_checker · 95%
high
Hardcoded OAuth client secret in homeassistant-ai-ha-mcp-01d685d/tests/src/unit/test_oauth_legacy_component.py auth_checker · 95%
high
Hardcoded OAuth client ID in homeassistant-ai-ha-mcp-01d685d/tests/src/unit/test_oauth_autoapprove.py auth_checker · 85%
high
Hardcoded OAuth client secret in homeassistant-ai-ha-mcp-01d685d/tests/src/unit/test_oauth_autoapprove.py auth_checker · 95%
high
Hardcoded OAuth client secret in homeassistant-ai-ha-mcp-01d685d/tests/src/unit/test_embedded_setup.py auth_checker · 95%
high
Hardcoded OAuth client secret in homeassistant-ai-ha-mcp-01d685d/tests/src/unit/test_oidc_compat.py auth_checker · 95%
high
Hardcoded OAuth client ID in homeassistant-ai-ha-mcp-01d685d/tests/initial_test_state/custom_components/hacs/const.py auth_checker · 85%
high
Hardcoded OAuth client ID in homeassistant-ai-ha-mcp-01d685d/custom_components/ha_mcp_tools/const.py auth_checker · 85%
high
Hardcoded OAuth client secret in homeassistant-ai-ha-mcp-01d685d/custom_components/ha_mcp_tools/const.py auth_checker · 95%
info
Sandbox failed to start for behavioral verification behavioral_verifier · 100%
info
OSV.dev API query failed dependency_analyzer · 100%
info
package.json metadata manifest_parser · 100%
info
pyproject.toml metadata manifest_parser · 100%
info
Tool: ha_get_app manifest_parser · 90%
info
Tool: ha_manage_app manifest_parser · 90%
info
Tool: ha_real_tool manifest_parser · 90%
info
Tool: ha_test_dict_param manifest_parser · 90%
info
Tool: ha_test_list_param manifest_parser · 90%
info
Tool: ha_test_int_param manifest_parser · 90%
info
Tool: ha_test_two_params manifest_parser · 90%
info
Tool: ha_test_coerced_dict manifest_parser · 90%
info
Tool: ha_test_union_param manifest_parser · 90%
info
Tool: ha_test_schedule manifest_parser · 90%
info
Tool: ha_test_union_details manifest_parser · 90%
info
Tool: ha_test_raises_tool_error manifest_parser · 90%
info
Tool: ha_get_logs manifest_parser · 90%
info
Tool: ha_eval_template manifest_parser · 90%
info
Tool: ha_manage_custom_tool manifest_parser · 90%
info
Tool: ha_manage_backup manifest_parser · 90%
info
Transport: streamable-http manifest_parser · 80%
info
Required env vars (118) manifest_parser · 80%
medium
Hardcoded OAuth client ID in homeassistant-ai-ha-mcp-01d685d/tests/addon/test_webhook_proxy.py oauth_scope_analyzer · 80%
high
Deprecated implicit grant flow in homeassistant-ai-ha-mcp-01d685d/tests/addon/test_webhook_proxy.py oauth_scope_analyzer · 85%
high
Deprecated implicit grant flow in homeassistant-ai-ha-mcp-01d685d/tests/src/unit/test_oauth_legacy_component.py oauth_scope_analyzer · 85%
medium
Hardcoded OAuth client ID in homeassistant-ai-ha-mcp-01d685d/tests/src/unit/test_oauth_autoapprove.py oauth_scope_analyzer · 80%
high
Deprecated implicit grant flow in homeassistant-ai-ha-mcp-01d685d/tests/src/unit/test_oauth_autoapprove.py oauth_scope_analyzer · 85%
medium
Hardcoded OAuth client ID in homeassistant-ai-ha-mcp-01d685d/tests/initial_test_state/custom_components/hacs/const.py oauth_scope_analyzer · 80%
medium
Hardcoded OAuth client ID in homeassistant-ai-ha-mcp-01d685d/custom_components/ha_mcp_tools/const.py oauth_scope_analyzer · 80%
info
Sandbox failed to start for output poisoning scan output_poisoning · 100%
medium
Permission: network access detected permission_analyzer · 90%
low
Permission: filesystem access detected permission_analyzer · 90%
high
Permission: shell access detected permission_analyzer · 95%
medium
Permission: database access detected permission_analyzer · 90%
low
Permission: env_vars access detected permission_analyzer · 90%
critical
Invisible Unicode characters in 'ha_manage_custom_tool' poisoning · 92%
critical
Tool poisoning in 'ha_get_app': Cross-tool suppression poisoning · 85%
info
SBOM generated: 682 components sbom_generator · 100%
critical
Database URL with Password found in homeassistant-ai-ha-mcp-01d685d/src/ha_mcp/redaction.py secret_scanner · 85%
medium
No build provenance detected (SLSA L0) slsa_assessor · 90%