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,343 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Add-on management tools for Home Assistant MCP Server.
|
|
3
|
+
|
|
4
|
+
Provides tools to list installed and available add-ons via the Supervisor API.
|
|
5
|
+
|
|
6
|
+
Note: These tools only work with Home Assistant OS or Supervised installations.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
from typing import Annotated, Any
|
|
11
|
+
|
|
12
|
+
from pydantic import Field
|
|
13
|
+
|
|
14
|
+
from ..client.rest_client import HomeAssistantClient
|
|
15
|
+
from .helpers import get_connected_ws_client, log_tool_usage
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
async def list_addons(
|
|
21
|
+
client: HomeAssistantClient, include_stats: bool = False
|
|
22
|
+
) -> dict[str, Any]:
|
|
23
|
+
"""
|
|
24
|
+
List installed Home Assistant add-ons.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
client: Home Assistant REST client
|
|
28
|
+
include_stats: Include CPU/memory usage statistics
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
Dictionary with installed add-ons and their status.
|
|
32
|
+
"""
|
|
33
|
+
ws_client = None
|
|
34
|
+
|
|
35
|
+
try:
|
|
36
|
+
# Connect to WebSocket
|
|
37
|
+
ws_client, error = await get_connected_ws_client(client.base_url, client.token)
|
|
38
|
+
if error or ws_client is None:
|
|
39
|
+
return error or {
|
|
40
|
+
"success": False,
|
|
41
|
+
"error": "Failed to establish WebSocket connection",
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
# Call Supervisor API to get installed add-ons
|
|
45
|
+
result = await ws_client.send_command(
|
|
46
|
+
"supervisor/api",
|
|
47
|
+
endpoint="/addons",
|
|
48
|
+
method="GET",
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
if not result.get("success"):
|
|
52
|
+
# Check if this is a non-Supervisor installation
|
|
53
|
+
error_msg = str(result.get("error", ""))
|
|
54
|
+
if "not_found" in error_msg.lower() or "unknown" in error_msg.lower():
|
|
55
|
+
return {
|
|
56
|
+
"success": False,
|
|
57
|
+
"error": "Supervisor API not available",
|
|
58
|
+
"suggestion": "This feature requires Home Assistant OS or Supervised installation",
|
|
59
|
+
"details": result,
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
"success": False,
|
|
63
|
+
"error": "Failed to retrieve add-ons list",
|
|
64
|
+
"details": result,
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
# Response structure: result.addons (not result.data.addons)
|
|
68
|
+
data = result.get("result", {})
|
|
69
|
+
addons = data.get("addons", [])
|
|
70
|
+
|
|
71
|
+
# Format add-on information
|
|
72
|
+
formatted_addons = []
|
|
73
|
+
for addon in addons:
|
|
74
|
+
addon_info = {
|
|
75
|
+
"name": addon.get("name"),
|
|
76
|
+
"slug": addon.get("slug"),
|
|
77
|
+
"description": addon.get("description"),
|
|
78
|
+
"version": addon.get("version"),
|
|
79
|
+
"installed": True,
|
|
80
|
+
"state": addon.get("state"),
|
|
81
|
+
"update_available": addon.get("update_available", False),
|
|
82
|
+
"repository": addon.get("repository"),
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
# Include stats if requested
|
|
86
|
+
if include_stats:
|
|
87
|
+
addon_info["stats"] = {
|
|
88
|
+
"cpu_percent": addon.get("cpu_percent"),
|
|
89
|
+
"memory_percent": addon.get("memory_percent"),
|
|
90
|
+
"memory_usage": addon.get("memory_usage"),
|
|
91
|
+
"memory_limit": addon.get("memory_limit"),
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
formatted_addons.append(addon_info)
|
|
95
|
+
|
|
96
|
+
# Count add-ons by state
|
|
97
|
+
running_count = sum(1 for a in addons if a.get("state") == "started")
|
|
98
|
+
update_count = sum(1 for a in addons if a.get("update_available"))
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
"success": True,
|
|
102
|
+
"addons": formatted_addons,
|
|
103
|
+
"summary": {
|
|
104
|
+
"total_installed": len(formatted_addons),
|
|
105
|
+
"running": running_count,
|
|
106
|
+
"stopped": len(formatted_addons) - running_count,
|
|
107
|
+
"updates_available": update_count,
|
|
108
|
+
},
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
except Exception as e:
|
|
112
|
+
logger.error(f"Error listing add-ons: {e}")
|
|
113
|
+
return {
|
|
114
|
+
"success": False,
|
|
115
|
+
"error": f"Failed to list add-ons: {str(e)}",
|
|
116
|
+
"suggestion": "Check Home Assistant connection and Supervisor availability",
|
|
117
|
+
}
|
|
118
|
+
finally:
|
|
119
|
+
if ws_client:
|
|
120
|
+
try:
|
|
121
|
+
await ws_client.disconnect()
|
|
122
|
+
except Exception:
|
|
123
|
+
pass
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
async def list_available_addons(
|
|
127
|
+
client: HomeAssistantClient,
|
|
128
|
+
repository: str | None = None,
|
|
129
|
+
query: str | None = None,
|
|
130
|
+
) -> dict[str, Any]:
|
|
131
|
+
"""
|
|
132
|
+
List add-ons available in the add-on store.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
client: Home Assistant REST client
|
|
136
|
+
repository: Filter by repository slug (e.g., "core", "community")
|
|
137
|
+
query: Search filter for add-on names/descriptions
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
Dictionary with available add-ons and repositories.
|
|
141
|
+
"""
|
|
142
|
+
ws_client = None
|
|
143
|
+
|
|
144
|
+
try:
|
|
145
|
+
# Connect to WebSocket
|
|
146
|
+
ws_client, error = await get_connected_ws_client(client.base_url, client.token)
|
|
147
|
+
if error or ws_client is None:
|
|
148
|
+
return error or {
|
|
149
|
+
"success": False,
|
|
150
|
+
"error": "Failed to establish WebSocket connection",
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
# Call Supervisor API to get store information
|
|
154
|
+
result = await ws_client.send_command(
|
|
155
|
+
"supervisor/api",
|
|
156
|
+
endpoint="/store",
|
|
157
|
+
method="GET",
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
if not result.get("success"):
|
|
161
|
+
# Check if this is a non-Supervisor installation
|
|
162
|
+
error_msg = str(result.get("error", ""))
|
|
163
|
+
if "not_found" in error_msg.lower() or "unknown" in error_msg.lower():
|
|
164
|
+
return {
|
|
165
|
+
"success": False,
|
|
166
|
+
"error": "Supervisor API not available",
|
|
167
|
+
"suggestion": "This feature requires Home Assistant OS or Supervised installation",
|
|
168
|
+
"details": result,
|
|
169
|
+
}
|
|
170
|
+
return {
|
|
171
|
+
"success": False,
|
|
172
|
+
"error": "Failed to retrieve add-on store",
|
|
173
|
+
"details": result,
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
# Response structure: result.addons/repositories (not result.data.*)
|
|
177
|
+
data = result.get("result", {})
|
|
178
|
+
repositories = data.get("repositories", [])
|
|
179
|
+
addons = data.get("addons", [])
|
|
180
|
+
|
|
181
|
+
# Format repository information
|
|
182
|
+
formatted_repos = []
|
|
183
|
+
for repo in repositories:
|
|
184
|
+
formatted_repos.append(
|
|
185
|
+
{
|
|
186
|
+
"slug": repo.get("slug"),
|
|
187
|
+
"name": repo.get("name"),
|
|
188
|
+
"source": repo.get("source"),
|
|
189
|
+
"maintainer": repo.get("maintainer"),
|
|
190
|
+
}
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
# Filter and format add-ons
|
|
194
|
+
formatted_addons = []
|
|
195
|
+
for addon in addons:
|
|
196
|
+
# Apply repository filter
|
|
197
|
+
if repository and addon.get("repository") != repository:
|
|
198
|
+
continue
|
|
199
|
+
|
|
200
|
+
# Apply search query filter
|
|
201
|
+
if query:
|
|
202
|
+
query_lower = query.lower()
|
|
203
|
+
name = (addon.get("name") or "").lower()
|
|
204
|
+
description = (addon.get("description") or "").lower()
|
|
205
|
+
if query_lower not in name and query_lower not in description:
|
|
206
|
+
continue
|
|
207
|
+
|
|
208
|
+
addon_info = {
|
|
209
|
+
"name": addon.get("name"),
|
|
210
|
+
"slug": addon.get("slug"),
|
|
211
|
+
"description": addon.get("description"),
|
|
212
|
+
"version": addon.get("version"),
|
|
213
|
+
"available": addon.get("available", True),
|
|
214
|
+
"installed": addon.get("installed", False),
|
|
215
|
+
"repository": addon.get("repository"),
|
|
216
|
+
"url": addon.get("url"),
|
|
217
|
+
"icon": addon.get("icon"),
|
|
218
|
+
"logo": addon.get("logo"),
|
|
219
|
+
}
|
|
220
|
+
formatted_addons.append(addon_info)
|
|
221
|
+
|
|
222
|
+
# Count statistics
|
|
223
|
+
installed_count = sum(1 for a in formatted_addons if a.get("installed"))
|
|
224
|
+
|
|
225
|
+
return {
|
|
226
|
+
"success": True,
|
|
227
|
+
"repositories": formatted_repos,
|
|
228
|
+
"addons": formatted_addons,
|
|
229
|
+
"summary": {
|
|
230
|
+
"total_available": len(formatted_addons),
|
|
231
|
+
"installed": installed_count,
|
|
232
|
+
"not_installed": len(formatted_addons) - installed_count,
|
|
233
|
+
"repository_count": len(formatted_repos),
|
|
234
|
+
},
|
|
235
|
+
"filters_applied": {
|
|
236
|
+
"repository": repository,
|
|
237
|
+
"query": query,
|
|
238
|
+
},
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
except Exception as e:
|
|
242
|
+
logger.error(f"Error listing available add-ons: {e}")
|
|
243
|
+
return {
|
|
244
|
+
"success": False,
|
|
245
|
+
"error": f"Failed to list available add-ons: {str(e)}",
|
|
246
|
+
"suggestion": "Check Home Assistant connection and Supervisor availability",
|
|
247
|
+
}
|
|
248
|
+
finally:
|
|
249
|
+
if ws_client:
|
|
250
|
+
try:
|
|
251
|
+
await ws_client.disconnect()
|
|
252
|
+
except Exception:
|
|
253
|
+
pass
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def register_addon_tools(mcp: Any, client: HomeAssistantClient, **kwargs) -> None:
|
|
257
|
+
"""
|
|
258
|
+
Register add-on management tools with the MCP server.
|
|
259
|
+
|
|
260
|
+
Args:
|
|
261
|
+
mcp: FastMCP server instance
|
|
262
|
+
client: Home Assistant REST client
|
|
263
|
+
**kwargs: Additional arguments (ignored, for auto-discovery compatibility)
|
|
264
|
+
"""
|
|
265
|
+
|
|
266
|
+
@mcp.tool(annotations={"idempotentHint": True, "readOnlyHint": True, "tags": ["addon"], "title": "Get Add-ons"})
|
|
267
|
+
@log_tool_usage
|
|
268
|
+
async def ha_get_addon(
|
|
269
|
+
source: Annotated[
|
|
270
|
+
str | None,
|
|
271
|
+
Field(
|
|
272
|
+
description="Add-on source: 'installed' (default) for currently installed add-ons, "
|
|
273
|
+
"'available' for add-ons in the store that can be installed.",
|
|
274
|
+
default=None,
|
|
275
|
+
),
|
|
276
|
+
] = None,
|
|
277
|
+
include_stats: Annotated[
|
|
278
|
+
bool,
|
|
279
|
+
Field(
|
|
280
|
+
description="Include CPU/memory usage statistics (only for source='installed')",
|
|
281
|
+
default=False,
|
|
282
|
+
),
|
|
283
|
+
] = False,
|
|
284
|
+
repository: Annotated[
|
|
285
|
+
str | None,
|
|
286
|
+
Field(
|
|
287
|
+
description="Filter by repository slug, e.g., 'core', 'community' (only for source='available')",
|
|
288
|
+
default=None,
|
|
289
|
+
),
|
|
290
|
+
] = None,
|
|
291
|
+
query: Annotated[
|
|
292
|
+
str | None,
|
|
293
|
+
Field(
|
|
294
|
+
description="Search filter for add-on names/descriptions (only for source='available')",
|
|
295
|
+
default=None,
|
|
296
|
+
),
|
|
297
|
+
] = None,
|
|
298
|
+
) -> dict[str, Any]:
|
|
299
|
+
"""
|
|
300
|
+
Get Home Assistant add-ons - list installed or available from store.
|
|
301
|
+
|
|
302
|
+
This tool retrieves add-on information based on the source parameter:
|
|
303
|
+
- source='installed' (default): Lists currently installed add-ons
|
|
304
|
+
- source='available': Lists add-ons available in the add-on store
|
|
305
|
+
|
|
306
|
+
**Note:** This tool only works with Home Assistant OS or Supervised installations.
|
|
307
|
+
|
|
308
|
+
**INSTALLED ADD-ONS (source='installed'):**
|
|
309
|
+
Returns add-ons with version, state (started/stopped), and update availability.
|
|
310
|
+
- include_stats: Optionally include CPU/memory usage statistics
|
|
311
|
+
|
|
312
|
+
**AVAILABLE ADD-ONS (source='available'):**
|
|
313
|
+
Returns add-ons from official and custom repositories that can be installed.
|
|
314
|
+
- repository: Filter by repository slug (e.g., 'core', 'community')
|
|
315
|
+
- query: Search by name or description (case-insensitive)
|
|
316
|
+
|
|
317
|
+
**Example Usage:**
|
|
318
|
+
- List installed add-ons: ha_get_addon()
|
|
319
|
+
- List with resource usage: ha_get_addon(include_stats=True)
|
|
320
|
+
- List available add-ons: ha_get_addon(source="available")
|
|
321
|
+
- Search for MQTT: ha_get_addon(source="available", query="mqtt")
|
|
322
|
+
- List official add-ons: ha_get_addon(source="available", repository="core")
|
|
323
|
+
|
|
324
|
+
**Use Cases:**
|
|
325
|
+
- Check which add-ons are installed and running
|
|
326
|
+
- Monitor add-on health and resource usage
|
|
327
|
+
- Find add-ons with available updates
|
|
328
|
+
- Find add-ons to recommend for user's needs
|
|
329
|
+
- Check if a specific add-on is available
|
|
330
|
+
"""
|
|
331
|
+
# Default to installed if not specified
|
|
332
|
+
effective_source = (source or "installed").lower()
|
|
333
|
+
|
|
334
|
+
if effective_source == "available":
|
|
335
|
+
return await list_available_addons(client, repository, query)
|
|
336
|
+
elif effective_source == "installed":
|
|
337
|
+
return await list_addons(client, include_stats)
|
|
338
|
+
else:
|
|
339
|
+
return {
|
|
340
|
+
"success": False,
|
|
341
|
+
"error": f"Invalid source: {source}. Must be 'installed' or 'available'.",
|
|
342
|
+
"valid_sources": ["installed", "available"],
|
|
343
|
+
}
|