Skip to main content
Glama
homeassistant-ai

Home Assistant MCP Server

Official

Create or Update Automation

ha_config_set_automation
Destructive

Create, update, or take control of Home Assistant automations. Use full configs, Python transforms, or blueprint inputs to define triggers, conditions, and actions.

Instructions

Create or update a Home Assistant automation.

MUST call ha_get_skill_guide OR refer to your locally installed skills first.

PREFER NATIVE SOLUTIONS OVER TEMPLATES (read this before writing any {{ ... }}): Native triggers/conditions/actions are validated at config load, fail loudly, and do not bypass HA's schema. Templates fail silently at runtime and obscure intent.

  • condition: numeric_state instead of {{ states('x') | float > N }}

  • condition: state (with state: list) instead of {{ is_state(...) }} / {{ states(x) in [...] }}

  • condition: time instead of {{ now().hour ... }} or {{ now().weekday() ... }}

  • condition: sun instead of {{ is_state('sun.sun', ...) }}

  • Native for: field on state/numeric_state triggers and state conditions over {{ now() - X.last_changed > timedelta(...) }} duration math.

  • wait_for_trigger instead of wait_template

  • choose action instead of template-based service names

  • For one-shot date firing, use a time trigger plus automation.turn_off on a hardcoded entity_id — not {{ now().date() ... }}.

  • Hardcode target.entity_id literals — never {{ this.entity_id }}. Templates are appropriate ONLY in data.* fields, notification message/title, event_data, and variables. The reactive best-practice checker on this tool will surface anything in a logic position that should be native; consult the best_practice_warnings field on the response and fix before re-submitting. The relevant skill section is auto-embedded under skill_content on warnings, and the full automation-patterns.md + template-guidelines.md references ship under skill_content proactively by default. For comprehensive guidance beyond that, call ha_get_skill_guide.

The returned automation_id is the resolved entity_id (canonical form, e.g. automation.morning_routine) when entity registration succeeds, falling back to the input identifier (update path) or the generated unique_id from the upsert response (fresh create when no identifier was passed).

Before reaching for ha_config_set_automation, consider whether a dedicated tool fits the use case better:

  • State snapshot of one or more entities (capture-then-replay, no trigger needed) -> ha_config_set_scene

  • State-derived value that recomputes when its inputs change (template sensor / binary sensor / number / select) -> ha_config_set_helper(helper_type='template')

  • Stateful counter / timer / schedule / boolean / etc. -> ha_config_set_helper(helper_type='counter' | 'timer' | ...)

Supports three modes: full config replacement, Python transformation, or take_control_of_blueprint (see below).

WHEN TO USE WHICH MODE:

  • python_transform: RECOMMENDED for edits to existing automations. Surgical updates.

  • config: Use for creating new automations or full restructures.

  • take_control_of_blueprint: converts a blueprint-backed automation into a standalone one. Takes no config of its own.

IMPORTANT: python_transform requires 'identifier' and 'config_hash' from ha_config_get_automation().

PYTHON TRANSFORM EXAMPLES (operate on the fetched config, which uses HA's canonical plural root keys 'triggers'/'actions'/'conditions'):

  • Update action: python_transform="config['actions'][0]['data']['brightness'] = 255"

  • Add trigger: python_transform="config['triggers'].append({'trigger': 'state', 'entity_id': 'binary_sensor.motion', 'to': 'on'})"

  • Remove last action: python_transform="config['actions'].pop()"

Omit identifier and config['id'] to create a new automation with a generated ID. A previously unused raw ID can also create an automation with that specific ID. Reusing an identifier targets the same automation, even if the alias changes. To intentionally rename or replace it, first read it with ha_config_get_automation and pass its config_hash. A changed alias without that hash is rejected before writing.

AUTOMATION TYPES:

  1. Regular Automations - Define triggers and actions directly

  2. Blueprint Automations - Use pre-built templates with customizable inputs

REQUIRED FIELDS (Regular Automations):

  • alias: Human-readable automation name

  • triggers: List of triggers (time, state, event, etc.)

  • actions: List of actions to execute

REQUIRED FIELDS (Blueprint Automations):

  • alias: Human-readable automation name

  • use_blueprint: Blueprint configuration

    • path: Blueprint file path (e.g., "motion_light.yaml")

    • input: Dictionary of input values for the blueprint

OPTIONAL CONFIG FIELDS (Regular Automations):

  • description: Detailed description of the user's intent (RECOMMENDED: helps safely modify implementation later)

  • category: Category ID for organization (use ha_config_get_category to list, ha_config_set_category to create)

  • conditions: Additional conditions that must be met

  • mode: 'single' (default), 'restart', 'queued', 'parallel'

  • max: Maximum concurrent executions (for queued/parallel modes)

  • initial_state: Whether automation starts enabled (true/false)

  • variables: Variables for use in automation

BASIC EXAMPLES:

Simple time-based automation: ha_config_set_automation(config={ "alias": "Morning Lights", "description": "Turn on bedroom lights at 7 AM to help wake up", "triggers": [{"trigger": "time", "at": "07:00:00"}], "actions": [{"action": "light.turn_on", "target": {"area_id": "bedroom"}}] })

Motion-activated lighting — for: on the off-transition replaces action-delay: ha_config_set_automation(config={ "alias": "Motion Light", "triggers": [ {"trigger": "state", "entity_id": "binary_sensor.motion", "to": "on", "id": "motion_on"}, {"trigger": "state", "entity_id": "binary_sensor.motion", "to": "off", "for": {"minutes": 5}, "id": "motion_off"} ], "actions": [ {"choose": [ {"conditions": [ {"condition": "trigger", "id": "motion_on"}, {"condition": "sun", "after": "sunset"} ], "sequence": [{"action": "light.turn_on", "target": {"entity_id": "light.hallway"}}]}, {"conditions": [{"condition": "trigger", "id": "motion_off"}], "sequence": [{"action": "light.turn_off", "target": {"entity_id": "light.hallway"}}]} ]} ] })

Update existing automation: current = ha_config_get_automation(identifier="automation.morning_routine") ha_config_set_automation( identifier="automation.morning_routine", config_hash=current["config_hash"], config={ "alias": "Updated Morning Routine", "triggers": [{"trigger": "time", "at": "06:30:00"}], "actions": [ {"action": "light.turn_on", "target": {"area_id": "bedroom"}}, {"action": "climate.set_temperature", "target": {"entity_id": "climate.bedroom"}, "data": {"temperature": 22}} ] } )

BLUEPRINT AUTOMATION EXAMPLES:

Create automation from blueprint: ha_config_set_automation(config={ "alias": "Motion Light Kitchen", "use_blueprint": { "path": "homeassistant/motion_light.yaml", "input": { "motion_entity": "binary_sensor.kitchen_motion", "light_target": {"entity_id": "light.kitchen"}, "no_motion_wait": 120 } } })

Update blueprint automation inputs: ha_config_set_automation( identifier="automation.motion_light_kitchen", config={ "alias": "Motion Light Kitchen", "use_blueprint": { "path": "homeassistant/motion_light.yaml", "input": { "motion_entity": "binary_sensor.kitchen_motion", "light_target": {"entity_id": "light.kitchen"}, "no_motion_wait": 300 } } } )

TAKE CONTROL OF A BLUEPRINT AUTOMATION:

take_control_of_blueprint=True converts a blueprint-backed automation into a standalone one — the UI's "Take control". The blueprint is rendered with the automation's CURRENT inputs and the result is saved over the same automation, which keeps its entity_id, alias and description but gains its own triggers/conditions/actions and loses 'use_blueprint'.

ha_config_set_automation( identifier="automation.motion_light_kitchen", take_control_of_blueprint=True, )

This is one-way: the automation is no longer linked to the blueprint, so later blueprint edits stop reaching it. To change an input value, update 'use_blueprint.input' instead (see the example above) — that keeps the link.

Taking control does NOT free the blueprint. Home Assistant goes on counting a converted automation as a user of it, so deleting that blueprint stays refused until the automation itself is removed (verified against Home Assistant 2026.9; an automation reload does not clear it either).

The response names the blueprint in took_control_of_blueprint. To see what the rendering looks like WITHOUT writing anything, call ha_manage_blueprints(action="substitute", path=..., input=...), which returns the config and leaves the automation alone. ha_manage_blueprints also lists, imports, saves and deletes blueprints, and action="get" reports which automations use one.

TRIGGER TYPES: time, time_pattern, sun, state, numeric_state, event, device, zone, template, and more CONDITION TYPES: state, numeric_state, time, sun, template, device, zone, and more ACTION TYPES: action calls, delays, wait_for_trigger, wait_template, if/then/else, choose, repeat, parallel

For comprehensive automation documentation with all trigger/condition/action types and advanced examples:

TROUBLESHOOTING:

  • Use ha_get_state() to verify entity_ids exist

  • Use ha_search() to find correct entity_ids

  • IF you must use Jinja2 and have no native alternative, test it first with ha_eval_template() before embedding it in the automation config — catches syntax errors and unresolved entity_ids before they fail silently at runtime

  • Use ha_search(domain_filter='automation') to find existing automations

create update modify edit automation triggers conditions actions new automation write save take control blueprint detach unlink standalone convert

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
waitNoWait for automation to be queryable before returning. Default: True. Set to False for bulk operations.
configNoComplete automation configuration with required fields: 'alias', 'triggers', 'actions'. Optional: 'description', 'conditions', 'mode', 'max', 'initial_state', 'variables'. Purpose-specific triggers/conditions (HA 2026.7+ default: 'trigger': '<domain>.<name>' with 'target'/'options') are valid config. Mutually exclusive with python_transform.
categoryNoCategory ID to assign to this automation. Use ha_config_get_category(scope='automation') to list available categories, or ha_config_set_category() to create one.
identifierNoTarget automation entity_id or HA config 'id' (unique_id). Omit for creation with a generated ID. Values such as 'new' are literal IDs, not placeholders. Required for python_transform.
config_hashNoConfig hash from ha_config_get_automation for optimistic locking. REQUIRED for python_transform (validates automation unchanged). Required when a config update changes an existing automation's alias. Otherwise optional for config updates (validates before full replacement if provided).
MandatoryBPSNo
BestPracticeKeyNoRead-receipt for the home-assistant-best-practices skill; required when strict best-practices mode is enabled. Not a secret or credential: the current value is an attestation phrase published openly at the top of the skill content served by ha_get_skill_guide. Read that content, then pass the value back verbatim — this round-trip is the server's designed protocol confirming the practices were read before writing.
python_transformNoPython expression to transform existing automation config. Mutually exclusive with config. Requires identifier and config_hash for validation. WARNING: Expressions with infinite loops will hang the server. Examples: Simple: python_transform="config['actions'][0]['data']['brightness'] = 255" Pattern: python_transform="for a in config['actions']: if a.get('alias') == 'My Step': a['data']['value'] = 100" PYTHON TRANSFORM SECURITY: ✅ ALLOWED: - Dictionary/list access: config['views'][0]['cards'][1] - Slicing: config['views'][0]['cards'][1:3] - Assignment: config['key'] = 'value' - Deletion: del config['key'] or config.pop('key') - List methods: append, insert, pop, remove, clear, extend - Dict methods: update, get, setdefault, keys, values, items - Loops: for, if/else, pass, break, continue - Comprehensions: [x for x in ...], {k: v for ...}, (x for x in ...) - Ternary: x if condition else y - Iterable unpacking (* in calls/literals): f(*xs), [*xs, y] - Dict unpacking (**) in calls and dict literals: {**d, 'k': v} - Keyword arguments: func(key=value) - Lambdas (e.g. for `key=`): sorted(items, key=lambda x: x['score']) - String methods: startswith, endswith, lower, upper, strip, split, join, replace - Safe builtins: isinstance, len, range, enumerate, zip, sorted, reversed, min, max, sum, abs, any, all, round, str, int, float, bool, list, dict, tuple, set ❌ FORBIDDEN: - Imports: import, from, __import__ - File operations: open, read, write - Dunder access: __class__, __bases__, __subclasses__ - Dangerous builtins: eval, exec, compile, getattr, setattr, delattr, hasattr - Function definitions: def, class - Exception handling: try/except (validate with isinstance/in/.get() instead) - While loops: use bounded for loops or comprehensions instead 🎯 PATTERNS: - Filter cards: cards = [c for c in cards if keep(c)] - Skip in a loop: prefer `continue` over an empty `pass` branch (clearer) - Conditionally include: build a new list and `.append(x)` only the cards you want, instead of iterating the original and using if/pass branches to drop entries - Modify in place when possible (single pass, fewer surprises) over reconstructing the entire list
take_control_of_blueprintNoConvert a blueprint-backed automation into an editable standalone one -- the UI's "Take control". Renders the blueprint with its current inputs and saves the result over the same automation, which then has its own triggers/conditions/actions and no 'use_blueprint'. Requires identifier; mutually exclusive with config and python_transform. Irreversible: the link to the blueprint is gone afterwards, so edit inputs instead if you only want to change a value. Does NOT free the blueprint: Home Assistant keeps counting the converted automation as a user, so deleting that blueprint stays refused until the automation is removed. To preview the rendering without writing anything, use ha_manage_blueprints(action="substitute").

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault

No arguments

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed3 schema fields changedv8.5.0
    • changedInput schema / properties / config_hash / description
      Previous value: -"Config hash from ha_config_get_automation for optimistic locking. REQUIRED for python_transform (validates automation unchanged). Optional for config updates (validates before full replacement if provided)."New value: +"Config hash from ha_config_get_automation for optimistic locking. REQUIRED for python_transform (validates automation unchanged). Required when a config update changes an existing automation's alias. Otherwise optional for config updates (validates before full replacement if provided)."
    • changedInput schema / properties / identifier / description
      Previous value: -"Automation entity_id or unique_id for updates. Required for python_transform. Omit to create new automation with generated unique_id."New value: +"Target automation entity_id or HA config 'id' (unique_id). Omit for creation with a generated ID. Values such as 'new' are literal IDs, not placeholders. Required for python_transform."
    • addedInput schema / properties / take_control_of_blueprint
      Added value: +{
      +  "default": false,
      +  "description": "Convert a blueprint-backed automation into an editable standalone one -- the UI's \"Take control\". Renders the blueprint with its current inputs and saves the result over the same automation, which then has its own triggers/conditions/actions and no 'use_blueprint'. Requires identifier; mutually exclusive with config and python_transform. Irreversible: the link to the blueprint is gone afterwards, so edit inputs instead if you only want to change a value. Does NOT free the blueprint: Home Assistant keeps counting the converted automation as a user, so deleting that blueprint stays refused until the automation is removed. To preview the rendering without writing anything, use ha_manage_blueprints(action=\"substitute\").",
      +  "type": "boolean"
      +}
  2. First observedv7.14.2

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond the destructiveHint annotation, the description discloses key behavioral traits: take_control_of_blueprint is irreversible, the blueprint remains counted as in use, alias changes without config_hash are rejected before writing, and infinite python_transform loops can hang the server. It also explains return-value semantics for automation_id and the best-practice checker behavior. No contradiction with annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long but well-structured with clear headings, examples, and front-loaded critical guidance (MUST call skill, prefer native solutions). Some redundancy exists around repeated skill references and the bottom documentation link, but the length is largely justified by the tool's complexity.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a tool with 9 parameters, three operating modes, blueprint edge cases, and an output schema, the description is remarkably complete. It covers creation, update, python_transform, blueprint take-control, troubleshooting, return behavior, and alternative tools, leaving little ambiguity for an agent.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Although schema coverage is 89%, the description adds substantial meaning beyond the schema: full config examples, python_transform patterns with allowed/forbidden operations, mode selection rules, mutual exclusivity constraints, identifier/config_hash requirements, and blueprint input examples. This significantly helps an agent construct correct parameter values.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a clear verb and resource: 'Create or update a Home Assistant automation.' It goes further by naming sibling tools for alternatives (ha_config_set_scene, ha_config_set_helper) and explicitly distinguishing modes, so an agent can tell this tool apart from nearby automation-related tools.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives explicit when-to-use guidance: use ha_config_set_scene for state snapshots, ha_config_set_helper for computed values, and this tool for automations. It also details which mode to use (python_transform for edits, config for new automations, take_control_of_blueprint for blueprint conversion) and requires calling ha_get_skill_guide first.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Deploy Server

Other Tools