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,380 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Domain-specific handlers for Home Assistant entities.
|
|
3
|
+
|
|
4
|
+
This module provides domain-specific configuration and logic for handling
|
|
5
|
+
different types of Home Assistant entities (lights, climate, covers, etc.).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
# Domain-specific handler configurations
|
|
11
|
+
DOMAIN_HANDLERS = {
|
|
12
|
+
"light": {
|
|
13
|
+
"valid_actions": ["on", "off", "toggle", "set", "adjust"],
|
|
14
|
+
"parameters": [
|
|
15
|
+
"brightness",
|
|
16
|
+
"color_temp",
|
|
17
|
+
"rgb_color",
|
|
18
|
+
"effect",
|
|
19
|
+
"kelvin",
|
|
20
|
+
"hs_color",
|
|
21
|
+
],
|
|
22
|
+
"quick_actions": ["toggle", "dim", "brighten"],
|
|
23
|
+
"state_attributes": ["brightness", "color_temp", "rgb_color"],
|
|
24
|
+
"supports_dimming": True,
|
|
25
|
+
"supports_color": True,
|
|
26
|
+
},
|
|
27
|
+
"climate": {
|
|
28
|
+
"valid_actions": ["on", "off", "set", "heat", "cool", "auto", "heat_cool"],
|
|
29
|
+
"parameters": [
|
|
30
|
+
"temperature",
|
|
31
|
+
"target_temp_high",
|
|
32
|
+
"target_temp_low",
|
|
33
|
+
"hvac_mode",
|
|
34
|
+
"fan_mode",
|
|
35
|
+
],
|
|
36
|
+
"quick_actions": ["warmer", "cooler", "auto_mode"],
|
|
37
|
+
"state_attributes": ["current_temperature", "target_temperature", "hvac_mode"],
|
|
38
|
+
"supports_temperature": True,
|
|
39
|
+
"supports_hvac_modes": True,
|
|
40
|
+
},
|
|
41
|
+
"cover": {
|
|
42
|
+
"valid_actions": ["open", "close", "stop", "toggle", "set"],
|
|
43
|
+
"parameters": ["position", "tilt_position"],
|
|
44
|
+
"quick_actions": ["open", "close", "toggle"],
|
|
45
|
+
"state_attributes": ["current_position", "current_tilt_position"],
|
|
46
|
+
"supports_position": True,
|
|
47
|
+
"supports_tilt": True,
|
|
48
|
+
},
|
|
49
|
+
"switch": {
|
|
50
|
+
"valid_actions": ["on", "off", "toggle"],
|
|
51
|
+
"parameters": [],
|
|
52
|
+
"quick_actions": ["toggle"],
|
|
53
|
+
"state_attributes": ["state"],
|
|
54
|
+
"supports_dimming": False,
|
|
55
|
+
"supports_color": False,
|
|
56
|
+
},
|
|
57
|
+
"media_player": {
|
|
58
|
+
"valid_actions": ["play", "pause", "stop", "toggle", "set", "next", "previous"],
|
|
59
|
+
"parameters": [
|
|
60
|
+
"volume_level",
|
|
61
|
+
"media_content_id",
|
|
62
|
+
"media_content_type",
|
|
63
|
+
"source",
|
|
64
|
+
],
|
|
65
|
+
"quick_actions": ["play_pause", "volume_up", "volume_down"],
|
|
66
|
+
"state_attributes": ["volume_level", "media_title", "source"],
|
|
67
|
+
"supports_volume": True,
|
|
68
|
+
"supports_media": True,
|
|
69
|
+
},
|
|
70
|
+
"fan": {
|
|
71
|
+
"valid_actions": ["on", "off", "toggle", "set"],
|
|
72
|
+
"parameters": ["speed", "percentage", "preset_mode", "direction"],
|
|
73
|
+
"quick_actions": ["toggle", "speed_up", "speed_down"],
|
|
74
|
+
"state_attributes": ["percentage", "preset_mode"],
|
|
75
|
+
"supports_speed": True,
|
|
76
|
+
"supports_direction": True,
|
|
77
|
+
},
|
|
78
|
+
"vacuum": {
|
|
79
|
+
"valid_actions": ["start", "stop", "pause", "return_to_base", "clean_spot"],
|
|
80
|
+
"parameters": ["fan_speed", "spot_area"],
|
|
81
|
+
"quick_actions": ["start_cleaning", "return_home"],
|
|
82
|
+
"state_attributes": ["battery_level", "status"],
|
|
83
|
+
"supports_zones": True,
|
|
84
|
+
"supports_mapping": True,
|
|
85
|
+
},
|
|
86
|
+
"lock": {
|
|
87
|
+
"valid_actions": ["lock", "unlock", "open"],
|
|
88
|
+
"parameters": ["code"],
|
|
89
|
+
"quick_actions": ["toggle_lock"],
|
|
90
|
+
"state_attributes": ["locked"],
|
|
91
|
+
"supports_codes": True,
|
|
92
|
+
"security_sensitive": True,
|
|
93
|
+
},
|
|
94
|
+
"alarm_control_panel": {
|
|
95
|
+
"valid_actions": ["arm_home", "arm_away", "arm_night", "disarm"],
|
|
96
|
+
"parameters": ["code"],
|
|
97
|
+
"quick_actions": ["arm", "disarm"],
|
|
98
|
+
"state_attributes": ["state"],
|
|
99
|
+
"security_sensitive": True,
|
|
100
|
+
"requires_code": True,
|
|
101
|
+
},
|
|
102
|
+
"water_heater": {
|
|
103
|
+
"valid_actions": ["on", "off", "set"],
|
|
104
|
+
"parameters": ["temperature", "operation_mode"],
|
|
105
|
+
"quick_actions": ["toggle", "temperature_up", "temperature_down"],
|
|
106
|
+
"state_attributes": ["current_temperature", "target_temperature"],
|
|
107
|
+
"supports_temperature": True,
|
|
108
|
+
"supports_modes": True,
|
|
109
|
+
},
|
|
110
|
+
"humidifier": {
|
|
111
|
+
"valid_actions": ["on", "off", "toggle", "set"],
|
|
112
|
+
"parameters": ["humidity", "mode"],
|
|
113
|
+
"quick_actions": ["toggle", "humidity_up", "humidity_down"],
|
|
114
|
+
"state_attributes": ["current_humidity", "target_humidity"],
|
|
115
|
+
"supports_humidity": True,
|
|
116
|
+
"supports_modes": True,
|
|
117
|
+
},
|
|
118
|
+
"camera": {
|
|
119
|
+
"valid_actions": ["snapshot", "record", "stream"],
|
|
120
|
+
"parameters": ["filename", "duration"],
|
|
121
|
+
"quick_actions": ["take_snapshot"],
|
|
122
|
+
"state_attributes": ["entity_picture", "access_token"],
|
|
123
|
+
"supports_streaming": True,
|
|
124
|
+
"supports_recording": True,
|
|
125
|
+
},
|
|
126
|
+
"scene": {
|
|
127
|
+
"valid_actions": ["turn_on", "activate"],
|
|
128
|
+
"parameters": [],
|
|
129
|
+
"quick_actions": ["activate"],
|
|
130
|
+
"state_attributes": ["state"],
|
|
131
|
+
"is_stateless": True,
|
|
132
|
+
"action_only": True,
|
|
133
|
+
},
|
|
134
|
+
"script": {
|
|
135
|
+
"valid_actions": ["turn_on", "turn_off", "toggle"],
|
|
136
|
+
"parameters": [],
|
|
137
|
+
"quick_actions": ["run", "stop"],
|
|
138
|
+
"state_attributes": ["state"],
|
|
139
|
+
"supports_execution": True,
|
|
140
|
+
"can_be_stopped": True,
|
|
141
|
+
},
|
|
142
|
+
"automation": {
|
|
143
|
+
"valid_actions": ["turn_on", "turn_off", "toggle", "trigger"],
|
|
144
|
+
"parameters": [],
|
|
145
|
+
"quick_actions": ["enable", "disable", "trigger"],
|
|
146
|
+
"state_attributes": ["state", "last_triggered"],
|
|
147
|
+
"supports_enable_disable": True,
|
|
148
|
+
"supports_manual_trigger": True,
|
|
149
|
+
},
|
|
150
|
+
# Read-only domains (sensors, etc.)
|
|
151
|
+
"sensor": {
|
|
152
|
+
"valid_actions": [],
|
|
153
|
+
"parameters": [],
|
|
154
|
+
"quick_actions": [],
|
|
155
|
+
"state_attributes": ["state", "unit_of_measurement"],
|
|
156
|
+
"read_only": True,
|
|
157
|
+
"provides_data": True,
|
|
158
|
+
},
|
|
159
|
+
"binary_sensor": {
|
|
160
|
+
"valid_actions": [],
|
|
161
|
+
"parameters": [],
|
|
162
|
+
"quick_actions": [],
|
|
163
|
+
"state_attributes": ["state", "device_class"],
|
|
164
|
+
"read_only": True,
|
|
165
|
+
"provides_data": True,
|
|
166
|
+
},
|
|
167
|
+
"device_tracker": {
|
|
168
|
+
"valid_actions": [],
|
|
169
|
+
"parameters": [],
|
|
170
|
+
"quick_actions": [],
|
|
171
|
+
"state_attributes": ["state", "latitude", "longitude"],
|
|
172
|
+
"read_only": True,
|
|
173
|
+
"provides_location": True,
|
|
174
|
+
},
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def get_domain_handler(entity_id: str) -> dict[str, Any]:
|
|
179
|
+
"""Get domain-specific configuration for an entity.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
entity_id: Full entity ID (e.g., 'light.living_room')
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
Domain handler configuration dictionary
|
|
186
|
+
"""
|
|
187
|
+
if "." not in entity_id:
|
|
188
|
+
# Fallback for invalid entity ID format
|
|
189
|
+
return get_default_handler()
|
|
190
|
+
|
|
191
|
+
domain = entity_id.split(".")[0]
|
|
192
|
+
return DOMAIN_HANDLERS.get(domain, get_default_handler())
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def get_default_handler() -> dict[str, Any]:
|
|
196
|
+
"""Get default handler for unknown domains.
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
Default handler configuration
|
|
200
|
+
"""
|
|
201
|
+
return {
|
|
202
|
+
"valid_actions": ["on", "off", "toggle"],
|
|
203
|
+
"parameters": [],
|
|
204
|
+
"quick_actions": ["toggle"],
|
|
205
|
+
"state_attributes": ["state"],
|
|
206
|
+
"supports_basic_control": True,
|
|
207
|
+
"unknown_domain": True,
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def get_domain_capabilities(domain: str) -> dict[str, Any]:
|
|
212
|
+
"""Get capabilities for a specific domain.
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
domain: Domain name (e.g., 'light', 'climate')
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
Dictionary of domain capabilities
|
|
219
|
+
"""
|
|
220
|
+
handler = DOMAIN_HANDLERS.get(domain, get_default_handler())
|
|
221
|
+
|
|
222
|
+
capabilities = {
|
|
223
|
+
"domain": domain,
|
|
224
|
+
"controllable": len(handler.get("valid_actions", [])) > 0,
|
|
225
|
+
"read_only": handler.get("read_only", False),
|
|
226
|
+
"security_sensitive": handler.get("security_sensitive", False),
|
|
227
|
+
"requires_parameters": len(handler.get("parameters", [])) > 0,
|
|
228
|
+
"has_quick_actions": len(handler.get("quick_actions", [])) > 0,
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
# Add specific capability flags
|
|
232
|
+
for capability in [
|
|
233
|
+
"supports_dimming",
|
|
234
|
+
"supports_color",
|
|
235
|
+
"supports_temperature",
|
|
236
|
+
"supports_position",
|
|
237
|
+
"supports_volume",
|
|
238
|
+
"supports_speed",
|
|
239
|
+
]:
|
|
240
|
+
if capability in handler:
|
|
241
|
+
capabilities[capability] = handler[capability]
|
|
242
|
+
|
|
243
|
+
return capabilities
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def get_all_controllable_domains() -> list[str]:
|
|
247
|
+
"""Get list of all controllable domains.
|
|
248
|
+
|
|
249
|
+
Returns:
|
|
250
|
+
List of domain names that support control actions
|
|
251
|
+
"""
|
|
252
|
+
controllable = []
|
|
253
|
+
for domain, handler in DOMAIN_HANDLERS.items():
|
|
254
|
+
if handler.get("valid_actions") and not handler.get("read_only", False):
|
|
255
|
+
controllable.append(domain)
|
|
256
|
+
|
|
257
|
+
return sorted(controllable)
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def get_all_sensor_domains() -> list[str]:
|
|
261
|
+
"""Get list of all sensor/read-only domains.
|
|
262
|
+
|
|
263
|
+
Returns:
|
|
264
|
+
List of domain names that provide data but don't support control
|
|
265
|
+
"""
|
|
266
|
+
sensors = []
|
|
267
|
+
for domain, handler in DOMAIN_HANDLERS.items():
|
|
268
|
+
if handler.get("read_only", False) or handler.get("provides_data", False):
|
|
269
|
+
sensors.append(domain)
|
|
270
|
+
|
|
271
|
+
return sorted(sensors)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def validate_action_for_domain(domain: str, action: str) -> tuple[bool, str]:
|
|
275
|
+
"""Validate if an action is supported for a domain.
|
|
276
|
+
|
|
277
|
+
Args:
|
|
278
|
+
domain: Domain name
|
|
279
|
+
action: Action to validate
|
|
280
|
+
|
|
281
|
+
Returns:
|
|
282
|
+
Tuple of (is_valid, error_message_if_invalid)
|
|
283
|
+
"""
|
|
284
|
+
handler = DOMAIN_HANDLERS.get(domain, get_default_handler())
|
|
285
|
+
valid_actions = handler.get("valid_actions", [])
|
|
286
|
+
|
|
287
|
+
if not valid_actions:
|
|
288
|
+
return False, f"Domain '{domain}' does not support any control actions"
|
|
289
|
+
|
|
290
|
+
if action not in valid_actions:
|
|
291
|
+
return (
|
|
292
|
+
False,
|
|
293
|
+
f"Action '{action}' not supported for domain '{domain}'. Valid actions: {', '.join(valid_actions)}",
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
return True, ""
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def get_suggested_parameters(domain: str, action: str) -> list[str]:
|
|
300
|
+
"""Get suggested parameters for a domain/action combination.
|
|
301
|
+
|
|
302
|
+
Args:
|
|
303
|
+
domain: Domain name
|
|
304
|
+
action: Action being performed
|
|
305
|
+
|
|
306
|
+
Returns:
|
|
307
|
+
List of suggested parameter names
|
|
308
|
+
"""
|
|
309
|
+
handler = DOMAIN_HANDLERS.get(domain, get_default_handler())
|
|
310
|
+
all_params = handler.get("parameters", [])
|
|
311
|
+
|
|
312
|
+
# Action-specific parameter suggestions
|
|
313
|
+
action_params = {
|
|
314
|
+
"light": {
|
|
315
|
+
"set": ["brightness", "color_temp", "rgb_color"],
|
|
316
|
+
"on": ["brightness", "color_temp"],
|
|
317
|
+
"adjust": ["brightness"],
|
|
318
|
+
},
|
|
319
|
+
"climate": {
|
|
320
|
+
"set": ["temperature"],
|
|
321
|
+
"heat": ["temperature"],
|
|
322
|
+
"cool": ["temperature"],
|
|
323
|
+
},
|
|
324
|
+
"cover": {"set": ["position"], "open": [], "close": []},
|
|
325
|
+
"media_player": {
|
|
326
|
+
"set": ["volume_level"],
|
|
327
|
+
"play": ["media_content_id", "media_content_type"],
|
|
328
|
+
},
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if domain in action_params and action in action_params[domain]:
|
|
332
|
+
return action_params[domain][action]
|
|
333
|
+
|
|
334
|
+
return list(all_params)
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def is_security_sensitive_domain(domain: str) -> bool:
|
|
338
|
+
"""Check if a domain is security sensitive.
|
|
339
|
+
|
|
340
|
+
Args:
|
|
341
|
+
domain: Domain name to check
|
|
342
|
+
|
|
343
|
+
Returns:
|
|
344
|
+
True if domain requires extra security considerations
|
|
345
|
+
"""
|
|
346
|
+
handler = DOMAIN_HANDLERS.get(domain, {})
|
|
347
|
+
return bool(handler.get("security_sensitive", False))
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
def get_domain_description(domain: str) -> str:
|
|
351
|
+
"""Get human-readable description of a domain.
|
|
352
|
+
|
|
353
|
+
Args:
|
|
354
|
+
domain: Domain name
|
|
355
|
+
|
|
356
|
+
Returns:
|
|
357
|
+
Human-readable description
|
|
358
|
+
"""
|
|
359
|
+
descriptions = {
|
|
360
|
+
"light": "Lighting devices (bulbs, strips, switches)",
|
|
361
|
+
"climate": "Climate control (thermostats, HVAC systems)",
|
|
362
|
+
"cover": "Window coverings (blinds, curtains, garage doors)",
|
|
363
|
+
"switch": "Simple on/off switches and outlets",
|
|
364
|
+
"media_player": "Audio/video players and streaming devices",
|
|
365
|
+
"fan": "Fans and ventilation devices",
|
|
366
|
+
"vacuum": "Robotic vacuums and cleaning devices",
|
|
367
|
+
"lock": "Smart locks and door controls",
|
|
368
|
+
"alarm_control_panel": "Security system panels and alarms",
|
|
369
|
+
"water_heater": "Water heating systems",
|
|
370
|
+
"humidifier": "Humidity control devices",
|
|
371
|
+
"camera": "Security cameras and video devices",
|
|
372
|
+
"scene": "Predefined device state combinations",
|
|
373
|
+
"script": "Custom automation scripts",
|
|
374
|
+
"automation": "Automated rules and triggers",
|
|
375
|
+
"sensor": "Environmental and status sensors",
|
|
376
|
+
"binary_sensor": "On/off status sensors",
|
|
377
|
+
"device_tracker": "Location tracking devices",
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
return descriptions.get(domain, f"Unknown domain: {domain}")
|