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,279 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Blueprint management tools for Home Assistant.
|
|
3
|
+
|
|
4
|
+
This module provides tools for discovering, retrieving, and importing
|
|
5
|
+
Home Assistant blueprints for automations and scripts.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
from typing import Annotated, Any
|
|
10
|
+
|
|
11
|
+
from pydantic import Field
|
|
12
|
+
|
|
13
|
+
from .helpers import log_tool_usage
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def register_blueprint_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
|
|
19
|
+
"""Register Home Assistant blueprint management tools."""
|
|
20
|
+
|
|
21
|
+
def _format_blueprint_list(blueprints_data: dict[str, Any], domain: str) -> dict[str, Any]:
|
|
22
|
+
"""Format blueprint data into list response structure.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
blueprints_data: Raw blueprint data from WebSocket API
|
|
26
|
+
domain: Blueprint domain (automation or script)
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
Formatted response with blueprints list, count, and domain
|
|
30
|
+
"""
|
|
31
|
+
blueprints = []
|
|
32
|
+
for bp_path, metadata in blueprints_data.items():
|
|
33
|
+
blueprint_info = {
|
|
34
|
+
"path": bp_path,
|
|
35
|
+
"domain": domain,
|
|
36
|
+
"name": metadata.get("name", bp_path.split("/")[-1].replace(".yaml", "")),
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
# Add optional metadata if available
|
|
40
|
+
if "metadata" in metadata:
|
|
41
|
+
meta = metadata["metadata"]
|
|
42
|
+
blueprint_info.update({
|
|
43
|
+
"description": meta.get("description"),
|
|
44
|
+
"source_url": meta.get("source_url"),
|
|
45
|
+
"author": meta.get("author"),
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
blueprints.append(blueprint_info)
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
"success": True,
|
|
52
|
+
"domain": domain,
|
|
53
|
+
"count": len(blueprints),
|
|
54
|
+
"blueprints": blueprints,
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
@mcp.tool(annotations={"idempotentHint": True, "readOnlyHint": True, "tags": ["blueprint"], "title": "Get Blueprint"})
|
|
58
|
+
@log_tool_usage
|
|
59
|
+
async def ha_get_blueprint(
|
|
60
|
+
path: Annotated[
|
|
61
|
+
str | None,
|
|
62
|
+
Field(
|
|
63
|
+
description="Blueprint path to get details for (e.g., 'homeassistant/motion_light.yaml'). "
|
|
64
|
+
"If omitted, lists all blueprints in the domain.",
|
|
65
|
+
default=None,
|
|
66
|
+
),
|
|
67
|
+
] = None,
|
|
68
|
+
domain: Annotated[
|
|
69
|
+
str,
|
|
70
|
+
Field(
|
|
71
|
+
description="Blueprint domain: 'automation' or 'script'",
|
|
72
|
+
default="automation",
|
|
73
|
+
),
|
|
74
|
+
] = "automation",
|
|
75
|
+
) -> dict[str, Any]:
|
|
76
|
+
"""
|
|
77
|
+
Get blueprint information - list all blueprints or get details for a specific one.
|
|
78
|
+
|
|
79
|
+
Without a path: Lists all installed blueprints for the specified domain.
|
|
80
|
+
With a path: Retrieves full blueprint configuration including inputs, triggers,
|
|
81
|
+
conditions, and actions.
|
|
82
|
+
|
|
83
|
+
EXAMPLES:
|
|
84
|
+
- List all automation blueprints: ha_get_blueprint(domain="automation")
|
|
85
|
+
- List script blueprints: ha_get_blueprint(domain="script")
|
|
86
|
+
- Get specific blueprint: ha_get_blueprint(path="homeassistant/motion_light.yaml", domain="automation")
|
|
87
|
+
|
|
88
|
+
RETURNS (when listing):
|
|
89
|
+
- List of blueprints with path, name, and domain information
|
|
90
|
+
- Count of blueprints found
|
|
91
|
+
|
|
92
|
+
RETURNS (when getting specific blueprint):
|
|
93
|
+
- Blueprint metadata (name, description, author, source_url)
|
|
94
|
+
- Input definitions with selectors and defaults
|
|
95
|
+
- Blueprint configuration (triggers, conditions, actions for automations; sequence for scripts)
|
|
96
|
+
"""
|
|
97
|
+
try:
|
|
98
|
+
# Validate domain
|
|
99
|
+
valid_domains = ["automation", "script"]
|
|
100
|
+
if domain not in valid_domains:
|
|
101
|
+
return {
|
|
102
|
+
"success": False,
|
|
103
|
+
"error": f"Invalid domain '{domain}'. Must be one of: {', '.join(valid_domains)}",
|
|
104
|
+
"valid_domains": valid_domains,
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
# Get list of blueprints
|
|
108
|
+
list_response = await client.send_websocket_message(
|
|
109
|
+
{"type": "blueprint/list", "domain": domain}
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
if not list_response.get("success"):
|
|
113
|
+
return {
|
|
114
|
+
"success": False,
|
|
115
|
+
"error": list_response.get("error", "Failed to query blueprints"),
|
|
116
|
+
"domain": domain,
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
blueprints_data = list_response.get("result", {})
|
|
120
|
+
|
|
121
|
+
# If no path provided, return list of all blueprints
|
|
122
|
+
if path is None:
|
|
123
|
+
return _format_blueprint_list(blueprints_data, domain)
|
|
124
|
+
|
|
125
|
+
# Path provided - get specific blueprint details
|
|
126
|
+
if path not in blueprints_data:
|
|
127
|
+
available_paths = list(blueprints_data.keys())[:10]
|
|
128
|
+
return {
|
|
129
|
+
"success": False,
|
|
130
|
+
"error": f"Blueprint not found: {path}",
|
|
131
|
+
"path": path,
|
|
132
|
+
"domain": domain,
|
|
133
|
+
"available_blueprints": available_paths,
|
|
134
|
+
"suggestions": [
|
|
135
|
+
"Use ha_get_blueprint() without path to see all available blueprints",
|
|
136
|
+
"Check the path format (e.g., 'homeassistant/motion_light.yaml')",
|
|
137
|
+
],
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
# Get the blueprint details from the list response
|
|
141
|
+
blueprint_data = blueprints_data[path]
|
|
142
|
+
|
|
143
|
+
# Extract and format blueprint information
|
|
144
|
+
result = {
|
|
145
|
+
"success": True,
|
|
146
|
+
"path": path,
|
|
147
|
+
"domain": domain,
|
|
148
|
+
"name": blueprint_data.get("name", path.split("/")[-1].replace(".yaml", "")),
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
# Add metadata if available
|
|
152
|
+
if "metadata" in blueprint_data:
|
|
153
|
+
meta = blueprint_data["metadata"]
|
|
154
|
+
result["metadata"] = {
|
|
155
|
+
"name": meta.get("name"),
|
|
156
|
+
"description": meta.get("description"),
|
|
157
|
+
"source_url": meta.get("source_url"),
|
|
158
|
+
"author": meta.get("author"),
|
|
159
|
+
"domain": meta.get("domain"),
|
|
160
|
+
"homeassistant": meta.get("homeassistant"),
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
# Add input definitions
|
|
164
|
+
if "input" in meta:
|
|
165
|
+
result["inputs"] = meta["input"]
|
|
166
|
+
|
|
167
|
+
# Add blueprint configuration if available
|
|
168
|
+
if "blueprint" in blueprint_data:
|
|
169
|
+
result["blueprint"] = blueprint_data["blueprint"]
|
|
170
|
+
|
|
171
|
+
return result
|
|
172
|
+
|
|
173
|
+
except Exception as e:
|
|
174
|
+
logger.error(f"Error getting blueprint: {e}")
|
|
175
|
+
return {
|
|
176
|
+
"success": False,
|
|
177
|
+
"path": path,
|
|
178
|
+
"domain": domain,
|
|
179
|
+
"error": str(e),
|
|
180
|
+
"suggestions": [
|
|
181
|
+
"Verify the blueprint path is correct",
|
|
182
|
+
"Use ha_get_blueprint() without path to see available blueprints",
|
|
183
|
+
"Check Home Assistant connection",
|
|
184
|
+
],
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
@mcp.tool(annotations={"destructiveHint": True, "tags": ["blueprint"], "title": "Import Blueprint"})
|
|
188
|
+
@log_tool_usage
|
|
189
|
+
async def ha_import_blueprint(
|
|
190
|
+
url: Annotated[
|
|
191
|
+
str,
|
|
192
|
+
Field(
|
|
193
|
+
description="URL to import blueprint from (GitHub, Home Assistant Community, or direct YAML URL)"
|
|
194
|
+
),
|
|
195
|
+
],
|
|
196
|
+
) -> dict[str, Any]:
|
|
197
|
+
"""
|
|
198
|
+
Import a blueprint from a URL.
|
|
199
|
+
|
|
200
|
+
Imports a blueprint from GitHub, Home Assistant Community forums,
|
|
201
|
+
or any direct URL to a blueprint YAML file.
|
|
202
|
+
|
|
203
|
+
EXAMPLES:
|
|
204
|
+
- Import from GitHub: ha_import_blueprint("https://github.com/user/repo/blob/main/blueprint.yaml")
|
|
205
|
+
- Import from HA Community: ha_import_blueprint("https://community.home-assistant.io/t/motion-light/123456")
|
|
206
|
+
- Import direct YAML: ha_import_blueprint("https://example.com/my-blueprint.yaml")
|
|
207
|
+
|
|
208
|
+
SUPPORTED SOURCES:
|
|
209
|
+
- GitHub repository URLs (will be converted to raw URLs)
|
|
210
|
+
- Home Assistant Community forum posts with blueprint code
|
|
211
|
+
- Direct URLs to YAML blueprint files
|
|
212
|
+
|
|
213
|
+
RETURNS:
|
|
214
|
+
- Import result with the blueprint path where it was saved
|
|
215
|
+
- Blueprint metadata (name, domain, description)
|
|
216
|
+
- Error details if import fails
|
|
217
|
+
"""
|
|
218
|
+
try:
|
|
219
|
+
# Validate URL format
|
|
220
|
+
if not url.startswith(("http://", "https://")):
|
|
221
|
+
return {
|
|
222
|
+
"success": False,
|
|
223
|
+
"error": "Invalid URL format. URL must start with http:// or https://",
|
|
224
|
+
"url": url,
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
# Send WebSocket command to import blueprint
|
|
228
|
+
response = await client.send_websocket_message(
|
|
229
|
+
{"type": "blueprint/import", "url": url}
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
if not response.get("success"):
|
|
233
|
+
error_msg = response.get("error", "Failed to import blueprint")
|
|
234
|
+
|
|
235
|
+
# Provide helpful error messages based on common issues
|
|
236
|
+
suggestions = [
|
|
237
|
+
"Verify the URL is accessible",
|
|
238
|
+
"Ensure the URL points to a valid blueprint YAML file",
|
|
239
|
+
"Check if the blueprint format is compatible with your Home Assistant version",
|
|
240
|
+
]
|
|
241
|
+
|
|
242
|
+
if "already exists" in str(error_msg).lower():
|
|
243
|
+
suggestions.insert(0, "Blueprint already exists - use ha_get_blueprint() to see installed blueprints")
|
|
244
|
+
|
|
245
|
+
return {
|
|
246
|
+
"success": False,
|
|
247
|
+
"error": error_msg,
|
|
248
|
+
"url": url,
|
|
249
|
+
"suggestions": suggestions,
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
# Extract import result
|
|
253
|
+
result_data = response.get("result", {})
|
|
254
|
+
|
|
255
|
+
return {
|
|
256
|
+
"success": True,
|
|
257
|
+
"url": url,
|
|
258
|
+
"imported_blueprint": {
|
|
259
|
+
"path": result_data.get("suggested_filename") or result_data.get("path"),
|
|
260
|
+
"domain": result_data.get("blueprint", {}).get("domain", "automation"),
|
|
261
|
+
"name": result_data.get("blueprint", {}).get("name"),
|
|
262
|
+
"description": result_data.get("blueprint", {}).get("description"),
|
|
263
|
+
},
|
|
264
|
+
"message": "Blueprint imported successfully. Use ha_get_blueprint() to see all installed blueprints.",
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
except Exception as e:
|
|
268
|
+
logger.error(f"Error importing blueprint: {e}")
|
|
269
|
+
return {
|
|
270
|
+
"success": False,
|
|
271
|
+
"url": url,
|
|
272
|
+
"error": str(e),
|
|
273
|
+
"suggestions": [
|
|
274
|
+
"Verify the URL is correct and accessible",
|
|
275
|
+
"Check if the URL points to a valid YAML blueprint file",
|
|
276
|
+
"Ensure Home Assistant has internet access",
|
|
277
|
+
"Try importing from a different source (GitHub, Community, direct URL)",
|
|
278
|
+
],
|
|
279
|
+
}
|