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,223 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Config Entry Flow API tools for Home Assistant MCP server.
|
|
3
|
+
|
|
4
|
+
This module provides tools for creating and managing config entry flow-based
|
|
5
|
+
helpers (template, group, utility_meter, etc.) via the Config Entry Flow API.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
from typing import Annotated, Any, Literal
|
|
10
|
+
|
|
11
|
+
from pydantic import Field
|
|
12
|
+
|
|
13
|
+
from .helpers import exception_to_structured_error, log_tool_usage
|
|
14
|
+
from .util_helpers import parse_json_param
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
# 15 helpers that use Config Entry Flow API (Issue #324)
|
|
19
|
+
SUPPORTED_HELPERS = Literal[
|
|
20
|
+
"template",
|
|
21
|
+
"group",
|
|
22
|
+
"utility_meter",
|
|
23
|
+
"derivative",
|
|
24
|
+
"min_max",
|
|
25
|
+
"threshold",
|
|
26
|
+
"integration",
|
|
27
|
+
"statistics",
|
|
28
|
+
"trend",
|
|
29
|
+
"random",
|
|
30
|
+
"filter",
|
|
31
|
+
"tod",
|
|
32
|
+
"generic_thermostat",
|
|
33
|
+
"switch_as_x",
|
|
34
|
+
"generic_hygrostat",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def register_config_entry_flow_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
|
|
39
|
+
"""Register Config Entry Flow API tools with the MCP server."""
|
|
40
|
+
|
|
41
|
+
async def _handle_flow_steps(
|
|
42
|
+
flow_id: str, config: dict[str, Any]
|
|
43
|
+
) -> dict[str, Any]:
|
|
44
|
+
"""
|
|
45
|
+
Handle multi-step flow internally (max 10 steps).
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
flow_id: Flow ID from start_config_flow
|
|
49
|
+
config: Configuration data to submit
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
Result dict with success/error and flow details
|
|
53
|
+
"""
|
|
54
|
+
max_steps = 10
|
|
55
|
+
for step_num in range(max_steps):
|
|
56
|
+
result = await client.submit_config_flow_step(flow_id, config)
|
|
57
|
+
|
|
58
|
+
result_type = result.get("type")
|
|
59
|
+
if result_type == "create_entry":
|
|
60
|
+
return {"success": True, "entry": result}
|
|
61
|
+
elif result_type == "abort":
|
|
62
|
+
return {
|
|
63
|
+
"success": False,
|
|
64
|
+
"error": f"Flow aborted: {result.get('reason')}",
|
|
65
|
+
"details": result,
|
|
66
|
+
}
|
|
67
|
+
elif result_type == "form":
|
|
68
|
+
# Need more input - for unified tool, this is an error
|
|
69
|
+
return {
|
|
70
|
+
"success": False,
|
|
71
|
+
"error": "Multi-step flow requires additional input",
|
|
72
|
+
"step_id": result.get("step_id"),
|
|
73
|
+
"data_schema": result.get("data_schema"),
|
|
74
|
+
"suggestion": "This helper may require manual configuration through the Home Assistant UI",
|
|
75
|
+
}
|
|
76
|
+
else:
|
|
77
|
+
# Unexpected flow result type
|
|
78
|
+
return {
|
|
79
|
+
"success": False,
|
|
80
|
+
"error": f"Unexpected flow result type: {result_type}",
|
|
81
|
+
"details": result,
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
"success": False,
|
|
86
|
+
"error": f"Flow exceeded {max_steps} steps",
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
@mcp.tool(
|
|
90
|
+
annotations={
|
|
91
|
+
"destructiveHint": True,
|
|
92
|
+
"tags": ["config"],
|
|
93
|
+
"title": "Create Config Entry Helper",
|
|
94
|
+
}
|
|
95
|
+
)
|
|
96
|
+
@log_tool_usage
|
|
97
|
+
async def ha_create_config_entry_helper(
|
|
98
|
+
helper_type: Annotated[
|
|
99
|
+
SUPPORTED_HELPERS, Field(description="Helper type")
|
|
100
|
+
],
|
|
101
|
+
config: Annotated[
|
|
102
|
+
str | dict, Field(description="Helper config (JSON or dict)")
|
|
103
|
+
],
|
|
104
|
+
) -> dict[str, Any]:
|
|
105
|
+
"""Create Config Entry Flow helper (template, group, utility_meter, etc.).
|
|
106
|
+
|
|
107
|
+
Supports 15 helper types that use Config Entry Flow API.
|
|
108
|
+
Use ha_get_helper_schema(helper_type) to discover required config fields.
|
|
109
|
+
"""
|
|
110
|
+
try:
|
|
111
|
+
flow_id = None # Track flow_id for error context
|
|
112
|
+
|
|
113
|
+
# Parse config if string
|
|
114
|
+
if isinstance(config, str):
|
|
115
|
+
parsed_config = parse_json_param(config)
|
|
116
|
+
if not isinstance(parsed_config, dict):
|
|
117
|
+
return {
|
|
118
|
+
"success": False,
|
|
119
|
+
"error": "Config must be a dictionary/object",
|
|
120
|
+
}
|
|
121
|
+
config_dict: dict[str, Any] = parsed_config
|
|
122
|
+
else:
|
|
123
|
+
config_dict = config
|
|
124
|
+
|
|
125
|
+
# Start flow
|
|
126
|
+
flow_result = await client.start_config_flow(helper_type)
|
|
127
|
+
flow_id = flow_result.get("flow_id")
|
|
128
|
+
|
|
129
|
+
if not flow_id:
|
|
130
|
+
return {
|
|
131
|
+
"success": False,
|
|
132
|
+
"error": "Failed to start config flow",
|
|
133
|
+
"details": flow_result,
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
# Handle flow steps
|
|
137
|
+
result = await _handle_flow_steps(flow_id, config_dict)
|
|
138
|
+
|
|
139
|
+
if result.get("success"):
|
|
140
|
+
entry = result["entry"].get("result", {})
|
|
141
|
+
return {
|
|
142
|
+
"success": True,
|
|
143
|
+
"entry_id": entry.get("entry_id"),
|
|
144
|
+
"title": entry.get("title"),
|
|
145
|
+
"domain": helper_type,
|
|
146
|
+
"message": f"{helper_type} helper created successfully",
|
|
147
|
+
}
|
|
148
|
+
else:
|
|
149
|
+
return result
|
|
150
|
+
|
|
151
|
+
except Exception as e:
|
|
152
|
+
error_msg = f"Error creating {helper_type} helper"
|
|
153
|
+
if flow_id:
|
|
154
|
+
error_msg += f" (flow_id: {flow_id})"
|
|
155
|
+
logger.error(f"{error_msg}: {e}")
|
|
156
|
+
|
|
157
|
+
context = {"helper_type": helper_type}
|
|
158
|
+
if flow_id:
|
|
159
|
+
context["flow_id"] = flow_id
|
|
160
|
+
return exception_to_structured_error(e, context=context)
|
|
161
|
+
|
|
162
|
+
@mcp.tool(
|
|
163
|
+
annotations={
|
|
164
|
+
"readOnlyHint": True,
|
|
165
|
+
"tags": ["config"],
|
|
166
|
+
"title": "Get Helper Schema",
|
|
167
|
+
}
|
|
168
|
+
)
|
|
169
|
+
@log_tool_usage
|
|
170
|
+
async def ha_get_helper_schema(
|
|
171
|
+
helper_type: Annotated[SUPPORTED_HELPERS, Field(description="Helper type")],
|
|
172
|
+
) -> dict[str, Any]:
|
|
173
|
+
"""Get configuration schema for a helper type.
|
|
174
|
+
|
|
175
|
+
Returns the form fields and their types needed to create this helper.
|
|
176
|
+
Use before ha_create_config_entry_helper to understand required config.
|
|
177
|
+
"""
|
|
178
|
+
try:
|
|
179
|
+
# Start flow but don't submit anything - just get the schema
|
|
180
|
+
flow_result = await client.start_config_flow(helper_type)
|
|
181
|
+
|
|
182
|
+
flow_type = flow_result.get("type")
|
|
183
|
+
|
|
184
|
+
# Handle different flow types
|
|
185
|
+
if flow_type == "form":
|
|
186
|
+
# Standard form with data_schema
|
|
187
|
+
return {
|
|
188
|
+
"success": True,
|
|
189
|
+
"helper_type": helper_type,
|
|
190
|
+
"flow_type": "form",
|
|
191
|
+
"step_id": flow_result.get("step_id"),
|
|
192
|
+
"data_schema": flow_result.get("data_schema", []),
|
|
193
|
+
"description_placeholders": flow_result.get(
|
|
194
|
+
"description_placeholders", {}
|
|
195
|
+
),
|
|
196
|
+
"errors": flow_result.get("errors", {}),
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
elif flow_type == "menu":
|
|
200
|
+
# Menu selection (e.g., group type selection)
|
|
201
|
+
return {
|
|
202
|
+
"success": True,
|
|
203
|
+
"helper_type": helper_type,
|
|
204
|
+
"flow_type": "menu",
|
|
205
|
+
"step_id": flow_result.get("step_id"),
|
|
206
|
+
"menu_options": flow_result.get("menu_options", []),
|
|
207
|
+
"description_placeholders": flow_result.get(
|
|
208
|
+
"description_placeholders", {}
|
|
209
|
+
),
|
|
210
|
+
"note": "This helper requires selecting from a menu first. Choose an option and submit to get the actual configuration schema.",
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
else:
|
|
214
|
+
# Unexpected flow type
|
|
215
|
+
return {
|
|
216
|
+
"success": False,
|
|
217
|
+
"error": f"Unexpected flow type: {flow_type}",
|
|
218
|
+
"details": flow_result,
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
except Exception as e:
|
|
222
|
+
logger.error(f"Error getting helper schema: {e}")
|
|
223
|
+
return exception_to_structured_error(e, context={"helper_type": helper_type})
|