ha-mcp-dev 7.2.0.dev370__tar.gz → 7.3.0.dev372__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.2.0.dev370/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.3.0.dev372}/PKG-INFO +2 -2
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/README.md +1 -1
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/pyproject.toml +1 -1
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/__init__.py +1 -1
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/tools_addons.py +254 -27
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372/src/ha_mcp_dev.egg-info}/PKG-INFO +2 -2
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/LICENSE +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/MANIFEST.in +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/setup.cfg +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/__main__.py +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/_pypi_marker +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/auth/__init__.py +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/auth/consent_form.py +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/auth/provider.py +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/client/__init__.py +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/client/rest_client.py +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/client/websocket_client.py +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/client/websocket_listener.py +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/config.py +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/errors.py +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/py.typed +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/server.py +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/smoke_test.py +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/__init__.py +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/backup.py +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/best_practice_checker.py +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/device_control.py +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/enhanced.py +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/helpers.py +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/registry.py +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/smart_search.py +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/tools_areas.py +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/tools_blueprints.py +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/tools_bug_report.py +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/tools_calendar.py +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/tools_camera.py +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/tools_categories.py +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/tools_config_automations.py +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/tools_config_entry_flow.py +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/tools_entities.py +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/tools_filesystem.py +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/tools_groups.py +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/tools_hacs.py +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/tools_history.py +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/tools_integrations.py +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/tools_labels.py +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/tools_mcp_component.py +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/tools_registry.py +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/tools_resources.py +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/tools_search.py +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/tools_service.py +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/tools_services.py +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/tools_system.py +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/tools_todo.py +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/tools_traces.py +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/tools_updates.py +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/tools_utility.py +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/tools_zones.py +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/util_helpers.py +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/transforms/__init__.py +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/transforms/categorized_search.py +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/utils/__init__.py +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/utils/config_hash.py +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/utils/domain_handlers.py +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/utils/fuzzy_search.py +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/utils/operation_manager.py +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/utils/python_sandbox.py +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/utils/usage_logger.py +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/tests/__init__.py +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/tests/test_constants.py +0 -0
- {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/tests/test_env_manager.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ha-mcp-dev
|
|
3
|
-
Version: 7.
|
|
3
|
+
Version: 7.3.0.dev372
|
|
4
4
|
Summary: Home Assistant MCP Server - Complete control of Home Assistant through MCP
|
|
5
5
|
Author-email: Julien <github@qc-h.net>
|
|
6
6
|
License: MIT
|
|
@@ -184,7 +184,7 @@ Spend less time configuring, more time enjoying your smart home.
|
|
|
184
184
|
|
|
185
185
|
| Category | Tools |
|
|
186
186
|
|----------|-------|
|
|
187
|
-
| **Add-ons** | `
|
|
187
|
+
| **Add-ons** | `ha_get_addon`, `ha_manage_addon` |
|
|
188
188
|
| **Areas & Floors** | `ha_config_list_areas`, `ha_config_list_floors`, `ha_config_remove_area`, `ha_config_remove_floor`, `ha_config_set_area`, `ha_config_set_floor` |
|
|
189
189
|
| **Automations** | `ha_config_get_automation`, `ha_config_remove_automation`, `ha_config_set_automation` |
|
|
190
190
|
| **Blueprints** | `ha_get_blueprint`, `ha_import_blueprint` |
|
|
@@ -155,7 +155,7 @@ Spend less time configuring, more time enjoying your smart home.
|
|
|
155
155
|
|
|
156
156
|
| Category | Tools |
|
|
157
157
|
|----------|-------|
|
|
158
|
-
| **Add-ons** | `
|
|
158
|
+
| **Add-ons** | `ha_get_addon`, `ha_manage_addon` |
|
|
159
159
|
| **Areas & Floors** | `ha_config_list_areas`, `ha_config_list_floors`, `ha_config_remove_area`, `ha_config_remove_floor`, `ha_config_set_area`, `ha_config_set_floor` |
|
|
160
160
|
| **Automations** | `ha_config_get_automation`, `ha_config_remove_automation`, `ha_config_set_automation` |
|
|
161
161
|
| **Blueprints** | `ha_get_blueprint`, `ha_import_blueprint` |
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "ha-mcp-dev"
|
|
7
|
-
version = "7.
|
|
7
|
+
version = "7.3.0.dev372"
|
|
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"
|
|
@@ -47,6 +47,22 @@ _MAX_WS_MESSAGES = 1000
|
|
|
47
47
|
_ANSI_ESCAPE_RE = re.compile(r"\x1b\[[0-9;]*[a-zA-Z]")
|
|
48
48
|
|
|
49
49
|
|
|
50
|
+
def _merge_options(base: dict, override: dict) -> dict:
|
|
51
|
+
"""Merge caller options into current options with one-level deep merge.
|
|
52
|
+
|
|
53
|
+
Top-level scalar values are replaced. Top-level dict values are merged
|
|
54
|
+
one level deep so callers can update a single nested field (e.g.
|
|
55
|
+
``{"ssh": {"sftp": True}}``) without losing sibling fields.
|
|
56
|
+
"""
|
|
57
|
+
merged = dict(base)
|
|
58
|
+
for key, value in override.items():
|
|
59
|
+
if isinstance(value, dict) and isinstance(merged.get(key), dict):
|
|
60
|
+
merged[key] = {**merged[key], **value}
|
|
61
|
+
else:
|
|
62
|
+
merged[key] = value
|
|
63
|
+
return merged
|
|
64
|
+
|
|
65
|
+
|
|
50
66
|
async def _supervisor_api_call(
|
|
51
67
|
client: HomeAssistantClient,
|
|
52
68
|
endpoint: str,
|
|
@@ -136,7 +152,7 @@ async def get_addon_info(client: HomeAssistantClient, slug: str) -> dict[str, An
|
|
|
136
152
|
"""
|
|
137
153
|
response = await _supervisor_api_call(client, f"/addons/{slug}/info")
|
|
138
154
|
if not response.get("success"):
|
|
139
|
-
return response
|
|
155
|
+
return response # TODO(tech-debt): should raise ToolError per AGENTS.md Pattern B
|
|
140
156
|
return {"success": True, "addon": response["result"]}
|
|
141
157
|
|
|
142
158
|
|
|
@@ -154,7 +170,7 @@ async def list_addons(
|
|
|
154
170
|
"""
|
|
155
171
|
response = await _supervisor_api_call(client, "/addons")
|
|
156
172
|
if not response.get("success"):
|
|
157
|
-
return response
|
|
173
|
+
return response # TODO(tech-debt): should raise ToolError per AGENTS.md Pattern B
|
|
158
174
|
|
|
159
175
|
data = response["result"]
|
|
160
176
|
addons = data.get("addons", [])
|
|
@@ -822,7 +838,7 @@ async def _call_addon_api(
|
|
|
822
838
|
"This add-on is blocking direct connections (likely Nginx IP restriction). "
|
|
823
839
|
"Try using the 'port' parameter to connect to the add-on's direct access port "
|
|
824
840
|
"(see addon_config.ports above) with 'leave_front_door_open' enabled. "
|
|
825
|
-
"Example:
|
|
841
|
+
"Example: ha_manage_addon(slug='...', path='...', port=<direct_port>). "
|
|
826
842
|
"The user may need to change add-on settings in the HA UI and restart the add-on."
|
|
827
843
|
)
|
|
828
844
|
|
|
@@ -898,7 +914,7 @@ def register_addon_tools(mcp: Any, client: HomeAssistantClient, **kwargs: Any) -
|
|
|
898
914
|
|
|
899
915
|
**SINGLE ADD-ON (slug provided):**
|
|
900
916
|
Returns comprehensive details including ingress entry, ports, options, and state.
|
|
901
|
-
Useful for discovering what APIs an add-on exposes before calling
|
|
917
|
+
Useful for discovering what APIs an add-on exposes before calling ha_manage_addon.
|
|
902
918
|
|
|
903
919
|
**INSTALLED ADD-ONS (source='installed'):**
|
|
904
920
|
Returns add-ons with version, state (started/stopped), and update availability.
|
|
@@ -949,11 +965,11 @@ def register_addon_tools(mcp: Any, client: HomeAssistantClient, **kwargs: Any) -
|
|
|
949
965
|
"destructiveHint": True,
|
|
950
966
|
"idempotentHint": False,
|
|
951
967
|
"readOnlyHint": False,
|
|
952
|
-
"title": "
|
|
968
|
+
"title": "Manage Add-on",
|
|
953
969
|
},
|
|
954
970
|
)
|
|
955
971
|
@log_tool_usage
|
|
956
|
-
async def
|
|
972
|
+
async def ha_manage_addon(
|
|
957
973
|
slug: Annotated[
|
|
958
974
|
str,
|
|
959
975
|
Field(
|
|
@@ -962,36 +978,39 @@ def register_addon_tools(mcp: Any, client: HomeAssistantClient, **kwargs: Any) -
|
|
|
962
978
|
),
|
|
963
979
|
],
|
|
964
980
|
path: Annotated[
|
|
965
|
-
str,
|
|
981
|
+
str | None,
|
|
966
982
|
Field(
|
|
967
|
-
description="API path relative to the add-on root
|
|
983
|
+
description="Proxy mode: API path relative to the add-on root "
|
|
984
|
+
"(e.g., '/flows', '/api/events', '/api/stats'). "
|
|
985
|
+
"Required for proxy mode; mutually exclusive with config parameters.",
|
|
986
|
+
default=None,
|
|
968
987
|
),
|
|
969
|
-
],
|
|
988
|
+
] = None,
|
|
970
989
|
method: Annotated[
|
|
971
990
|
str,
|
|
972
991
|
Field(
|
|
973
|
-
description="HTTP method: GET, POST, PUT, DELETE, PATCH. Defaults to GET.",
|
|
992
|
+
description="Proxy mode only. HTTP method: GET, POST, PUT, DELETE, PATCH. Defaults to GET.",
|
|
974
993
|
default="GET",
|
|
975
994
|
),
|
|
976
995
|
] = "GET",
|
|
977
996
|
body: Annotated[
|
|
978
997
|
dict[str, Any] | str | None,
|
|
979
998
|
Field(
|
|
980
|
-
description="Request body for POST/PUT/PATCH. Pass a JSON object or JSON string.",
|
|
999
|
+
description="Proxy mode only. Request body for POST/PUT/PATCH. Pass a JSON object or JSON string.",
|
|
981
1000
|
default=None,
|
|
982
1001
|
),
|
|
983
1002
|
] = None,
|
|
984
1003
|
debug: Annotated[
|
|
985
1004
|
bool,
|
|
986
1005
|
Field(
|
|
987
|
-
description="Include diagnostic info (request URL, headers sent, response headers). Default: false.",
|
|
1006
|
+
description="Proxy mode only. Include diagnostic info (request URL, headers sent, response headers). Default: false.",
|
|
988
1007
|
default=False,
|
|
989
1008
|
),
|
|
990
1009
|
] = False,
|
|
991
1010
|
port: Annotated[
|
|
992
1011
|
int | None,
|
|
993
1012
|
Field(
|
|
994
|
-
description="Connect to this port instead of the Ingress port. "
|
|
1013
|
+
description="Proxy mode only. Connect to this port instead of the Ingress port. "
|
|
995
1014
|
"Use ha_get_addon(slug='...') to find available ports.",
|
|
996
1015
|
default=None,
|
|
997
1016
|
),
|
|
@@ -999,21 +1018,21 @@ def register_addon_tools(mcp: Any, client: HomeAssistantClient, **kwargs: Any) -
|
|
|
999
1018
|
offset: Annotated[
|
|
1000
1019
|
int,
|
|
1001
1020
|
Field(
|
|
1002
|
-
description="
|
|
1021
|
+
description="Proxy mode only. HTTP: skip this many items in a JSON array response. Default: 0.",
|
|
1003
1022
|
default=0,
|
|
1004
1023
|
),
|
|
1005
1024
|
] = 0,
|
|
1006
1025
|
limit: Annotated[
|
|
1007
1026
|
int | None,
|
|
1008
1027
|
Field(
|
|
1009
|
-
description="
|
|
1028
|
+
description="Proxy mode only. HTTP: return at most this many items from a JSON array response.",
|
|
1010
1029
|
default=None,
|
|
1011
1030
|
),
|
|
1012
1031
|
] = None,
|
|
1013
1032
|
websocket: Annotated[
|
|
1014
1033
|
bool,
|
|
1015
1034
|
Field(
|
|
1016
|
-
description="Use WebSocket instead of HTTP. For streaming endpoints "
|
|
1035
|
+
description="Proxy mode only. Use WebSocket instead of HTTP. For streaming endpoints "
|
|
1017
1036
|
"(e.g., ESPHome /compile, /validate). Sends 'body' as initial message, "
|
|
1018
1037
|
"collects responses. Default: false.",
|
|
1019
1038
|
default=False,
|
|
@@ -1022,25 +1041,233 @@ def register_addon_tools(mcp: Any, client: HomeAssistantClient, **kwargs: Any) -
|
|
|
1022
1041
|
wait_for_close: Annotated[
|
|
1023
1042
|
bool,
|
|
1024
1043
|
Field(
|
|
1025
|
-
description="
|
|
1044
|
+
description="Proxy mode only. WebSocket: True: wait for server to close (for compile/validate). "
|
|
1026
1045
|
"False: return after first response batch (for quick commands). Default: true.",
|
|
1027
1046
|
default=True,
|
|
1028
1047
|
),
|
|
1029
1048
|
] = True,
|
|
1049
|
+
options: Annotated[
|
|
1050
|
+
dict[str, Any] | None,
|
|
1051
|
+
Field(
|
|
1052
|
+
description="Config mode: Add-on configuration values (the 'Configuration' tab in the UI).",
|
|
1053
|
+
default=None,
|
|
1054
|
+
),
|
|
1055
|
+
] = None,
|
|
1056
|
+
network: Annotated[
|
|
1057
|
+
dict[str, Any] | None,
|
|
1058
|
+
Field(
|
|
1059
|
+
description="Config mode: Host port mappings (e.g., {'5800/tcp': 8081}).",
|
|
1060
|
+
default=None,
|
|
1061
|
+
),
|
|
1062
|
+
] = None,
|
|
1063
|
+
boot: Annotated[
|
|
1064
|
+
str | None,
|
|
1065
|
+
Field(
|
|
1066
|
+
description="Config mode: Boot strategy — 'auto' (start with HA) or 'manual'.",
|
|
1067
|
+
default=None,
|
|
1068
|
+
),
|
|
1069
|
+
] = None,
|
|
1070
|
+
auto_update: Annotated[
|
|
1071
|
+
bool | None,
|
|
1072
|
+
Field(
|
|
1073
|
+
description="Config mode: Enable or disable automatic updates for this add-on.",
|
|
1074
|
+
default=None,
|
|
1075
|
+
),
|
|
1076
|
+
] = None,
|
|
1077
|
+
watchdog: Annotated[
|
|
1078
|
+
bool | None,
|
|
1079
|
+
Field(
|
|
1080
|
+
description="Config mode: Enable or disable Supervisor watchdog (auto-restart on crash).",
|
|
1081
|
+
default=None,
|
|
1082
|
+
),
|
|
1083
|
+
] = None,
|
|
1030
1084
|
) -> dict[str, Any]:
|
|
1031
|
-
"""
|
|
1085
|
+
"""Manage a Home Assistant add-on — update its configuration or call its internal API.
|
|
1032
1086
|
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1087
|
+
Two mutually exclusive operating modes:
|
|
1088
|
+
|
|
1089
|
+
**Config mode** (when any of options/network/boot/auto_update/watchdog is provided):
|
|
1090
|
+
Updates the add-on's Supervisor configuration via POST /addons/{slug}/options.
|
|
1091
|
+
All config parameters are optional; only provided fields are updated — current values
|
|
1092
|
+
are fetched and merged automatically (including one level of nested dicts).
|
|
1093
|
+
|
|
1094
|
+
**Proxy mode** (when path is provided):
|
|
1095
|
+
Sends requests directly to the add-on container's own web API via HTTP or WebSocket.
|
|
1096
|
+
Use ha_get_addon(slug="...") to discover available ports and endpoints.
|
|
1097
|
+
|
|
1098
|
+
**WARNING:** Setting boot="auto"/"manual" will fail for add-ons whose Supervisor
|
|
1099
|
+
metadata locks the boot mode. The Supervisor returns an error in this case.
|
|
1100
|
+
|
|
1101
|
+
**NOTE:** This tool only works with Home Assistant OS or Supervised installations.
|
|
1037
1102
|
|
|
1038
1103
|
**Examples:**
|
|
1039
|
-
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1104
|
+
- Set add-on option: ha_manage_addon(slug="...", options={"log_level": "debug"})
|
|
1105
|
+
Note: only the fields you provide are updated — current values are fetched first
|
|
1106
|
+
and merged automatically. Fields not in the add-on's schema are ignored with a warning.
|
|
1107
|
+
- Disable auto-update: ha_manage_addon(slug="...", auto_update=False)
|
|
1108
|
+
- Change host port: ha_manage_addon(slug="...", network={"5800/tcp": 8082})
|
|
1109
|
+
- Set boot mode: ha_manage_addon(slug="...", boot="manual")
|
|
1110
|
+
- Call HTTP API: ha_manage_addon(slug="...", path="/api/events")
|
|
1111
|
+
- Direct port: ha_manage_addon(slug="...", path="/flows", port=1880)
|
|
1112
|
+
- WebSocket: ha_manage_addon(slug="...", path="/validate", port=6052, websocket=True, body={"type": "spawn", "configuration": "device.yaml"})
|
|
1042
1113
|
"""
|
|
1043
|
-
#
|
|
1114
|
+
# Build config payload from provided config parameters
|
|
1115
|
+
config_data: dict[str, Any] = {}
|
|
1116
|
+
if options:
|
|
1117
|
+
config_data["options"] = options
|
|
1118
|
+
if network:
|
|
1119
|
+
config_data["network"] = network
|
|
1120
|
+
if boot is not None:
|
|
1121
|
+
config_data["boot"] = boot
|
|
1122
|
+
if auto_update is not None:
|
|
1123
|
+
config_data["auto_update"] = auto_update
|
|
1124
|
+
if watchdog is not None:
|
|
1125
|
+
config_data["watchdog"] = watchdog
|
|
1126
|
+
|
|
1127
|
+
# Validate mode selection
|
|
1128
|
+
if path is not None and path == "":
|
|
1129
|
+
raise_tool_error(
|
|
1130
|
+
create_validation_error(
|
|
1131
|
+
"'path' must not be empty. Provide a non-empty path for proxy mode "
|
|
1132
|
+
"(e.g., '/api/events') or omit it to use config mode.",
|
|
1133
|
+
parameter="path",
|
|
1134
|
+
)
|
|
1135
|
+
)
|
|
1136
|
+
if path is not None and config_data:
|
|
1137
|
+
raise_tool_error(
|
|
1138
|
+
create_validation_error(
|
|
1139
|
+
"Cannot combine 'path' (proxy mode) with config parameters "
|
|
1140
|
+
"(options/network/boot/auto_update/watchdog). Use one mode at a time.",
|
|
1141
|
+
parameter="path",
|
|
1142
|
+
)
|
|
1143
|
+
)
|
|
1144
|
+
if not path and not config_data:
|
|
1145
|
+
raise_tool_error(
|
|
1146
|
+
create_validation_error(
|
|
1147
|
+
"Must provide either 'path' for proxy mode or at least one config parameter "
|
|
1148
|
+
"(options/network/boot/auto_update/watchdog) for config mode.",
|
|
1149
|
+
parameter="path",
|
|
1150
|
+
)
|
|
1151
|
+
)
|
|
1152
|
+
|
|
1153
|
+
# Validate that proxy-only params are not passed in config mode
|
|
1154
|
+
if config_data:
|
|
1155
|
+
proxy_overrides: list[tuple[str, str]] = []
|
|
1156
|
+
if method != "GET":
|
|
1157
|
+
proxy_overrides.append(("method", f"method={method!r}"))
|
|
1158
|
+
if body is not None:
|
|
1159
|
+
proxy_overrides.append(("body", "body"))
|
|
1160
|
+
if debug:
|
|
1161
|
+
proxy_overrides.append(("debug", "debug=True"))
|
|
1162
|
+
if port is not None:
|
|
1163
|
+
proxy_overrides.append(("port", f"port={port}"))
|
|
1164
|
+
if offset != 0:
|
|
1165
|
+
proxy_overrides.append(("offset", f"offset={offset}"))
|
|
1166
|
+
if limit is not None:
|
|
1167
|
+
proxy_overrides.append(("limit", f"limit={limit}"))
|
|
1168
|
+
if websocket:
|
|
1169
|
+
proxy_overrides.append(("websocket", "websocket=True"))
|
|
1170
|
+
if not wait_for_close:
|
|
1171
|
+
proxy_overrides.append(("wait_for_close", "wait_for_close=False"))
|
|
1172
|
+
if proxy_overrides:
|
|
1173
|
+
raise_tool_error(
|
|
1174
|
+
create_validation_error(
|
|
1175
|
+
f"Proxy-mode parameters cannot be used in config mode: {', '.join(d for _, d in proxy_overrides)}. "
|
|
1176
|
+
"Remove these parameters or switch to proxy mode by providing 'path'.",
|
|
1177
|
+
parameter=proxy_overrides[0][0],
|
|
1178
|
+
)
|
|
1179
|
+
)
|
|
1180
|
+
|
|
1181
|
+
# Config mode: update Supervisor settings
|
|
1182
|
+
if config_data:
|
|
1183
|
+
ignored_fields: list[str] = [] # populated only when options are provided
|
|
1184
|
+
# For options updates: fetch current state first.
|
|
1185
|
+
# GET /info provides both current options (for merge) and schema_ui
|
|
1186
|
+
# (for pre-write unknown-field detection) in a single roundtrip.
|
|
1187
|
+
if "options" in config_data:
|
|
1188
|
+
info_result = await _supervisor_api_call(client, f"/addons/{slug}/info")
|
|
1189
|
+
if not info_result.get("success"):
|
|
1190
|
+
raise_tool_error(
|
|
1191
|
+
create_error_response(
|
|
1192
|
+
ErrorCode.RESOURCE_NOT_FOUND,
|
|
1193
|
+
f"Add-on '{slug}' not found or Supervisor unavailable",
|
|
1194
|
+
details=str(info_result),
|
|
1195
|
+
)
|
|
1196
|
+
)
|
|
1197
|
+
addon_info = info_result.get("result", {})
|
|
1198
|
+
|
|
1199
|
+
# Merge caller's options into current options (fixes partial-update rejection).
|
|
1200
|
+
# Supervisor validates the full options dict against the add-on schema,
|
|
1201
|
+
# so callers must always submit all required fields — merging makes that
|
|
1202
|
+
# transparent.
|
|
1203
|
+
current_options: dict = addon_info.get("options") or {}
|
|
1204
|
+
merged_options = _merge_options(current_options, config_data["options"])
|
|
1205
|
+
|
|
1206
|
+
# Pre-write schema check: identify fields not in the add-on's schema.
|
|
1207
|
+
# Supervisor silently drops unknown fields on write; surfacing them here
|
|
1208
|
+
# lets the caller correct mistakes before any state is changed.
|
|
1209
|
+
schema_ui: list | None = addon_info.get("schema")
|
|
1210
|
+
if schema_ui is not None:
|
|
1211
|
+
allowed_keys = {item["name"] for item in schema_ui if "name" in item}
|
|
1212
|
+
ignored_fields = [k for k in config_data["options"] if k not in allowed_keys]
|
|
1213
|
+
# Remove unknown fields from the merged dict so Supervisor does not
|
|
1214
|
+
# silently strip them after the write succeeds.
|
|
1215
|
+
for k in ignored_fields:
|
|
1216
|
+
merged_options.pop(k, None)
|
|
1217
|
+
|
|
1218
|
+
config_data["options"] = merged_options
|
|
1219
|
+
|
|
1220
|
+
result = await _supervisor_api_call(
|
|
1221
|
+
client,
|
|
1222
|
+
f"/addons/{slug}/options",
|
|
1223
|
+
method="POST",
|
|
1224
|
+
data=config_data,
|
|
1225
|
+
)
|
|
1226
|
+
if not result.get("success"):
|
|
1227
|
+
# Surface Supervisor schema errors (e.g. missing required field) as
|
|
1228
|
+
# VALIDATION_FAILED so the model receives an actionable error code.
|
|
1229
|
+
error_detail = str(result)
|
|
1230
|
+
raise_tool_error(
|
|
1231
|
+
create_error_response(
|
|
1232
|
+
ErrorCode.VALIDATION_FAILED,
|
|
1233
|
+
f"Supervisor rejected configuration for add-on '{slug}'",
|
|
1234
|
+
details=error_detail,
|
|
1235
|
+
suggestions=[
|
|
1236
|
+
"Fetch current options via ha_get_addon(slug) to see required fields",
|
|
1237
|
+
"Re-submit all required option fields together",
|
|
1238
|
+
],
|
|
1239
|
+
)
|
|
1240
|
+
)
|
|
1241
|
+
submitted_fields = list(config_data.keys())
|
|
1242
|
+
response: dict = {}
|
|
1243
|
+
if {"options", "network"} & config_data.keys():
|
|
1244
|
+
response = {
|
|
1245
|
+
"status": "pending_restart",
|
|
1246
|
+
"message": (
|
|
1247
|
+
f"Configuration submitted for add-on '{slug}'. "
|
|
1248
|
+
"Restart the add-on for options/network changes to take effect."
|
|
1249
|
+
),
|
|
1250
|
+
"submitted_fields": submitted_fields,
|
|
1251
|
+
}
|
|
1252
|
+
else:
|
|
1253
|
+
response = {
|
|
1254
|
+
"success": True,
|
|
1255
|
+
"message": f"Configuration updated for add-on '{slug}'.",
|
|
1256
|
+
"submitted_fields": submitted_fields,
|
|
1257
|
+
}
|
|
1258
|
+
if ignored_fields:
|
|
1259
|
+
response["warning"] = (
|
|
1260
|
+
f"{len(ignored_fields)} field(s) not in add-on schema were ignored "
|
|
1261
|
+
f"before write: {ignored_fields}. Use ha_get_addon(slug) to see the "
|
|
1262
|
+
"declared schema."
|
|
1263
|
+
)
|
|
1264
|
+
response["ignored_fields"] = ignored_fields
|
|
1265
|
+
return response
|
|
1266
|
+
|
|
1267
|
+
# Proxy mode: call add-on container API
|
|
1268
|
+
# At this point path is guaranteed non-None (validated above)
|
|
1269
|
+
assert path is not None
|
|
1270
|
+
# WebSocket
|
|
1044
1271
|
if websocket:
|
|
1045
1272
|
result = await _call_addon_ws(
|
|
1046
1273
|
client=client,
|
|
@@ -1056,7 +1283,7 @@ def register_addon_tools(mcp: Any, client: HomeAssistantClient, **kwargs: Any) -
|
|
|
1056
1283
|
raise_tool_error(result)
|
|
1057
1284
|
return result
|
|
1058
1285
|
|
|
1059
|
-
# HTTP
|
|
1286
|
+
# HTTP
|
|
1060
1287
|
valid_methods = {"GET", "POST", "PUT", "DELETE", "PATCH"}
|
|
1061
1288
|
if method.upper() not in valid_methods:
|
|
1062
1289
|
raise_tool_error(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ha-mcp-dev
|
|
3
|
-
Version: 7.
|
|
3
|
+
Version: 7.3.0.dev372
|
|
4
4
|
Summary: Home Assistant MCP Server - Complete control of Home Assistant through MCP
|
|
5
5
|
Author-email: Julien <github@qc-h.net>
|
|
6
6
|
License: MIT
|
|
@@ -184,7 +184,7 @@ Spend less time configuring, more time enjoying your smart home.
|
|
|
184
184
|
|
|
185
185
|
| Category | Tools |
|
|
186
186
|
|----------|-------|
|
|
187
|
-
| **Add-ons** | `
|
|
187
|
+
| **Add-ons** | `ha_get_addon`, `ha_manage_addon` |
|
|
188
188
|
| **Areas & Floors** | `ha_config_list_areas`, `ha_config_list_floors`, `ha_config_remove_area`, `ha_config_remove_floor`, `ha_config_set_area`, `ha_config_set_floor` |
|
|
189
189
|
| **Automations** | `ha_config_get_automation`, `ha_config_remove_automation`, `ha_config_set_automation` |
|
|
190
190
|
| **Blueprints** | `ha_get_blueprint`, `ha_import_blueprint` |
|
|
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.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/resources/skills-vendor/AGENTS.md
RENAMED
|
File without changes
|
{ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/resources/skills-vendor/CLAUDE.md
RENAMED
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/resources/skills-vendor/LICENSE
RENAMED
|
File without changes
|
{ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/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.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/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
|
{ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/tools_config_automations.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/tools_config_dashboards.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/tools_config_entry_flow.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/tools_config_helpers.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/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
|
{ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/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.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/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.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp_dev.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/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
|