ha-mcp-dev 7.3.0.dev379__tar.gz → 7.3.0.dev380__tar.gz
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_dev-7.3.0.dev379/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.3.0.dev380}/PKG-INFO +1 -1
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/pyproject.toml +1 -1
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/client/rest_client.py +65 -23
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/tools/tools_utility.py +46 -47
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/LICENSE +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/MANIFEST.in +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/README.md +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/setup.cfg +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/__init__.py +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/__main__.py +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/_pypi_marker +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/auth/__init__.py +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/auth/consent_form.py +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/auth/provider.py +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/client/__init__.py +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/client/websocket_client.py +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/client/websocket_listener.py +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/config.py +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/errors.py +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/py.typed +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/server.py +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/smoke_test.py +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/tools/__init__.py +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/tools/backup.py +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/tools/best_practice_checker.py +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/tools/device_control.py +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/tools/enhanced.py +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/tools/helpers.py +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/tools/registry.py +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/tools/smart_search.py +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/tools/tools_addons.py +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/tools/tools_areas.py +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/tools/tools_blueprints.py +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/tools/tools_bug_report.py +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/tools/tools_calendar.py +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/tools/tools_camera.py +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/tools/tools_categories.py +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/tools/tools_config_automations.py +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/tools/tools_config_entry_flow.py +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/tools/tools_entities.py +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/tools/tools_filesystem.py +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/tools/tools_groups.py +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/tools/tools_hacs.py +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/tools/tools_history.py +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/tools/tools_integrations.py +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/tools/tools_labels.py +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/tools/tools_mcp_component.py +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/tools/tools_registry.py +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/tools/tools_resources.py +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/tools/tools_search.py +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/tools/tools_service.py +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/tools/tools_services.py +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/tools/tools_system.py +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/tools/tools_todo.py +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/tools/tools_traces.py +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/tools/tools_updates.py +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/tools/tools_zones.py +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/tools/util_helpers.py +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/transforms/__init__.py +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/transforms/categorized_search.py +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/utils/__init__.py +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/utils/config_hash.py +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/utils/domain_handlers.py +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/utils/fuzzy_search.py +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/utils/operation_manager.py +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/utils/python_sandbox.py +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/utils/usage_logger.py +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/tests/__init__.py +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/tests/test_constants.py +0 -0
- {ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/tests/test_env_manager.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "ha-mcp-dev"
|
|
7
|
-
version = "7.3.0.
|
|
7
|
+
version = "7.3.0.dev380"
|
|
8
8
|
description = "Home Assistant MCP Server - Complete control of Home Assistant through MCP"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.13,<3.14"
|
|
@@ -97,50 +97,44 @@ class HomeAssistantClient:
|
|
|
97
97
|
await self.httpx_client.aclose()
|
|
98
98
|
logger.debug("Closed Home Assistant client")
|
|
99
99
|
|
|
100
|
-
async def
|
|
101
|
-
|
|
102
|
-
|
|
100
|
+
async def _raw_request(
|
|
101
|
+
self, method: str, endpoint: str, **kwargs: Any
|
|
102
|
+
) -> httpx.Response:
|
|
103
|
+
"""Authenticated request that returns the raw httpx.Response.
|
|
103
104
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
**kwargs: Additional arguments for httpx request
|
|
108
|
-
|
|
109
|
-
Returns:
|
|
110
|
-
Response data as dictionary
|
|
105
|
+
Handles auth, HTTP 4xx/5xx, and transport errors in one place.
|
|
106
|
+
Callers parse the body themselves (JSON via `_request`, text via
|
|
107
|
+
`get_addon_logs`, etc.).
|
|
111
108
|
|
|
112
109
|
Raises:
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
110
|
+
HomeAssistantAuthError: 401 response.
|
|
111
|
+
HomeAssistantAPIError: Non-2xx response (with status_code and
|
|
112
|
+
response_data set from JSON body when possible).
|
|
113
|
+
HomeAssistantConnectionError: Network, timeout, or transport error.
|
|
116
114
|
"""
|
|
117
115
|
try:
|
|
118
116
|
response = await self.httpx_client.request(method, endpoint, **kwargs)
|
|
119
117
|
|
|
120
|
-
# Handle authentication errors
|
|
121
118
|
if response.status_code == 401:
|
|
122
119
|
raise HomeAssistantAuthError("Invalid authentication token")
|
|
123
120
|
|
|
124
|
-
# Handle other HTTP errors
|
|
125
121
|
if response.status_code >= 400:
|
|
126
122
|
try:
|
|
127
123
|
error_data = response.json()
|
|
128
124
|
except Exception:
|
|
129
125
|
error_data = {"message": response.text}
|
|
130
126
|
|
|
127
|
+
message = error_data.get("message")
|
|
128
|
+
if not message or not message.strip():
|
|
129
|
+
message = response.reason_phrase or "<empty body>"
|
|
130
|
+
|
|
131
131
|
raise HomeAssistantAPIError(
|
|
132
|
-
f"API error: {response.status_code} - {
|
|
132
|
+
f"API error: {response.status_code} - {message}",
|
|
133
133
|
status_code=response.status_code,
|
|
134
134
|
response_data=error_data,
|
|
135
135
|
)
|
|
136
136
|
|
|
137
|
-
|
|
138
|
-
try:
|
|
139
|
-
result: dict[str, Any] = response.json()
|
|
140
|
-
return result
|
|
141
|
-
except json.JSONDecodeError:
|
|
142
|
-
# Some endpoints return empty responses
|
|
143
|
-
return {}
|
|
137
|
+
return response
|
|
144
138
|
|
|
145
139
|
except httpx.ConnectError as e:
|
|
146
140
|
raise HomeAssistantConnectionError(
|
|
@@ -151,6 +145,31 @@ class HomeAssistantClient:
|
|
|
151
145
|
except httpx.HTTPError as e:
|
|
152
146
|
raise HomeAssistantConnectionError(f"HTTP error: {e}") from e
|
|
153
147
|
|
|
148
|
+
async def _request(self, method: str, endpoint: str, **kwargs: Any) -> dict[str, Any]:
|
|
149
|
+
"""
|
|
150
|
+
Make authenticated request to Home Assistant API and parse JSON body.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
method: HTTP method (GET, POST, etc.)
|
|
154
|
+
endpoint: API endpoint (without /api prefix)
|
|
155
|
+
**kwargs: Additional arguments for httpx request
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
Response data as dictionary
|
|
159
|
+
|
|
160
|
+
Raises:
|
|
161
|
+
HomeAssistantConnectionError: Connection failed
|
|
162
|
+
HomeAssistantAuthError: Authentication failed
|
|
163
|
+
HomeAssistantAPIError: API error
|
|
164
|
+
"""
|
|
165
|
+
response = await self._raw_request(method, endpoint, **kwargs)
|
|
166
|
+
try:
|
|
167
|
+
result: dict[str, Any] = response.json()
|
|
168
|
+
return result
|
|
169
|
+
except json.JSONDecodeError:
|
|
170
|
+
# Some endpoints return empty responses
|
|
171
|
+
return {}
|
|
172
|
+
|
|
154
173
|
async def get_config(self) -> dict[str, Any]:
|
|
155
174
|
"""Get Home Assistant configuration."""
|
|
156
175
|
logger.debug("Fetching Home Assistant configuration")
|
|
@@ -367,6 +386,29 @@ class HomeAssistantClient:
|
|
|
367
386
|
response = await self._request("GET", "/error_log")
|
|
368
387
|
return response if isinstance(response, str) else str(response)
|
|
369
388
|
|
|
389
|
+
async def get_addon_logs(self, slug: str) -> str:
|
|
390
|
+
"""Fetch an add-on's container logs via HA Core's Supervisor REST proxy.
|
|
391
|
+
|
|
392
|
+
Uses `/api/hassio/addons/{slug}/logs`, which HA Core proxies to
|
|
393
|
+
Supervisor and returns as `text/plain`. This avoids the
|
|
394
|
+
`supervisor/api` websocket path that tries to JSON-decode the text
|
|
395
|
+
body and always fails (see #950).
|
|
396
|
+
|
|
397
|
+
Raises:
|
|
398
|
+
HomeAssistantAuthError: 401 from HA Core.
|
|
399
|
+
HomeAssistantAPIError: Non-2xx response (e.g. 404 unknown slug,
|
|
400
|
+
400 addon not installed). `status_code` is set so callers
|
|
401
|
+
can map to specific suggestions.
|
|
402
|
+
HomeAssistantConnectionError: Network, timeout, or transport error.
|
|
403
|
+
"""
|
|
404
|
+
logger.debug(f"Fetching addon logs for slug={slug}")
|
|
405
|
+
response = await self._raw_request(
|
|
406
|
+
"GET",
|
|
407
|
+
f"/hassio/addons/{slug}/logs",
|
|
408
|
+
headers={"Accept": "text/plain"},
|
|
409
|
+
)
|
|
410
|
+
return response.text
|
|
411
|
+
|
|
370
412
|
async def test_connection(self) -> tuple[bool, str | None]:
|
|
371
413
|
"""
|
|
372
414
|
Test connection to Home Assistant.
|
|
@@ -182,7 +182,7 @@ def register_utility_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
|
|
|
182
182
|
"The 'slug' parameter is required for source='supervisor'",
|
|
183
183
|
suggestions=[
|
|
184
184
|
"Provide the add-on slug, e.g. slug='core_mosquitto'",
|
|
185
|
-
"Use
|
|
185
|
+
"Use ha_get_addon() to list installed add-on slugs",
|
|
186
186
|
],
|
|
187
187
|
)
|
|
188
188
|
)
|
|
@@ -515,53 +515,17 @@ def register_utility_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
|
|
|
515
515
|
limit: int | str | None = None,
|
|
516
516
|
search: str | None = None,
|
|
517
517
|
) -> dict[str, Any]:
|
|
518
|
-
"""Fetch add-on container logs via
|
|
518
|
+
"""Fetch add-on container logs via HA Core's Supervisor REST proxy.
|
|
519
|
+
|
|
520
|
+
Routes through `/api/hassio/addons/{slug}/logs` (returned as
|
|
521
|
+
text/plain) instead of the `supervisor/api` websocket path, which
|
|
522
|
+
always fails because HA Core's proxy tries to JSON-decode the
|
|
523
|
+
text body. See #950.
|
|
524
|
+
"""
|
|
519
525
|
effective_limit = _coerce_limit(limit, default=DEFAULT_LOG_LIMIT, suggestion_example="100")
|
|
520
526
|
|
|
521
527
|
try:
|
|
522
|
-
|
|
523
|
-
{
|
|
524
|
-
"type": "supervisor/api",
|
|
525
|
-
"endpoint": f"/addons/{slug}/logs",
|
|
526
|
-
"method": "get",
|
|
527
|
-
}
|
|
528
|
-
)
|
|
529
|
-
|
|
530
|
-
if not result.get("success"):
|
|
531
|
-
error_msg = str(result.get("error", ""))
|
|
532
|
-
suggestions = [
|
|
533
|
-
f"Verify add-on slug '{slug}' is correct",
|
|
534
|
-
"Use ha_list_addons() to find available add-on slugs",
|
|
535
|
-
]
|
|
536
|
-
if "not_found" in error_msg.lower() or "unknown" in error_msg.lower():
|
|
537
|
-
suggestions.insert(
|
|
538
|
-
0,
|
|
539
|
-
"Supervisor API not available - requires HA OS or Supervised install",
|
|
540
|
-
)
|
|
541
|
-
raise_tool_error(
|
|
542
|
-
create_error_response(
|
|
543
|
-
ErrorCode.SERVICE_CALL_FAILED,
|
|
544
|
-
result.get(
|
|
545
|
-
"error", f"Failed to retrieve logs for add-on '{slug}'"
|
|
546
|
-
),
|
|
547
|
-
context={"slug": slug},
|
|
548
|
-
suggestions=suggestions,
|
|
549
|
-
)
|
|
550
|
-
)
|
|
551
|
-
|
|
552
|
-
# Result may be a string (log text) or dict with result key
|
|
553
|
-
log_text = result.get("result", "")
|
|
554
|
-
if isinstance(log_text, dict):
|
|
555
|
-
if "data" in log_text:
|
|
556
|
-
log_text = log_text["data"]
|
|
557
|
-
else:
|
|
558
|
-
logger.warning(
|
|
559
|
-
"Supervisor log for '%s' returned unexpected dict structure",
|
|
560
|
-
slug,
|
|
561
|
-
)
|
|
562
|
-
log_text = ""
|
|
563
|
-
if not isinstance(log_text, str):
|
|
564
|
-
log_text = str(log_text)
|
|
528
|
+
log_text = await client.get_addon_logs(slug)
|
|
565
529
|
|
|
566
530
|
lines = log_text.splitlines() if log_text else []
|
|
567
531
|
|
|
@@ -594,9 +558,43 @@ def register_utility_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
|
|
|
594
558
|
|
|
595
559
|
except ToolError:
|
|
596
560
|
raise
|
|
561
|
+
except HomeAssistantAPIError as e:
|
|
562
|
+
status = getattr(e, "status_code", None)
|
|
563
|
+
if status == 400:
|
|
564
|
+
# Supervisor-side rejection — not caller validation. The default
|
|
565
|
+
# `exception_to_structured_error` path would map 400 →
|
|
566
|
+
# VALIDATION_INVALID_PARAMETER, which reads as "caller passed
|
|
567
|
+
# bad input"; a downstream proxy rejection is better modelled
|
|
568
|
+
# as SERVICE_CALL_FAILED.
|
|
569
|
+
raise_tool_error(
|
|
570
|
+
create_error_response(
|
|
571
|
+
ErrorCode.SERVICE_CALL_FAILED,
|
|
572
|
+
str(e),
|
|
573
|
+
context={"source": "supervisor", "slug": slug},
|
|
574
|
+
suggestions=[
|
|
575
|
+
f"Supervisor rejected the request for '{slug}' — "
|
|
576
|
+
"verify slug format or that the add-on is installed "
|
|
577
|
+
"and running",
|
|
578
|
+
"Use ha_get_addon() to list installed add-on slugs",
|
|
579
|
+
"Ensure Supervisor is available (HA OS or Supervised install)",
|
|
580
|
+
],
|
|
581
|
+
)
|
|
582
|
+
)
|
|
583
|
+
if status == 404:
|
|
584
|
+
first_suggestion = f"Add-on '{slug}' not found or not installed"
|
|
585
|
+
else:
|
|
586
|
+
first_suggestion = f"Verify add-on slug '{slug}' is correct"
|
|
587
|
+
exception_to_structured_error(
|
|
588
|
+
e,
|
|
589
|
+
context={"source": "supervisor", "slug": slug},
|
|
590
|
+
suggestions=[
|
|
591
|
+
first_suggestion,
|
|
592
|
+
"Use ha_get_addon() to list installed add-on slugs",
|
|
593
|
+
"Ensure Supervisor is available (HA OS or Supervised install)",
|
|
594
|
+
],
|
|
595
|
+
)
|
|
597
596
|
except (
|
|
598
597
|
HomeAssistantConnectionError,
|
|
599
|
-
HomeAssistantAPIError,
|
|
600
598
|
TimeoutError,
|
|
601
599
|
OSError,
|
|
602
600
|
) as e:
|
|
@@ -604,8 +602,9 @@ def register_utility_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
|
|
|
604
602
|
e,
|
|
605
603
|
context={"source": "supervisor", "slug": slug},
|
|
606
604
|
suggestions=[
|
|
605
|
+
"Check Home Assistant connection",
|
|
607
606
|
f"Verify add-on slug '{slug}' is correct",
|
|
608
|
-
"Use
|
|
607
|
+
"Use ha_get_addon() to list installed add-on slugs",
|
|
609
608
|
"Ensure Supervisor is available (HA OS or Supervised install)",
|
|
610
609
|
],
|
|
611
610
|
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/resources/skills-vendor/AGENTS.md
RENAMED
|
File without changes
|
{ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/resources/skills-vendor/CLAUDE.md
RENAMED
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/resources/skills-vendor/LICENSE
RENAMED
|
File without changes
|
{ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/resources/skills-vendor/README.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/tools/best_practice_checker.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/tools/tools_config_automations.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/tools/tools_config_dashboards.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/tools/tools_config_entry_flow.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/tools/tools_config_helpers.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/tools/tools_config_scripts.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/tools/tools_voice_assistant.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp/transforms/categorized_search.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp_dev.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{ha_mcp_dev-7.3.0.dev379 → ha_mcp_dev-7.3.0.dev380}/src/ha_mcp_dev.egg-info/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|