Skip to main content
Glama
homeassistant-ai

Home Assistant MCP Server

Official

Create or Update Script

ha_config_set_script
Destructive

Create or update Home Assistant scripts with action sequences or blueprint inputs, and convert blueprint-backed scripts into standalone ones for direct editing.

Instructions

Create or update a Home Assistant script.

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

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

  • choose / if/then/else instead of template-based service names

  • wait_for_trigger instead of wait_template

  • Native for: field on state conditions inside choose/if, and on state/numeric_state triggers in wait_for_trigger, instead of {{ now() - X.last_changed > timedelta(...) }} duration math.

  • repeat with for_each instead of template loops

  • 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.

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 scripts. Surgical updates.

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

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

IMPORTANT: python_transform requires 'config_hash' from ha_config_get_script().

PYTHON TRANSFORM EXAMPLES:

  • Update step: python_transform="config['sequence'][0]['data']['message'] = 'Hello'"

  • Add step: python_transform="config['sequence'].append({'delay': {'seconds': 5}})"

  • Remove last step: python_transform="config['sequence'].pop()"

Creates a new script or updates an existing one with the provided configuration. Supports both regular scripts (with sequence) and blueprint-based scripts.

Required config fields (choose one): - sequence: List of actions to execute (for regular scripts) - use_blueprint: Blueprint configuration (for blueprint-based scripts)

Optional config fields: - alias: Display name (defaults to script_id) - description: Script description - icon: Icon to display - mode: Execution mode ('single', 'restart', 'queued', 'parallel') - max: Maximum concurrent executions (for queued/parallel modes) - fields: Input parameters for the script

SCRIPTS vs AUTOMATIONS: Scripts use 'sequence', NOT 'trigger' or 'action'. If you need trigger-based execution, use ha_config_set_automation instead.

EXAMPLES:

Create basic delay script: ha_config_set_script(script_id="wait_script", config={ "sequence": [{"delay": {"seconds": 5}}], "alias": "Wait 5 Seconds", "description": "Simple delay script" })

Create service call script: ha_config_set_script(script_id="blink_light", config={ "sequence": [ {"action": "light.turn_on", "target": {"entity_id": "light.living_room"}}, {"delay": {"seconds": 2}}, {"action": "light.turn_off", "target": {"entity_id": "light.living_room"}} ], "alias": "Light Blink", "mode": "single" })

Create script with parameters: ha_config_set_script(script_id="backup_script", config={ "alias": "Backup with Reference", "description": "Create backup with optional reference parameter", "fields": { "reference": { "name": "Reference", "description": "Optional reference for backup identification", "selector": {"text": None} } }, "sequence": [ { "action": "hassio.backup_partial", "data": { "compressed": False, "homeassistant": True, "homeassistant_exclude_database": True, "name": "Backup_{{ reference | default('auto') }}{{ now().strftime('%Y%m%d%H%M%S') }}" } } ] })

Update script: ha_config_set_script(script_id="morning_routine", config={ "sequence": [ {"action": "light.turn_on", "target": {"area_id": "bedroom"}}, {"action": "climate.set_temperature", "target": {"entity_id": "climate.bedroom"}, "data": {"temperature": 22}} ], "alias": "Updated Morning Routine" })

Create blueprint-based script: ha_config_set_script(script_id="notification_script", config={ "alias": "My Notification Script", "use_blueprint": { "path": "notification_script.yaml", "input": { "message": "Hello World", "title": "Test Notification" } } })

Update blueprint script inputs: ha_config_set_script(script_id="notification_script", config={ "alias": "My Notification Script", "use_blueprint": { "path": "notification_script.yaml", "input": { "message": "Updated message", "title": "Updated Title" } } })

TAKE CONTROL OF A BLUEPRINT SCRIPT:

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

ha_config_set_script( script_id="notification_script", take_control_of_blueprint=True, )

This is one-way: the script 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 script as a user of it, so deleting that blueprint stays refused until the script itself is removed. To see the rendering WITHOUT writing anything, call ha_manage_blueprints(action="substitute", domain="script", path=..., input=...). ha_manage_blueprints also lists, imports, saves and deletes blueprints, and action="get" reports which scripts use one.

Note: Scripts use Home Assistant's action syntax. Check the documentation for advanced features like conditions, variables, parallel execution, and service call options.

create update modify edit script sequence actions new script write save take control blueprint detach unlink standalone convert

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
waitNoWait for script to be queryable before returning. Default: True. Set to False for bulk operations.
configNoScript configuration dictionary. Must include EITHER 'sequence' (for regular scripts) OR 'use_blueprint' (for blueprint-based scripts). Optional fields: 'alias', 'description', 'icon', 'mode', 'max', 'fields'. Mutually exclusive with python_transform.
categoryNoCategory ID to assign to this script. Use ha_config_get_category(scope='script') to list available categories, or ha_config_set_category() to create one.
script_idYesScript identifier — bare storage key ('morning_routine') or entity_id form ('script.morning_routine'); a leading 'script.' prefix is stripped before lookup.
config_hashNoConfig hash from ha_config_get_script for optimistic locking. REQUIRED for python_transform (validates script unchanged). 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 script config. Mutually exclusive with config. Requires config_hash for validation. WARNING: Expressions with infinite loops will hang the server. Examples: Simple: python_transform="config['sequence'][0]['data']['message'] = 'Hello'" Pattern: python_transform="for step in config['sequence']: if step.get('alias') == 'My Step': step['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 script into an editable standalone one -- the UI's "Take control". Renders the blueprint with its current inputs and saves the result over the same script, which then has its own sequence and no 'use_blueprint'. 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 script as a user, so deleting that blueprint stays refused until the script is removed. To preview the rendering without writing anything, use ha_manage_blueprints(action="substitute", domain="script").

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault

No arguments

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed1 schema field changedv8.5.0
    • addedInput schema / properties / take_control_of_blueprint
      Added value: +{
      +  "default": false,
      +  "description": "Convert a blueprint-backed script into an editable standalone one -- the UI's \"Take control\". Renders the blueprint with its current inputs and saves the result over the same script, which then has its own sequence and no 'use_blueprint'. 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 script as a user, so deleting that blueprint stays refused until the script is removed. To preview the rendering without writing anything, use ha_manage_blueprints(action=\"substitute\", domain=\"script\").",
      +  "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?

With annotations only providing destructiveHint:true and openWorldHint:false, the description carries full behavioral disclosure. It warns of infinite loops in python_transform, details the irreversible nature of take_control_of_blueprint, explains that it does not free the blueprint, and describes the best-practice checker and skill_content embedding. It also clarifies side effects like remaining counted as a blueprint user, far exceeding annotation coverage.

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 section headers (MUST call, PREFER NATIVE ACTIONS, WHEN TO USE WHICH MODE, PYTHON TRANSFORM EXAMPLES, SCRIPTS vs AUTOMATIONS, EXAMPLES, TAKE CONTROL). It front-loads the critical prerequisite and preference. Some redundancy exists (e.g., a repeated sentence about creating/updating near the end) and the note about checking documentation is generic, but the overall organization earns its length given tool 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?

The description covers all critical aspects for correct invocation: mode selection, prerequisites (config_hash), side effects (irreversibility, blueprint usage), interplay with related tools (ha_get_skill_guide, ha_manage_blueprints), and the distinction from automations. With an output schema present, return-value explanation is not required. Nothing an agent needs to call this tool correctly is missing.

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 high (89%), the description adds substantial meaning beyond the field definitions. It expands on config structure with examples, explains mutual exclusivity between config/python_transform/take_control_of_blueprint, clarifies config_hash's role in optimistic locking, and gives concrete usage examples for each parameter. This enriches the schema without redundancy.

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 specific verb-resource combination ('Create or update a Home Assistant script') and clearly differentiates from the sibling tool ha_config_set_automation, noting scripts use 'sequence' not 'trigger'. It also enumerates three operation modes (config, python_transform, take_control_of_blueprint), giving the agent an unambiguous picture of scope.

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 advice for each mode (python_transform for edits, config for new/full restructures, take_control_of_blueprint for converting), requires a prior call to ha_get_skill_guide or local skills, and names the alternative for trigger-based execution (ha_config_set_automation). It also states that python_transform requires config_hash from ha_config_get_script. This leaves no ambiguity about selection criteria.

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