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.
Files changed (71) hide show
  1. ha_mcp/__init__.py +51 -0
  2. ha_mcp/__main__.py +753 -0
  3. ha_mcp/_pypi_marker +0 -0
  4. ha_mcp/auth/__init__.py +10 -0
  5. ha_mcp/auth/consent_form.py +501 -0
  6. ha_mcp/auth/provider.py +786 -0
  7. ha_mcp/client/__init__.py +10 -0
  8. ha_mcp/client/rest_client.py +924 -0
  9. ha_mcp/client/websocket_client.py +656 -0
  10. ha_mcp/client/websocket_listener.py +340 -0
  11. ha_mcp/config.py +175 -0
  12. ha_mcp/errors.py +399 -0
  13. ha_mcp/py.typed +0 -0
  14. ha_mcp/resources/card_types.json +48 -0
  15. ha_mcp/resources/dashboard_guide.md +573 -0
  16. ha_mcp/server.py +189 -0
  17. ha_mcp/smoke_test.py +108 -0
  18. ha_mcp/tools/__init__.py +11 -0
  19. ha_mcp/tools/backup.py +457 -0
  20. ha_mcp/tools/device_control.py +687 -0
  21. ha_mcp/tools/enhanced.py +191 -0
  22. ha_mcp/tools/helpers.py +184 -0
  23. ha_mcp/tools/registry.py +199 -0
  24. ha_mcp/tools/smart_search.py +797 -0
  25. ha_mcp/tools/tools_addons.py +343 -0
  26. ha_mcp/tools/tools_areas.py +478 -0
  27. ha_mcp/tools/tools_blueprints.py +279 -0
  28. ha_mcp/tools/tools_bug_report.py +570 -0
  29. ha_mcp/tools/tools_calendar.py +391 -0
  30. ha_mcp/tools/tools_camera.py +149 -0
  31. ha_mcp/tools/tools_config_automations.py +508 -0
  32. ha_mcp/tools/tools_config_dashboards.py +1502 -0
  33. ha_mcp/tools/tools_config_entry_flow.py +223 -0
  34. ha_mcp/tools/tools_config_helpers.py +925 -0
  35. ha_mcp/tools/tools_config_info.py +258 -0
  36. ha_mcp/tools/tools_config_scripts.py +267 -0
  37. ha_mcp/tools/tools_entities.py +76 -0
  38. ha_mcp/tools/tools_filesystem.py +589 -0
  39. ha_mcp/tools/tools_groups.py +306 -0
  40. ha_mcp/tools/tools_hacs.py +787 -0
  41. ha_mcp/tools/tools_history.py +714 -0
  42. ha_mcp/tools/tools_integrations.py +297 -0
  43. ha_mcp/tools/tools_labels.py +713 -0
  44. ha_mcp/tools/tools_mcp_component.py +305 -0
  45. ha_mcp/tools/tools_registry.py +1115 -0
  46. ha_mcp/tools/tools_resources.py +622 -0
  47. ha_mcp/tools/tools_search.py +573 -0
  48. ha_mcp/tools/tools_service.py +211 -0
  49. ha_mcp/tools/tools_services.py +352 -0
  50. ha_mcp/tools/tools_system.py +363 -0
  51. ha_mcp/tools/tools_todo.py +530 -0
  52. ha_mcp/tools/tools_traces.py +496 -0
  53. ha_mcp/tools/tools_updates.py +476 -0
  54. ha_mcp/tools/tools_utility.py +519 -0
  55. ha_mcp/tools/tools_voice_assistant.py +345 -0
  56. ha_mcp/tools/tools_zones.py +387 -0
  57. ha_mcp/tools/util_helpers.py +199 -0
  58. ha_mcp/utils/__init__.py +30 -0
  59. ha_mcp/utils/domain_handlers.py +380 -0
  60. ha_mcp/utils/fuzzy_search.py +339 -0
  61. ha_mcp/utils/operation_manager.py +442 -0
  62. ha_mcp/utils/usage_logger.py +290 -0
  63. ha_mcp_dev-6.3.1.dev137.dist-info/METADATA +229 -0
  64. ha_mcp_dev-6.3.1.dev137.dist-info/RECORD +71 -0
  65. ha_mcp_dev-6.3.1.dev137.dist-info/WHEEL +5 -0
  66. ha_mcp_dev-6.3.1.dev137.dist-info/entry_points.txt +7 -0
  67. ha_mcp_dev-6.3.1.dev137.dist-info/licenses/LICENSE +21 -0
  68. ha_mcp_dev-6.3.1.dev137.dist-info/top_level.txt +2 -0
  69. tests/__init__.py +1 -0
  70. tests/test_constants.py +15 -0
  71. 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
+ }