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.
Files changed (101) hide show
  1. {ha_mcp_dev-7.2.0.dev370/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.3.0.dev372}/PKG-INFO +2 -2
  2. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/README.md +1 -1
  3. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/pyproject.toml +1 -1
  4. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/__init__.py +1 -1
  5. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/tools_addons.py +254 -27
  6. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372/src/ha_mcp_dev.egg-info}/PKG-INFO +2 -2
  7. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/LICENSE +0 -0
  8. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/MANIFEST.in +0 -0
  9. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/setup.cfg +0 -0
  10. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/__main__.py +0 -0
  11. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/_pypi_marker +0 -0
  12. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/auth/__init__.py +0 -0
  13. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/auth/consent_form.py +0 -0
  14. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/auth/provider.py +0 -0
  15. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/client/__init__.py +0 -0
  16. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/client/rest_client.py +0 -0
  17. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/client/websocket_client.py +0 -0
  18. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/client/websocket_listener.py +0 -0
  19. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/config.py +0 -0
  20. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/errors.py +0 -0
  21. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/py.typed +0 -0
  22. {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
  23. {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
  24. {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
  25. {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
  26. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
  27. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
  28. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
  29. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
  30. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
  31. {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
  32. {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
  33. {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
  34. {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
  35. {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
  36. {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
  37. {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
  38. {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
  39. {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
  40. {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
  41. {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
  42. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/server.py +0 -0
  43. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/smoke_test.py +0 -0
  44. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/__init__.py +0 -0
  45. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/backup.py +0 -0
  46. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/best_practice_checker.py +0 -0
  47. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/device_control.py +0 -0
  48. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/enhanced.py +0 -0
  49. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/helpers.py +0 -0
  50. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/registry.py +0 -0
  51. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/smart_search.py +0 -0
  52. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/tools_areas.py +0 -0
  53. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/tools_blueprints.py +0 -0
  54. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/tools_bug_report.py +0 -0
  55. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/tools_calendar.py +0 -0
  56. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/tools_camera.py +0 -0
  57. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/tools_categories.py +0 -0
  58. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/tools_config_automations.py +0 -0
  59. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
  60. {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
  61. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
  62. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
  63. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/tools_entities.py +0 -0
  64. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/tools_filesystem.py +0 -0
  65. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/tools_groups.py +0 -0
  66. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/tools_hacs.py +0 -0
  67. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/tools_history.py +0 -0
  68. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/tools_integrations.py +0 -0
  69. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/tools_labels.py +0 -0
  70. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/tools_mcp_component.py +0 -0
  71. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/tools_registry.py +0 -0
  72. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/tools_resources.py +0 -0
  73. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/tools_search.py +0 -0
  74. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/tools_service.py +0 -0
  75. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/tools_services.py +0 -0
  76. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/tools_system.py +0 -0
  77. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/tools_todo.py +0 -0
  78. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/tools_traces.py +0 -0
  79. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/tools_updates.py +0 -0
  80. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/tools_utility.py +0 -0
  81. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
  82. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
  83. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/tools_zones.py +0 -0
  84. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/tools/util_helpers.py +0 -0
  85. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/transforms/__init__.py +0 -0
  86. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/transforms/categorized_search.py +0 -0
  87. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/utils/__init__.py +0 -0
  88. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/utils/config_hash.py +0 -0
  89. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/utils/domain_handlers.py +0 -0
  90. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/utils/fuzzy_search.py +0 -0
  91. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/utils/operation_manager.py +0 -0
  92. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/utils/python_sandbox.py +0 -0
  93. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp/utils/usage_logger.py +0 -0
  94. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
  95. {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
  96. {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
  97. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
  98. {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
  99. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/tests/__init__.py +0 -0
  100. {ha_mcp_dev-7.2.0.dev370 → ha_mcp_dev-7.3.0.dev372}/tests/test_constants.py +0 -0
  101. {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.2.0.dev370
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** | `ha_call_addon_api`, `ha_get_addon` |
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** | `ha_call_addon_api`, `ha_get_addon` |
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.2.0.dev370"
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"
@@ -5,7 +5,7 @@ A Model Context Protocol server that provides complete control over Home Assista
5
5
  through REST API and WebSocket integration with 20+ enhanced tools.
6
6
  """
7
7
 
8
- __version__ = "7.2.0"
8
+ __version__ = "7.3.0"
9
9
  __author__ = "Julien"
10
10
  __license__ = "MIT"
11
11
 
@@ -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: ha_call_addon_api(slug='...', path='...', port=<direct_port>). "
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 ha_call_addon_api.
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": "Call Add-on API",
968
+ "title": "Manage Add-on",
953
969
  },
954
970
  )
955
971
  @log_tool_usage
956
- async def ha_call_addon_api(
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 (e.g., '/flows', '/api/events', '/api/stats').",
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="HTTP only. Skip this many items in a JSON array response. Default: 0.",
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="HTTP only. Return at most this many items from a JSON array response (e.g., limit=20).",
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="WebSocket only. True: wait for server to close (for compile/validate). "
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
- """Call an add-on's HTTP or WebSocket API.
1085
+ """Manage a Home Assistant add-on update its configuration or call its internal API.
1032
1086
 
1033
- Sends requests directly to add-on containers. Use `websocket=true` for
1034
- streaming endpoints (e.g., ESPHome compile/validate). Use `port` to bypass
1035
- Nginx IP restrictions on community add-ons. Use ha_get_addon(slug="...")
1036
- to discover available ports and endpoints.
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
- - HTTP: ha_call_addon_api(slug="...", path="/api/events")
1040
- - Direct port: ha_call_addon_api(slug="...", path="/flows", port=1880)
1041
- - WebSocket: ha_call_addon_api(slug="...", path="/validate", port=6052, websocket=true, body={"type": "spawn", "configuration": "device.yaml"})
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
- # WebSocket mode
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 mode
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.2.0.dev370
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** | `ha_call_addon_api`, `ha_get_addon` |
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` |