ha-mcp-dev 7.4.1.dev471__tar.gz → 7.4.1.dev473__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.4.1.dev471/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.4.1.dev473}/PKG-INFO +1 -1
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/pyproject.toml +1 -1
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/client/rest_client.py +10 -11
- ha_mcp_dev-7.4.1.dev473/src/ha_mcp/client/supervisor_client.py +88 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/settings_ui.py +5 -8
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/tools/tools_bug_report.py +4 -8
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/tools/tools_config_scenes.py +7 -10
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/utils/python_sandbox.py +11 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp_dev.egg-info/SOURCES.txt +1 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/LICENSE +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/MANIFEST.in +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/README.md +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/setup.cfg +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/__init__.py +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/__main__.py +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/_pypi_marker +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/_version.py +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/auth/__init__.py +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/auth/consent_form.py +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/auth/provider.py +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/client/__init__.py +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/client/websocket_client.py +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/client/websocket_listener.py +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/config.py +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/errors.py +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/py.typed +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/server.py +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/smoke_test.py +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/tools/__init__.py +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/tools/backup.py +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/tools/best_practice_checker.py +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/tools/device_control.py +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/tools/enhanced.py +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/tools/helpers.py +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/tools/reference_validator.py +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/tools/registry.py +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/tools/smart_search.py +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/tools/tools_addons.py +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/tools/tools_areas.py +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/tools/tools_blueprints.py +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/tools/tools_calendar.py +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/tools/tools_camera.py +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/tools/tools_categories.py +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/tools/tools_code.py +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/tools/tools_config_automations.py +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/tools/tools_config_entry_flow.py +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/tools/tools_energy.py +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/tools/tools_entities.py +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/tools/tools_filesystem.py +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/tools/tools_groups.py +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/tools/tools_hacs.py +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/tools/tools_history.py +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/tools/tools_integrations.py +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/tools/tools_labels.py +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/tools/tools_mcp_component.py +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/tools/tools_registry.py +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/tools/tools_resources.py +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/tools/tools_search.py +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/tools/tools_service.py +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/tools/tools_services.py +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/tools/tools_system.py +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/tools/tools_todo.py +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/tools/tools_traces.py +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/tools/tools_updates.py +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/tools/tools_utility.py +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/tools/tools_zones.py +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/tools/util_helpers.py +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/transforms/__init__.py +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/transforms/categorized_search.py +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/utils/__init__.py +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/utils/config_hash.py +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/utils/data_paths.py +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/utils/domain_handlers.py +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/utils/fuzzy_search.py +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/utils/operation_manager.py +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/utils/usage_logger.py +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/tests/__init__.py +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/tests/test_constants.py +0 -0
- {ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/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.4.1.
|
|
7
|
+
version = "7.4.1.dev473"
|
|
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"
|
|
@@ -13,6 +13,7 @@ import httpx
|
|
|
13
13
|
|
|
14
14
|
from .._version import get_supervisor_base_url, is_running_in_addon
|
|
15
15
|
from ..config import get_global_settings
|
|
16
|
+
from .supervisor_client import make_supervisor_httpx_client
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
def _is_ssl_error(exc: BaseException) -> bool:
|
|
@@ -555,23 +556,21 @@ class HomeAssistantClient:
|
|
|
555
556
|
"(addon-mode gate fired but SUPERVISOR_TOKEN env var not set)"
|
|
556
557
|
)
|
|
557
558
|
|
|
558
|
-
|
|
559
|
-
logger.debug(
|
|
559
|
+
relative_path = f"/{path}/logs"
|
|
560
|
+
logger.debug(
|
|
561
|
+
"Fetching %s%s via Supervisor direct",
|
|
562
|
+
get_supervisor_base_url(),
|
|
563
|
+
relative_path,
|
|
564
|
+
)
|
|
560
565
|
|
|
561
566
|
try:
|
|
562
|
-
async with
|
|
567
|
+
async with make_supervisor_httpx_client(
|
|
563
568
|
timeout=httpx.Timeout(self.timeout),
|
|
564
|
-
# `verify` is a no-op for plain http://supervisor, but kept
|
|
565
|
-
# for symmetry with the other two direct-Supervisor httpx
|
|
566
|
-
# clients (#1128 establishes the 3-site convention).
|
|
567
569
|
verify=self.verify_ssl,
|
|
568
570
|
) as client:
|
|
569
571
|
response = await client.get(
|
|
570
|
-
|
|
571
|
-
headers={
|
|
572
|
-
"Authorization": f"Bearer {token}",
|
|
573
|
-
"Accept": "text/plain",
|
|
574
|
-
},
|
|
572
|
+
relative_path,
|
|
573
|
+
headers={"Accept": "text/plain"},
|
|
575
574
|
)
|
|
576
575
|
except httpx.TimeoutException as e:
|
|
577
576
|
raise HomeAssistantConnectionError(
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""Shared factory for direct-Supervisor httpx clients.
|
|
2
|
+
|
|
3
|
+
Three call sites in the codebase talk directly to the Home Assistant
|
|
4
|
+
Supervisor REST API at ``http://supervisor`` rather than through
|
|
5
|
+
``HomeAssistantClient.httpx_client`` (which is bound to HA Core, not the
|
|
6
|
+
Supervisor — different base URL, different token, different role gate):
|
|
7
|
+
|
|
8
|
+
- :meth:`ha_mcp.client.rest_client.HomeAssistantClient._supervisor_logs_get`
|
|
9
|
+
— fetches addon and system-service logs
|
|
10
|
+
- :func:`ha_mcp.tools.tools_bug_report._fetch_addon_logs` — bundles ha-mcp's
|
|
11
|
+
own addon logs into a bug-report payload
|
|
12
|
+
- :func:`ha_mcp.settings_ui._restart_addon` — POSTs ``/addons/self/restart``
|
|
13
|
+
from the settings UI
|
|
14
|
+
|
|
15
|
+
All three share the same boilerplate (base URL, ``Authorization: Bearer
|
|
16
|
+
${SUPERVISOR_TOKEN}`` header), so this module supplies a single factory and
|
|
17
|
+
keeps the three sites consistent.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import os
|
|
23
|
+
import ssl
|
|
24
|
+
|
|
25
|
+
import httpx
|
|
26
|
+
|
|
27
|
+
from .._version import get_supervisor_base_url
|
|
28
|
+
|
|
29
|
+
__all__ = ["make_supervisor_httpx_client"]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def make_supervisor_httpx_client(
|
|
33
|
+
*,
|
|
34
|
+
timeout: float | httpx.Timeout,
|
|
35
|
+
verify: bool | str | ssl.SSLContext,
|
|
36
|
+
) -> httpx.AsyncClient:
|
|
37
|
+
"""Construct an ``httpx.AsyncClient`` pre-configured for the Supervisor REST API.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
timeout: Per-request timeout. Accepts either a plain ``float``
|
|
41
|
+
(seconds, applied to all phases) or a full :class:`httpx.Timeout`
|
|
42
|
+
for finer-grained control.
|
|
43
|
+
verify: TLS verify policy. A no-op for the default
|
|
44
|
+
``http://supervisor`` base URL (plain HTTP — no TLS to verify),
|
|
45
|
+
but kept as a parameter because :func:`get_supervisor_base_url`
|
|
46
|
+
honours ``SUPERVISOR_BASE_URL`` env-var overrides that may be
|
|
47
|
+
HTTPS in non-add-on test rigs. The full httpx ``verify`` surface
|
|
48
|
+
(``bool``, CA-bundle path, or :class:`ssl.SSLContext`) is
|
|
49
|
+
accepted and forwarded verbatim.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
A new :class:`httpx.AsyncClient` bound to the Supervisor base URL
|
|
53
|
+
with ``Authorization: Bearer ${SUPERVISOR_TOKEN}`` preset. Callers
|
|
54
|
+
pass relative paths (``/addons/self/logs``) to ``client.get/post``;
|
|
55
|
+
``base_url`` joins them onto the Supervisor host.
|
|
56
|
+
|
|
57
|
+
Raises:
|
|
58
|
+
RuntimeError: ``SUPERVISOR_TOKEN`` is unset or empty in the
|
|
59
|
+
environment. Each call site has its own absent-token policy
|
|
60
|
+
(a rich :class:`HomeAssistantAuthError`, a silent ``""``
|
|
61
|
+
return, or a 400 ``JSONResponse``) that does not share a
|
|
62
|
+
common shape, so the factory cannot translate. Detecting the
|
|
63
|
+
absence at construction time prevents a malformed
|
|
64
|
+
``Authorization: Bearer `` header from being read as a token
|
|
65
|
+
rejection by Supervisor, which would mask the missing-env-var
|
|
66
|
+
root cause.
|
|
67
|
+
|
|
68
|
+
Note:
|
|
69
|
+
``SUPERVISOR_TOKEN`` is read from env at construction time and
|
|
70
|
+
baked into the constructed client's ``Authorization`` header.
|
|
71
|
+
Reusing a single client across token rotations would not pick up
|
|
72
|
+
the new value — short-lived ``async with`` callers are unaffected,
|
|
73
|
+
but a future long-lived caller would need to discard and re-create.
|
|
74
|
+
"""
|
|
75
|
+
token = os.environ.get("SUPERVISOR_TOKEN", "")
|
|
76
|
+
if not token:
|
|
77
|
+
raise RuntimeError(
|
|
78
|
+
"SUPERVISOR_TOKEN is not set; "
|
|
79
|
+
"make_supervisor_httpx_client cannot construct an "
|
|
80
|
+
"authenticated client. Callers must verify the token is "
|
|
81
|
+
"present before invoking the factory."
|
|
82
|
+
)
|
|
83
|
+
return httpx.AsyncClient(
|
|
84
|
+
base_url=get_supervisor_base_url(),
|
|
85
|
+
timeout=timeout,
|
|
86
|
+
verify=verify,
|
|
87
|
+
headers={"Authorization": f"Bearer {token}"},
|
|
88
|
+
)
|
|
@@ -19,7 +19,8 @@ import httpx
|
|
|
19
19
|
from starlette.requests import Request
|
|
20
20
|
from starlette.responses import HTMLResponse, JSONResponse
|
|
21
21
|
|
|
22
|
-
from ._version import
|
|
22
|
+
from ._version import is_running_in_addon
|
|
23
|
+
from .client.supervisor_client import make_supervisor_httpx_client
|
|
23
24
|
from .errors import ErrorCode, create_error_response
|
|
24
25
|
from .transforms import DEFAULT_PINNED_TOOLS
|
|
25
26
|
from .utils.data_paths import get_data_dir
|
|
@@ -893,8 +894,7 @@ def register_settings_routes(
|
|
|
893
894
|
)
|
|
894
895
|
|
|
895
896
|
async def _restart_addon(_: Request) -> JSONResponse:
|
|
896
|
-
|
|
897
|
-
if not token:
|
|
897
|
+
if not os.environ.get("SUPERVISOR_TOKEN"):
|
|
898
898
|
return JSONResponse(
|
|
899
899
|
create_error_response(
|
|
900
900
|
ErrorCode.CONFIG_VALIDATION_FAILED,
|
|
@@ -906,13 +906,10 @@ def register_settings_routes(
|
|
|
906
906
|
# Short timeout — the supervisor kills our process during restart so
|
|
907
907
|
# the connection will drop. A connection drop is actually success.
|
|
908
908
|
try:
|
|
909
|
-
async with
|
|
909
|
+
async with make_supervisor_httpx_client(
|
|
910
910
|
timeout=5.0, verify=server.settings.verify_ssl
|
|
911
911
|
) as client:
|
|
912
|
-
resp = await client.post(
|
|
913
|
-
f"{get_supervisor_base_url()}/addons/self/restart",
|
|
914
|
-
headers={"Authorization": f"Bearer {token}"},
|
|
915
|
-
)
|
|
912
|
+
resp = await client.post("/addons/self/restart")
|
|
916
913
|
except (httpx.ReadError, httpx.RemoteProtocolError):
|
|
917
914
|
# Connection dropped mid-request — restart is happening.
|
|
918
915
|
# `ConnectError` is deliberately NOT in this tuple: it fires
|
|
@@ -20,7 +20,7 @@ from pydantic import Field
|
|
|
20
20
|
|
|
21
21
|
from ha_mcp import __version__
|
|
22
22
|
|
|
23
|
-
from ..
|
|
23
|
+
from ..client.supervisor_client import make_supervisor_httpx_client
|
|
24
24
|
from ..config import Settings, get_global_settings
|
|
25
25
|
from ..utils.usage_logger import (
|
|
26
26
|
AVG_LOG_ENTRIES_PER_TOOL,
|
|
@@ -349,18 +349,14 @@ async def _fetch_addon_logs() -> str:
|
|
|
349
349
|
"""
|
|
350
350
|
# Redundant with the caller's `install_method == "addon"` gate, but kept
|
|
351
351
|
# as a defensive guard for any direct callers added later.
|
|
352
|
-
|
|
353
|
-
if not token:
|
|
352
|
+
if not os.environ.get("SUPERVISOR_TOKEN"):
|
|
354
353
|
return ""
|
|
355
354
|
|
|
356
355
|
try:
|
|
357
|
-
async with
|
|
356
|
+
async with make_supervisor_httpx_client(
|
|
358
357
|
timeout=10.0, verify=get_global_settings().verify_ssl
|
|
359
358
|
) as http_client:
|
|
360
|
-
resp = await http_client.get(
|
|
361
|
-
f"{get_supervisor_base_url()}/addons/self/logs",
|
|
362
|
-
headers={"Authorization": f"Bearer {token}"},
|
|
363
|
-
)
|
|
359
|
+
resp = await http_client.get("/addons/self/logs")
|
|
364
360
|
if resp.status_code != 200:
|
|
365
361
|
logger.info("Addon log fetch returned HTTP %s", resp.status_code)
|
|
366
362
|
return ""
|
|
@@ -23,6 +23,7 @@ from ..errors import ErrorCode, create_error_response
|
|
|
23
23
|
from ..utils.config_hash import compute_config_hash
|
|
24
24
|
from ..utils.python_sandbox import (
|
|
25
25
|
PythonSandboxError,
|
|
26
|
+
format_sandbox_error,
|
|
26
27
|
get_security_documentation,
|
|
27
28
|
safe_execute,
|
|
28
29
|
)
|
|
@@ -572,16 +573,12 @@ class ConfigSceneTools:
|
|
|
572
573
|
try:
|
|
573
574
|
transformed_config = safe_execute(python_transform, actual_config)
|
|
574
575
|
except PythonSandboxError as e:
|
|
576
|
+
message, suggestions = format_sandbox_error(e, python_transform)
|
|
575
577
|
raise_tool_error(
|
|
576
578
|
create_error_response(
|
|
577
579
|
ErrorCode.VALIDATION_FAILED,
|
|
578
|
-
|
|
579
|
-
suggestions=
|
|
580
|
-
"Check expression syntax",
|
|
581
|
-
"Ensure only allowed operations are used",
|
|
582
|
-
"See tool description for allowed operations",
|
|
583
|
-
f"Expression: {python_transform[:100]}{'...' if len(python_transform) > 100 else ''}",
|
|
584
|
-
],
|
|
580
|
+
message,
|
|
581
|
+
suggestions=suggestions,
|
|
585
582
|
context={
|
|
586
583
|
"action": "python_transform",
|
|
587
584
|
"scene_id": resolved_id,
|
|
@@ -656,9 +653,9 @@ class ConfigSceneTools:
|
|
|
656
653
|
# is the in-place equivalent of ``del`` — normalise both
|
|
657
654
|
# by checking against ``(None, resolved_id)``, so only an
|
|
658
655
|
# explicit non-None mismatch triggers the reject.
|
|
659
|
-
if (
|
|
660
|
-
|
|
661
|
-
|
|
656
|
+
if "id" in transformed_config and transformed_config["id"] not in (
|
|
657
|
+
None,
|
|
658
|
+
resolved_id,
|
|
662
659
|
):
|
|
663
660
|
raise_tool_error(
|
|
664
661
|
create_error_response(
|
|
@@ -472,6 +472,17 @@ def format_sandbox_error(
|
|
|
472
472
|
"See tool description for allowed operations",
|
|
473
473
|
f"Expression: {preview}",
|
|
474
474
|
]
|
|
475
|
+
# Agents commonly emit ``\"`` (intending a JSON-style quote escape)
|
|
476
|
+
# inside transforms. Outside Python string literals this is a
|
|
477
|
+
# line-continuation token followed by an unexpected character.
|
|
478
|
+
# Pinpoint the actual mistake so the agent fixes it on the first
|
|
479
|
+
# retry instead of guessing at "syntax".
|
|
480
|
+
if "line continuation character" in str(error):
|
|
481
|
+
suggestions.insert(
|
|
482
|
+
0,
|
|
483
|
+
'Remove the leading backslash before quotes — write "foo" '
|
|
484
|
+
'not \\"foo\\" in the Python source.',
|
|
485
|
+
)
|
|
475
486
|
if variable_name != "config":
|
|
476
487
|
suggestions = [
|
|
477
488
|
f"Operate on the `{variable_name}` variable (in-place or reassign)",
|
|
@@ -17,6 +17,7 @@ src/ha_mcp/auth/consent_form.py
|
|
|
17
17
|
src/ha_mcp/auth/provider.py
|
|
18
18
|
src/ha_mcp/client/__init__.py
|
|
19
19
|
src/ha_mcp/client/rest_client.py
|
|
20
|
+
src/ha_mcp/client/supervisor_client.py
|
|
20
21
|
src/ha_mcp/client/websocket_client.py
|
|
21
22
|
src/ha_mcp/client/websocket_listener.py
|
|
22
23
|
src/ha_mcp/resources/skills-vendor/AGENTS.md
|
|
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
|
|
File without changes
|
{ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/resources/skills-vendor/AGENTS.md
RENAMED
|
File without changes
|
{ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/resources/skills-vendor/CLAUDE.md
RENAMED
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/resources/skills-vendor/LICENSE
RENAMED
|
File without changes
|
{ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/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.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/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
|
|
File without changes
|
{ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/tools/tools_config_automations.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/tools/tools_config_dashboards.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/tools/tools_config_entry_flow.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/tools/tools_config_helpers.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/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
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/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.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/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
|
{ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp/utils/kill_signal_diagnostics.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/src/ha_mcp_dev.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{ha_mcp_dev-7.4.1.dev471 → ha_mcp_dev-7.4.1.dev473}/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
|