ha-mcp-dev 6.3.1.dev137__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ha_mcp/__init__.py +51 -0
- ha_mcp/__main__.py +753 -0
- ha_mcp/_pypi_marker +0 -0
- ha_mcp/auth/__init__.py +10 -0
- ha_mcp/auth/consent_form.py +501 -0
- ha_mcp/auth/provider.py +786 -0
- ha_mcp/client/__init__.py +10 -0
- ha_mcp/client/rest_client.py +924 -0
- ha_mcp/client/websocket_client.py +656 -0
- ha_mcp/client/websocket_listener.py +340 -0
- ha_mcp/config.py +175 -0
- ha_mcp/errors.py +399 -0
- ha_mcp/py.typed +0 -0
- ha_mcp/resources/card_types.json +48 -0
- ha_mcp/resources/dashboard_guide.md +573 -0
- ha_mcp/server.py +189 -0
- ha_mcp/smoke_test.py +108 -0
- ha_mcp/tools/__init__.py +11 -0
- ha_mcp/tools/backup.py +457 -0
- ha_mcp/tools/device_control.py +687 -0
- ha_mcp/tools/enhanced.py +191 -0
- ha_mcp/tools/helpers.py +184 -0
- ha_mcp/tools/registry.py +199 -0
- ha_mcp/tools/smart_search.py +797 -0
- ha_mcp/tools/tools_addons.py +343 -0
- ha_mcp/tools/tools_areas.py +478 -0
- ha_mcp/tools/tools_blueprints.py +279 -0
- ha_mcp/tools/tools_bug_report.py +570 -0
- ha_mcp/tools/tools_calendar.py +391 -0
- ha_mcp/tools/tools_camera.py +149 -0
- ha_mcp/tools/tools_config_automations.py +508 -0
- ha_mcp/tools/tools_config_dashboards.py +1502 -0
- ha_mcp/tools/tools_config_entry_flow.py +223 -0
- ha_mcp/tools/tools_config_helpers.py +925 -0
- ha_mcp/tools/tools_config_info.py +258 -0
- ha_mcp/tools/tools_config_scripts.py +267 -0
- ha_mcp/tools/tools_entities.py +76 -0
- ha_mcp/tools/tools_filesystem.py +589 -0
- ha_mcp/tools/tools_groups.py +306 -0
- ha_mcp/tools/tools_hacs.py +787 -0
- ha_mcp/tools/tools_history.py +714 -0
- ha_mcp/tools/tools_integrations.py +297 -0
- ha_mcp/tools/tools_labels.py +713 -0
- ha_mcp/tools/tools_mcp_component.py +305 -0
- ha_mcp/tools/tools_registry.py +1115 -0
- ha_mcp/tools/tools_resources.py +622 -0
- ha_mcp/tools/tools_search.py +573 -0
- ha_mcp/tools/tools_service.py +211 -0
- ha_mcp/tools/tools_services.py +352 -0
- ha_mcp/tools/tools_system.py +363 -0
- ha_mcp/tools/tools_todo.py +530 -0
- ha_mcp/tools/tools_traces.py +496 -0
- ha_mcp/tools/tools_updates.py +476 -0
- ha_mcp/tools/tools_utility.py +519 -0
- ha_mcp/tools/tools_voice_assistant.py +345 -0
- ha_mcp/tools/tools_zones.py +387 -0
- ha_mcp/tools/util_helpers.py +199 -0
- ha_mcp/utils/__init__.py +30 -0
- ha_mcp/utils/domain_handlers.py +380 -0
- ha_mcp/utils/fuzzy_search.py +339 -0
- ha_mcp/utils/operation_manager.py +442 -0
- ha_mcp/utils/usage_logger.py +290 -0
- ha_mcp_dev-6.3.1.dev137.dist-info/METADATA +229 -0
- ha_mcp_dev-6.3.1.dev137.dist-info/RECORD +71 -0
- ha_mcp_dev-6.3.1.dev137.dist-info/WHEEL +5 -0
- ha_mcp_dev-6.3.1.dev137.dist-info/entry_points.txt +7 -0
- ha_mcp_dev-6.3.1.dev137.dist-info/licenses/LICENSE +21 -0
- ha_mcp_dev-6.3.1.dev137.dist-info/top_level.txt +2 -0
- tests/__init__.py +1 -0
- tests/test_constants.py +15 -0
- tests/test_env_manager.py +336 -0
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configuration information tool for Home Assistant MCP Server.
|
|
3
|
+
|
|
4
|
+
This module provides an informational tool that explains how Home Assistant
|
|
5
|
+
configuration works when accessed remotely via ha-mcp, and provides guidance
|
|
6
|
+
on generating configuration snippets.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
from typing import Annotated, Any
|
|
11
|
+
|
|
12
|
+
from pydantic import Field
|
|
13
|
+
|
|
14
|
+
from .helpers import log_tool_usage
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def register_config_info_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
|
|
20
|
+
"""Register configuration information tools with the MCP server."""
|
|
21
|
+
|
|
22
|
+
@mcp.tool(
|
|
23
|
+
annotations={
|
|
24
|
+
"idempotentHint": True,
|
|
25
|
+
"readOnlyHint": True,
|
|
26
|
+
"tags": ["information", "configuration"],
|
|
27
|
+
"title": "Home Assistant Configuration Info",
|
|
28
|
+
}
|
|
29
|
+
)
|
|
30
|
+
@log_tool_usage
|
|
31
|
+
async def ha_config_info(
|
|
32
|
+
config_type: Annotated[
|
|
33
|
+
str,
|
|
34
|
+
Field(
|
|
35
|
+
default="general",
|
|
36
|
+
description=(
|
|
37
|
+
"Type of configuration information to retrieve. "
|
|
38
|
+
"Options: 'general' (default), 'automation', 'script', 'dashboard', "
|
|
39
|
+
"'integration', or 'yaml'. Use 'general' for an overview of how "
|
|
40
|
+
"configuration works via ha-mcp."
|
|
41
|
+
),
|
|
42
|
+
),
|
|
43
|
+
] = "general",
|
|
44
|
+
) -> dict[str, Any]:
|
|
45
|
+
"""
|
|
46
|
+
Get information about Home Assistant configuration access via ha-mcp.
|
|
47
|
+
|
|
48
|
+
**IMPORTANT FOR AI AGENTS:**
|
|
49
|
+
Home Assistant accessed via ha-mcp is a REMOTE system accessed through
|
|
50
|
+
REST and WebSocket APIs. Configuration files are NOT accessible on the
|
|
51
|
+
local filesystem where this MCP server runs.
|
|
52
|
+
|
|
53
|
+
**DO NOT:**
|
|
54
|
+
- Search for configuration.yaml or other HA config files on disk
|
|
55
|
+
- Attempt to read HA configuration files from the filesystem
|
|
56
|
+
- Try to modify configuration files directly
|
|
57
|
+
- Assume HA is running on the same machine as this MCP server
|
|
58
|
+
|
|
59
|
+
**DO:**
|
|
60
|
+
- Use ha-mcp tools to query configuration via the API
|
|
61
|
+
- Generate configuration snippets for the user to copy/paste
|
|
62
|
+
- Direct users to the Home Assistant UI for configuration changes
|
|
63
|
+
- Suggest configuration examples based on user needs
|
|
64
|
+
|
|
65
|
+
**CONFIGURATION ACCESS:**
|
|
66
|
+
- Automations: Use ha_config_* tools (list, get, create, update, delete)
|
|
67
|
+
- Scripts: Use ha_config_script_* tools
|
|
68
|
+
- Dashboards: Use ha_config_dashboard_* tools
|
|
69
|
+
- Integrations: Most are configured via UI, some via YAML
|
|
70
|
+
- YAML files: Not directly accessible via ha-mcp
|
|
71
|
+
|
|
72
|
+
**GENERATING SNIPPETS:**
|
|
73
|
+
When users need configuration examples:
|
|
74
|
+
1. Generate valid YAML/JSON snippets based on their requirements
|
|
75
|
+
2. Provide clear instructions on where to add the configuration
|
|
76
|
+
3. Explain any required steps (restart, reload, etc.)
|
|
77
|
+
4. Link to relevant Home Assistant documentation
|
|
78
|
+
|
|
79
|
+
**FEATURE REQUESTS:**
|
|
80
|
+
If users need direct configuration file access or advanced features:
|
|
81
|
+
- Suggest filing a feature request at:
|
|
82
|
+
https://github.com/homeassistant-ai/ha-mcp/issues/new
|
|
83
|
+
- Explain the current capabilities and limitations
|
|
84
|
+
- Provide workarounds using existing tools
|
|
85
|
+
|
|
86
|
+
Returns information specific to the requested config_type.
|
|
87
|
+
"""
|
|
88
|
+
info: dict[str, Any] = {
|
|
89
|
+
"success": True,
|
|
90
|
+
"config_type": config_type,
|
|
91
|
+
"ha_access_method": "Remote via REST/WebSocket APIs",
|
|
92
|
+
"filesystem_access": False,
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if config_type == "general":
|
|
96
|
+
info["message"] = (
|
|
97
|
+
"Home Assistant is accessed remotely via REST and WebSocket APIs. "
|
|
98
|
+
"Configuration files (configuration.yaml, etc.) are NOT accessible "
|
|
99
|
+
"on the local filesystem. Use ha-mcp tools to query and modify "
|
|
100
|
+
"configuration via the API, or generate configuration snippets "
|
|
101
|
+
"for users to manually add."
|
|
102
|
+
)
|
|
103
|
+
info["available_tools"] = [
|
|
104
|
+
"ha_config_* - Automation management",
|
|
105
|
+
"ha_config_script_* - Script management",
|
|
106
|
+
"ha_config_dashboard_* - Dashboard management",
|
|
107
|
+
"ha_config_helper_* - Helper entity management",
|
|
108
|
+
"ha_get_config - Get basic HA configuration info",
|
|
109
|
+
"ha_list_entities - List all entities",
|
|
110
|
+
"ha_search_* - Search entities, services, etc.",
|
|
111
|
+
]
|
|
112
|
+
info["not_available"] = [
|
|
113
|
+
"Direct file system access to configuration.yaml",
|
|
114
|
+
"Direct file system access to automations.yaml",
|
|
115
|
+
"Direct file system access to scripts.yaml",
|
|
116
|
+
"Direct file system access to secrets.yaml",
|
|
117
|
+
]
|
|
118
|
+
|
|
119
|
+
elif config_type == "automation":
|
|
120
|
+
info["message"] = (
|
|
121
|
+
"Automations can be managed via ha_config_* tools. "
|
|
122
|
+
"For YAML-based automations, generate snippets for users to add manually."
|
|
123
|
+
)
|
|
124
|
+
info["tools"] = [
|
|
125
|
+
"ha_config_list_automations - List all automations",
|
|
126
|
+
"ha_config_get_automation - Get automation details",
|
|
127
|
+
"ha_config_create_automation - Create new automation",
|
|
128
|
+
"ha_config_update_automation - Update existing automation",
|
|
129
|
+
"ha_config_delete_automation - Delete automation",
|
|
130
|
+
"ha_trigger_automation - Manually trigger automation",
|
|
131
|
+
]
|
|
132
|
+
info["snippet_example"] = {
|
|
133
|
+
"description": "Example automation snippet",
|
|
134
|
+
"yaml": """# Add to automations.yaml or via HA UI
|
|
135
|
+
- alias: "Example Automation"
|
|
136
|
+
description: "Turn on light when motion detected"
|
|
137
|
+
trigger:
|
|
138
|
+
- platform: state
|
|
139
|
+
entity_id: binary_sensor.motion_sensor
|
|
140
|
+
to: "on"
|
|
141
|
+
condition: []
|
|
142
|
+
action:
|
|
143
|
+
- service: light.turn_on
|
|
144
|
+
target:
|
|
145
|
+
entity_id: light.living_room
|
|
146
|
+
data:
|
|
147
|
+
brightness: 255""",
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
elif config_type == "script":
|
|
151
|
+
info["message"] = (
|
|
152
|
+
"Scripts can be managed via ha_config_script_* tools. "
|
|
153
|
+
"For YAML-based scripts, generate snippets for users to add manually."
|
|
154
|
+
)
|
|
155
|
+
info["tools"] = [
|
|
156
|
+
"ha_config_list_scripts - List all scripts",
|
|
157
|
+
"ha_config_get_script - Get script details",
|
|
158
|
+
"ha_config_create_script - Create new script",
|
|
159
|
+
"ha_config_update_script - Update existing script",
|
|
160
|
+
"ha_config_delete_script - Delete script",
|
|
161
|
+
"ha_execute_script - Run a script",
|
|
162
|
+
]
|
|
163
|
+
info["snippet_example"] = {
|
|
164
|
+
"description": "Example script snippet",
|
|
165
|
+
"yaml": """# Add to scripts.yaml or via HA UI
|
|
166
|
+
good_night:
|
|
167
|
+
alias: "Good Night"
|
|
168
|
+
description: "Turn off all lights and lock doors"
|
|
169
|
+
sequence:
|
|
170
|
+
- service: light.turn_off
|
|
171
|
+
target:
|
|
172
|
+
area_id: all
|
|
173
|
+
- service: lock.lock
|
|
174
|
+
target:
|
|
175
|
+
entity_id: lock.front_door""",
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
elif config_type == "dashboard":
|
|
179
|
+
info["message"] = (
|
|
180
|
+
"Dashboards can be managed via ha_config_dashboard_* tools. "
|
|
181
|
+
"Dashboard configuration is stored in the HA database, not YAML files."
|
|
182
|
+
)
|
|
183
|
+
info["tools"] = [
|
|
184
|
+
"ha_config_get_dashboard(list_only=True) - List all dashboards",
|
|
185
|
+
"ha_config_get_dashboard(url_path=...) - Get dashboard configuration",
|
|
186
|
+
"ha_config_set_dashboard - Create/update dashboard",
|
|
187
|
+
"ha_config_delete_dashboard - Delete dashboard",
|
|
188
|
+
]
|
|
189
|
+
info["note"] = (
|
|
190
|
+
"Dashboard YAML configuration is for Lovelace cards, not file access. "
|
|
191
|
+
"Use the dashboard tools to manage dashboards programmatically."
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
elif config_type == "integration":
|
|
195
|
+
info["message"] = (
|
|
196
|
+
"Most integrations are configured via the Home Assistant UI. "
|
|
197
|
+
"Some integrations support YAML configuration, but files are not "
|
|
198
|
+
"accessible via ha-mcp."
|
|
199
|
+
)
|
|
200
|
+
info["recommendations"] = [
|
|
201
|
+
"Use ha_get_integration() to see installed integrations",
|
|
202
|
+
"Generate YAML snippets for manual addition to configuration.yaml",
|
|
203
|
+
"Direct users to Settings > Devices & Services in HA UI",
|
|
204
|
+
"Provide documentation links for specific integrations",
|
|
205
|
+
]
|
|
206
|
+
info["snippet_example"] = {
|
|
207
|
+
"description": "Example integration configuration",
|
|
208
|
+
"yaml": """# Add to configuration.yaml
|
|
209
|
+
mqtt:
|
|
210
|
+
broker: 192.168.1.100
|
|
211
|
+
port: 1883
|
|
212
|
+
username: !secret mqtt_username
|
|
213
|
+
password: !secret mqtt_password
|
|
214
|
+
|
|
215
|
+
sensor:
|
|
216
|
+
- platform: mqtt
|
|
217
|
+
name: "Temperature"
|
|
218
|
+
state_topic: "home/temperature"
|
|
219
|
+
unit_of_measurement: "°C\"""",
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
elif config_type == "yaml":
|
|
223
|
+
info["message"] = (
|
|
224
|
+
"YAML configuration files (configuration.yaml, automations.yaml, etc.) "
|
|
225
|
+
"are NOT accessible via ha-mcp. Generate snippets for users to add manually."
|
|
226
|
+
)
|
|
227
|
+
info["workflow"] = [
|
|
228
|
+
"1. Ask user what configuration they need",
|
|
229
|
+
"2. Generate valid YAML snippet based on requirements",
|
|
230
|
+
"3. Explain which file to edit (configuration.yaml, automations.yaml, etc.)",
|
|
231
|
+
"4. Provide instructions on reloading/restarting HA",
|
|
232
|
+
"5. Link to relevant documentation",
|
|
233
|
+
]
|
|
234
|
+
info["common_files"] = {
|
|
235
|
+
"configuration.yaml": "Main configuration file",
|
|
236
|
+
"automations.yaml": "Automation definitions (or use UI)",
|
|
237
|
+
"scripts.yaml": "Script definitions (or use UI)",
|
|
238
|
+
"secrets.yaml": "Sensitive data (passwords, API keys, etc.)",
|
|
239
|
+
"customize.yaml": "Entity customization",
|
|
240
|
+
"groups.yaml": "Entity groups",
|
|
241
|
+
"scenes.yaml": "Scene definitions",
|
|
242
|
+
}
|
|
243
|
+
info["feature_request_url"] = (
|
|
244
|
+
"https://github.com/homeassistant-ai/ha-mcp/issues/new"
|
|
245
|
+
)
|
|
246
|
+
info["feature_request_note"] = (
|
|
247
|
+
"If direct file access is needed, file a feature request. "
|
|
248
|
+
"Explain your use case and why current tools are insufficient."
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
else:
|
|
252
|
+
info["success"] = False
|
|
253
|
+
info["error"] = (
|
|
254
|
+
f"Unknown config_type: {config_type}. "
|
|
255
|
+
"Valid options: general, automation, script, dashboard, integration, yaml"
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
return info
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configuration management tools for Home Assistant scripts.
|
|
3
|
+
|
|
4
|
+
This module provides tools for retrieving, creating, updating, and removing
|
|
5
|
+
Home Assistant script configurations.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
from typing import Annotated, Any, cast
|
|
10
|
+
|
|
11
|
+
from pydantic import Field
|
|
12
|
+
|
|
13
|
+
from .helpers import log_tool_usage
|
|
14
|
+
from .util_helpers import parse_json_param
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def register_config_script_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
|
|
20
|
+
"""Register Home Assistant script configuration tools."""
|
|
21
|
+
|
|
22
|
+
@mcp.tool(
|
|
23
|
+
annotations={
|
|
24
|
+
"idempotentHint": True,
|
|
25
|
+
"readOnlyHint": True,
|
|
26
|
+
"tags": ["script"],
|
|
27
|
+
"title": "Get Script Config",
|
|
28
|
+
}
|
|
29
|
+
)
|
|
30
|
+
@log_tool_usage
|
|
31
|
+
async def ha_config_get_script(
|
|
32
|
+
script_id: Annotated[
|
|
33
|
+
str, Field(description="Script identifier (e.g., 'morning_routine')")
|
|
34
|
+
],
|
|
35
|
+
) -> dict[str, Any]:
|
|
36
|
+
"""
|
|
37
|
+
Retrieve Home Assistant script configuration.
|
|
38
|
+
|
|
39
|
+
Returns the complete configuration for a script, including sequence, mode, fields, and other settings.
|
|
40
|
+
|
|
41
|
+
EXAMPLES:
|
|
42
|
+
- Get script: ha_config_get_script("morning_routine")
|
|
43
|
+
- Get script: ha_config_get_script("backup_script")
|
|
44
|
+
|
|
45
|
+
For detailed script configuration help, use: ha_get_domain_docs("script")
|
|
46
|
+
"""
|
|
47
|
+
try:
|
|
48
|
+
config_result = await client.get_script_config(script_id)
|
|
49
|
+
return {
|
|
50
|
+
"success": True,
|
|
51
|
+
"action": "get",
|
|
52
|
+
"script_id": script_id,
|
|
53
|
+
"config": config_result,
|
|
54
|
+
}
|
|
55
|
+
except Exception as e:
|
|
56
|
+
logger.error(f"Error getting script: {e}")
|
|
57
|
+
return {
|
|
58
|
+
"success": False,
|
|
59
|
+
"action": "get",
|
|
60
|
+
"script_id": script_id,
|
|
61
|
+
"error": str(e),
|
|
62
|
+
"suggestions": [
|
|
63
|
+
"Verify script_id exists using ha_search_entities(domain_filter='script')",
|
|
64
|
+
"Check Home Assistant connection",
|
|
65
|
+
"Use ha_get_domain_docs('script') for configuration help",
|
|
66
|
+
],
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
@mcp.tool(
|
|
70
|
+
annotations={
|
|
71
|
+
"destructiveHint": True,
|
|
72
|
+
"tags": ["script"],
|
|
73
|
+
"title": "Create or Update Script",
|
|
74
|
+
}
|
|
75
|
+
)
|
|
76
|
+
@log_tool_usage
|
|
77
|
+
async def ha_config_set_script(
|
|
78
|
+
script_id: Annotated[
|
|
79
|
+
str, Field(description="Script identifier (e.g., 'morning_routine')")
|
|
80
|
+
],
|
|
81
|
+
config: Annotated[
|
|
82
|
+
str | dict[str, Any],
|
|
83
|
+
Field(
|
|
84
|
+
description="Script configuration dictionary with 'sequence' (required) and optional fields like 'alias', 'description', 'icon', 'mode', 'max', 'fields'"
|
|
85
|
+
),
|
|
86
|
+
],
|
|
87
|
+
) -> dict[str, Any]:
|
|
88
|
+
"""
|
|
89
|
+
Create or update a Home Assistant script.
|
|
90
|
+
|
|
91
|
+
Creates a new script or updates an existing one with the provided configuration.
|
|
92
|
+
|
|
93
|
+
Required config fields:
|
|
94
|
+
- sequence: List of actions to execute
|
|
95
|
+
|
|
96
|
+
Optional config fields:
|
|
97
|
+
- alias: Display name (defaults to script_id)
|
|
98
|
+
- description: Script description
|
|
99
|
+
- icon: Icon to display
|
|
100
|
+
- mode: Execution mode ('single', 'restart', 'queued', 'parallel')
|
|
101
|
+
- max: Maximum concurrent executions (for queued/parallel modes)
|
|
102
|
+
- fields: Input parameters for the script
|
|
103
|
+
|
|
104
|
+
IMPORTANT: The 'config' parameter must be passed as a proper dictionary/object.
|
|
105
|
+
|
|
106
|
+
EXAMPLES:
|
|
107
|
+
|
|
108
|
+
Create basic delay script:
|
|
109
|
+
ha_config_set_script("wait_script", {
|
|
110
|
+
"sequence": [{"delay": {"seconds": 5}}],
|
|
111
|
+
"alias": "Wait 5 Seconds",
|
|
112
|
+
"description": "Simple delay script"
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
Create service call script:
|
|
116
|
+
ha_config_set_script("blink_light", {
|
|
117
|
+
"sequence": [
|
|
118
|
+
{"service": "light.turn_on", "target": {"entity_id": "light.living_room"}},
|
|
119
|
+
{"delay": {"seconds": 2}},
|
|
120
|
+
{"service": "light.turn_off", "target": {"entity_id": "light.living_room"}}
|
|
121
|
+
],
|
|
122
|
+
"alias": "Light Blink",
|
|
123
|
+
"mode": "single"
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
Create script with parameters:
|
|
127
|
+
ha_config_set_script("backup_script", {
|
|
128
|
+
"alias": "Backup with Reference",
|
|
129
|
+
"description": "Create backup with optional reference parameter",
|
|
130
|
+
"fields": {
|
|
131
|
+
"reference": {
|
|
132
|
+
"name": "Reference",
|
|
133
|
+
"description": "Optional reference for backup identification",
|
|
134
|
+
"selector": {"text": None}
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
"sequence": [
|
|
138
|
+
{
|
|
139
|
+
"action": "hassio.backup_partial",
|
|
140
|
+
"data": {
|
|
141
|
+
"compressed": False,
|
|
142
|
+
"homeassistant": True,
|
|
143
|
+
"homeassistant_exclude_database": True,
|
|
144
|
+
"name": "Backup_{{ reference | default('auto') }}_{{ now().strftime('%Y%m%d_%H%M%S') }}"
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
]
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
Update script:
|
|
151
|
+
ha_config_set_script("morning_routine", {
|
|
152
|
+
"sequence": [
|
|
153
|
+
{"service": "light.turn_on", "target": {"area_id": "bedroom"}},
|
|
154
|
+
{"service": "climate.set_temperature", "target": {"entity_id": "climate.bedroom"}, "data": {"temperature": 22}}
|
|
155
|
+
],
|
|
156
|
+
"alias": "Updated Morning Routine"
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
PREFER NATIVE ACTIONS OVER TEMPLATES:
|
|
160
|
+
Before using template-based logic in scripts, check if native actions exist:
|
|
161
|
+
- Use `choose` action instead of template-based service names
|
|
162
|
+
- Use `if/then/else` action instead of template conditions
|
|
163
|
+
- Use `repeat` action with `for_each` instead of template loops
|
|
164
|
+
- Use `wait_for_trigger` instead of `wait_template` when waiting for state changes
|
|
165
|
+
- Use native action variables instead of complex template calculations
|
|
166
|
+
|
|
167
|
+
For detailed script configuration help, use: ha_get_domain_docs("script")
|
|
168
|
+
|
|
169
|
+
Note: Scripts use Home Assistant's action syntax. Check the documentation for advanced
|
|
170
|
+
features like conditions, variables, parallel execution, and service call options.
|
|
171
|
+
"""
|
|
172
|
+
try:
|
|
173
|
+
# Parse JSON config if provided as string
|
|
174
|
+
try:
|
|
175
|
+
parsed_config = parse_json_param(config, "config")
|
|
176
|
+
except ValueError as e:
|
|
177
|
+
return {
|
|
178
|
+
"success": False,
|
|
179
|
+
"error": f"Invalid config parameter: {e}",
|
|
180
|
+
"provided_config_type": type(config).__name__,
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
# Ensure config is a dict
|
|
184
|
+
if parsed_config is None or not isinstance(parsed_config, dict):
|
|
185
|
+
return {
|
|
186
|
+
"success": False,
|
|
187
|
+
"error": "Config parameter must be a JSON object",
|
|
188
|
+
"provided_type": type(parsed_config).__name__,
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
config_dict = cast(dict[str, Any], parsed_config)
|
|
192
|
+
|
|
193
|
+
if "sequence" not in config_dict:
|
|
194
|
+
return {
|
|
195
|
+
"success": False,
|
|
196
|
+
"error": "config must include 'sequence' field",
|
|
197
|
+
"required_fields": ["sequence"],
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
result = await client.upsert_script_config(config_dict, script_id)
|
|
201
|
+
return {
|
|
202
|
+
"success": True,
|
|
203
|
+
**result,
|
|
204
|
+
"config_provided": config_dict,
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
except Exception as e:
|
|
208
|
+
logger.error(f"Error upserting script: {e}")
|
|
209
|
+
return {
|
|
210
|
+
"success": False,
|
|
211
|
+
"script_id": script_id,
|
|
212
|
+
"error": str(e),
|
|
213
|
+
"suggestions": [
|
|
214
|
+
"Ensure config includes 'sequence' field",
|
|
215
|
+
"Validate sequence actions syntax",
|
|
216
|
+
"Check entity_ids exist if using service calls",
|
|
217
|
+
"Use ha_search_entities(domain_filter='script') to find scripts",
|
|
218
|
+
"Use ha_get_domain_docs('script') for configuration help",
|
|
219
|
+
],
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
@mcp.tool(
|
|
223
|
+
annotations={
|
|
224
|
+
"destructiveHint": True,
|
|
225
|
+
"idempotentHint": True,
|
|
226
|
+
"tags": ["script"],
|
|
227
|
+
"title": "Remove Script",
|
|
228
|
+
}
|
|
229
|
+
)
|
|
230
|
+
@log_tool_usage
|
|
231
|
+
async def ha_config_remove_script(
|
|
232
|
+
script_id: Annotated[
|
|
233
|
+
str, Field(description="Script identifier to delete (e.g., 'old_script')")
|
|
234
|
+
],
|
|
235
|
+
) -> dict[str, Any]:
|
|
236
|
+
"""
|
|
237
|
+
Delete a Home Assistant script.
|
|
238
|
+
|
|
239
|
+
EXAMPLES:
|
|
240
|
+
- Delete script: ha_config_remove_script("old_script")
|
|
241
|
+
- Delete script: ha_config_remove_script("temporary_script")
|
|
242
|
+
|
|
243
|
+
**IMPORTANT LIMITATION:**
|
|
244
|
+
This tool can only delete scripts created via the Home Assistant UI.
|
|
245
|
+
Scripts defined in YAML configuration files (scripts.yaml or configuration.yaml)
|
|
246
|
+
cannot be deleted through the API and will return a 405 Method Not Allowed error.
|
|
247
|
+
|
|
248
|
+
To remove YAML-defined scripts, you must edit the configuration file directly.
|
|
249
|
+
|
|
250
|
+
**WARNING:** Deleting a script that is used by automations may cause those automations to fail.
|
|
251
|
+
"""
|
|
252
|
+
try:
|
|
253
|
+
result = await client.delete_script_config(script_id)
|
|
254
|
+
return {"success": True, "action": "delete", **result}
|
|
255
|
+
except Exception as e:
|
|
256
|
+
logger.error(f"Error deleting script: {e}")
|
|
257
|
+
return {
|
|
258
|
+
"success": False,
|
|
259
|
+
"action": "delete",
|
|
260
|
+
"script_id": script_id,
|
|
261
|
+
"error": str(e),
|
|
262
|
+
"suggestions": [
|
|
263
|
+
"Verify script_id exists using ha_search_entities(domain_filter='script')",
|
|
264
|
+
"Check if script is being used by automations",
|
|
265
|
+
"Use ha_get_domain_docs('script') for configuration help",
|
|
266
|
+
],
|
|
267
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Entity management tools for Home Assistant MCP server.
|
|
3
|
+
|
|
4
|
+
This module provides tools for managing entity lifecycle and properties
|
|
5
|
+
via the Home Assistant entity registry API.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
from typing import Annotated, Any
|
|
10
|
+
|
|
11
|
+
from pydantic import Field
|
|
12
|
+
|
|
13
|
+
from .helpers import exception_to_structured_error, log_tool_usage
|
|
14
|
+
from .util_helpers import coerce_bool_param
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def register_entity_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
|
|
20
|
+
"""Register entity management tools with the MCP server."""
|
|
21
|
+
|
|
22
|
+
@mcp.tool(
|
|
23
|
+
annotations={
|
|
24
|
+
"destructiveHint": True,
|
|
25
|
+
"idempotentHint": True,
|
|
26
|
+
"tags": ["entity"],
|
|
27
|
+
"title": "Set Entity Enabled",
|
|
28
|
+
}
|
|
29
|
+
)
|
|
30
|
+
@log_tool_usage
|
|
31
|
+
async def ha_set_entity_enabled(
|
|
32
|
+
entity_id: Annotated[
|
|
33
|
+
str, Field(description="Entity ID (e.g., 'sensor.temperature')")
|
|
34
|
+
],
|
|
35
|
+
enabled: Annotated[
|
|
36
|
+
bool | str, Field(description="True to enable, False to disable")
|
|
37
|
+
],
|
|
38
|
+
) -> dict[str, Any]:
|
|
39
|
+
"""Enable/disable entity. Disabled entities don't appear in UI.
|
|
40
|
+
|
|
41
|
+
Use ha_search_entities() or ha_get_device() to find entity IDs.
|
|
42
|
+
"""
|
|
43
|
+
try:
|
|
44
|
+
enabled_bool = coerce_bool_param(enabled, "enabled")
|
|
45
|
+
|
|
46
|
+
message = {
|
|
47
|
+
"type": "config/entity_registry/update",
|
|
48
|
+
"entity_id": entity_id,
|
|
49
|
+
"disabled_by": None if enabled_bool else "user",
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
result = await client.send_websocket_message(message)
|
|
53
|
+
|
|
54
|
+
if result.get("success"):
|
|
55
|
+
entity_entry = result.get("result", {}).get("entity_entry", {})
|
|
56
|
+
return {
|
|
57
|
+
"success": True,
|
|
58
|
+
"entity_id": entity_id,
|
|
59
|
+
"enabled": entity_entry.get("disabled_by") is None,
|
|
60
|
+
"message": f"Entity {'enabled' if enabled_bool else 'disabled'}",
|
|
61
|
+
}
|
|
62
|
+
else:
|
|
63
|
+
error = result.get("error", {})
|
|
64
|
+
error_msg = (
|
|
65
|
+
error.get("message", str(error))
|
|
66
|
+
if isinstance(error, dict)
|
|
67
|
+
else str(error)
|
|
68
|
+
)
|
|
69
|
+
return {
|
|
70
|
+
"success": False,
|
|
71
|
+
"error": f"Failed to {'enable' if enabled_bool else 'disable'}: {error_msg}",
|
|
72
|
+
"entity_id": entity_id,
|
|
73
|
+
}
|
|
74
|
+
except Exception as e:
|
|
75
|
+
logger.error(f"Error setting entity enabled: {e}")
|
|
76
|
+
return exception_to_structured_error(e, context={"entity_id": entity_id})
|