ha-mcp-dev 7.4.1.dev441__tar.gz → 7.4.1.dev443__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 (107) hide show
  1. {ha_mcp_dev-7.4.1.dev441/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.4.1.dev443}/PKG-INFO +5 -6
  2. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/README.md +4 -5
  3. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/pyproject.toml +1 -1
  4. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/config.py +1 -24
  5. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/server.py +179 -66
  6. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/settings_ui.py +102 -24
  7. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/best_practice_checker.py +4 -21
  8. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/tools_config_automations.py +2 -9
  9. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/tools_config_scripts.py +2 -9
  10. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443/src/ha_mcp_dev.egg-info}/PKG-INFO +5 -6
  11. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/LICENSE +0 -0
  12. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/MANIFEST.in +0 -0
  13. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/setup.cfg +0 -0
  14. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/__init__.py +0 -0
  15. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/__main__.py +0 -0
  16. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/_pypi_marker +0 -0
  17. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/_version.py +0 -0
  18. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/auth/__init__.py +0 -0
  19. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/auth/consent_form.py +0 -0
  20. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/auth/provider.py +0 -0
  21. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/client/__init__.py +0 -0
  22. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/client/rest_client.py +0 -0
  23. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/client/websocket_client.py +0 -0
  24. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/client/websocket_listener.py +0 -0
  25. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/errors.py +0 -0
  26. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/py.typed +0 -0
  27. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
  28. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
  29. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
  30. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
  31. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
  32. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
  33. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
  34. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
  35. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
  36. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
  37. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
  38. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
  39. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
  40. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
  41. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
  42. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
  43. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
  44. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
  45. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
  46. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
  47. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/smoke_test.py +0 -0
  48. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/__init__.py +0 -0
  49. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/backup.py +0 -0
  50. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/device_control.py +0 -0
  51. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/enhanced.py +0 -0
  52. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/helpers.py +0 -0
  53. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/reference_validator.py +0 -0
  54. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/registry.py +0 -0
  55. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/smart_search.py +0 -0
  56. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/tools_addons.py +0 -0
  57. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/tools_areas.py +0 -0
  58. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/tools_blueprints.py +0 -0
  59. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/tools_bug_report.py +0 -0
  60. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/tools_calendar.py +0 -0
  61. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/tools_camera.py +0 -0
  62. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/tools_categories.py +0 -0
  63. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
  64. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/tools_config_entry_flow.py +0 -0
  65. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
  66. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/tools_energy.py +0 -0
  67. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/tools_entities.py +0 -0
  68. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/tools_filesystem.py +0 -0
  69. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/tools_groups.py +0 -0
  70. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/tools_hacs.py +0 -0
  71. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/tools_history.py +0 -0
  72. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/tools_integrations.py +0 -0
  73. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/tools_labels.py +0 -0
  74. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/tools_mcp_component.py +0 -0
  75. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/tools_registry.py +0 -0
  76. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/tools_resources.py +0 -0
  77. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/tools_search.py +0 -0
  78. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/tools_service.py +0 -0
  79. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/tools_services.py +0 -0
  80. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/tools_system.py +0 -0
  81. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/tools_todo.py +0 -0
  82. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/tools_traces.py +0 -0
  83. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/tools_updates.py +0 -0
  84. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/tools_utility.py +0 -0
  85. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
  86. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
  87. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/tools_zones.py +0 -0
  88. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/util_helpers.py +0 -0
  89. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/transforms/__init__.py +0 -0
  90. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/transforms/categorized_search.py +0 -0
  91. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/utils/__init__.py +0 -0
  92. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/utils/config_hash.py +0 -0
  93. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/utils/data_paths.py +0 -0
  94. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/utils/domain_handlers.py +0 -0
  95. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/utils/fuzzy_search.py +0 -0
  96. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
  97. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/utils/operation_manager.py +0 -0
  98. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/utils/python_sandbox.py +0 -0
  99. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/utils/usage_logger.py +0 -0
  100. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
  101. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
  102. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
  103. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
  104. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
  105. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/tests/__init__.py +0 -0
  106. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/tests/test_constants.py +0 -0
  107. {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/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.4.1.dev441
3
+ Version: 7.4.1.dev443
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
@@ -254,12 +254,9 @@ An MCP server can create automations, helpers, and dashboards, but it has no opi
254
254
 
255
255
  ### Bundled Skills (built-in)
256
256
 
257
- Skills from `homeassistant-ai/skills` are bundled and served as [MCP resources](https://modelcontextprotocol.io/docs/concepts/resources) via `skill://` URIs. Any MCP client that supports resources can discover them automatically — no manual installation needed.
257
+ Skills from `homeassistant-ai/skills` are bundled and served as [MCP resources](https://modelcontextprotocol.io/docs/concepts/resources) via `skill://` URIs. Any MCP client that supports resources can discover them automatically — no manual installation needed. For tool-only clients, the same skills are also exposed as `ha_list_resources` / `ha_read_resource` tools. Resources are not auto-injected into context — clients must explicitly request them, so idle context cost is just the metadata listing.
258
258
 
259
- | Setting | Default | Description |
260
- |---------|---------|-------------|
261
- | `ENABLE_SKILLS` | `true` | Serve skills as MCP resources. Resources are not auto-injected into context — clients must explicitly request them. |
262
- | `ENABLE_SKILLS_AS_TOOLS` | `true` | Expose skills and doc resources via `list_resources`/`read_resource` tools. Resource-capable clients can set to `false` to reduce tool count. |
259
+ If you want to hide either tool from the catalog, disable it from the web settings UI like any other tool.
263
260
 
264
261
  Skills can still be installed manually for clients that prefer local skill files — see the [skills repo](https://github.com/homeassistant-ai/skills) for instructions.
265
262
 
@@ -344,6 +341,8 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
344
341
  - **[@gcormier](https://github.com/gcormier)** — Windows installer improvements: removed unused variable and fixed terminal closing after install.
345
342
  - **[@ekobres](https://github.com/ekobres)** — Feature flags for `HAMCP_ENABLE_FILESYSTEM_TOOLS` and `HAMCP_ENABLE_CUSTOM_COMPONENT_INTEGRATION` in the add-on config, with beta tagging in source and docs.
346
343
  - **[@w3z315](https://github.com/w3z315)** — Financial support via [GitHub Sponsors](https://github.com/sponsors/julienld). Thank you! ☕
344
+ - **[@griffinmartin](https://github.com/griffinmartin)** — Added OpenCode (by Anomaly) as a selectable AI client in the setup wizard, with both stdio and streamable HTTP support.
345
+ - **[@hhopke](https://github.com/hhopke)** — Fixed addon API calls to route through HA Core ingress proxy instead of direct container connections, fixing `ha_manage_addon` proxy mode on addon installs.
347
346
 
348
347
  ---
349
348
 
@@ -225,12 +225,9 @@ An MCP server can create automations, helpers, and dashboards, but it has no opi
225
225
 
226
226
  ### Bundled Skills (built-in)
227
227
 
228
- Skills from `homeassistant-ai/skills` are bundled and served as [MCP resources](https://modelcontextprotocol.io/docs/concepts/resources) via `skill://` URIs. Any MCP client that supports resources can discover them automatically — no manual installation needed.
228
+ Skills from `homeassistant-ai/skills` are bundled and served as [MCP resources](https://modelcontextprotocol.io/docs/concepts/resources) via `skill://` URIs. Any MCP client that supports resources can discover them automatically — no manual installation needed. For tool-only clients, the same skills are also exposed as `ha_list_resources` / `ha_read_resource` tools. Resources are not auto-injected into context — clients must explicitly request them, so idle context cost is just the metadata listing.
229
229
 
230
- | Setting | Default | Description |
231
- |---------|---------|-------------|
232
- | `ENABLE_SKILLS` | `true` | Serve skills as MCP resources. Resources are not auto-injected into context — clients must explicitly request them. |
233
- | `ENABLE_SKILLS_AS_TOOLS` | `true` | Expose skills and doc resources via `list_resources`/`read_resource` tools. Resource-capable clients can set to `false` to reduce tool count. |
230
+ If you want to hide either tool from the catalog, disable it from the web settings UI like any other tool.
234
231
 
235
232
  Skills can still be installed manually for clients that prefer local skill files — see the [skills repo](https://github.com/homeassistant-ai/skills) for instructions.
236
233
 
@@ -315,6 +312,8 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
315
312
  - **[@gcormier](https://github.com/gcormier)** — Windows installer improvements: removed unused variable and fixed terminal closing after install.
316
313
  - **[@ekobres](https://github.com/ekobres)** — Feature flags for `HAMCP_ENABLE_FILESYSTEM_TOOLS` and `HAMCP_ENABLE_CUSTOM_COMPONENT_INTEGRATION` in the add-on config, with beta tagging in source and docs.
317
314
  - **[@w3z315](https://github.com/w3z315)** — Financial support via [GitHub Sponsors](https://github.com/sponsors/julienld). Thank you! ☕
315
+ - **[@griffinmartin](https://github.com/griffinmartin)** — Added OpenCode (by Anomaly) as a selectable AI client in the setup wizard, with both stdio and streamable HTTP support.
316
+ - **[@hhopke](https://github.com/hhopke)** — Fixed addon API calls to route through HA Core ingress proxy instead of direct container connections, fixing `ha_manage_addon` proxy mode on addon installs.
318
317
 
319
318
  ---
320
319
 
@@ -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.dev441"
7
+ version = "7.4.1.dev443"
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"
@@ -9,7 +9,7 @@ import os
9
9
  from pathlib import Path
10
10
 
11
11
  from dotenv import load_dotenv
12
- from pydantic import Field, field_validator, model_validator
12
+ from pydantic import Field, field_validator
13
13
  from pydantic_settings import BaseSettings, SettingsConfigDict
14
14
 
15
15
  from ha_mcp._version import get_version
@@ -92,17 +92,6 @@ class Settings(BaseSettings):
92
92
  True, alias="ENABLE_DASHBOARD_PARTIAL_TOOLS"
93
93
  )
94
94
 
95
- # Skills configuration
96
- # Serve bundled HA best-practice skills as MCP resources (skill:// URIs).
97
- # Resources are not auto-injected — clients must explicitly request them.
98
- enable_skills: bool = Field(True, alias="ENABLE_SKILLS")
99
-
100
- # Expose skills and doc resources as tools (list_resources/read_resource)
101
- # for clients that don't support MCP resources natively.
102
- # Defaults to True so all clients can access documentation and skills.
103
- # Resource-capable clients can set to False to reduce tool count.
104
- enable_skills_as_tools: bool = Field(True, alias="ENABLE_SKILLS_AS_TOOLS")
105
-
106
95
  # Tool search transform — replaces the full tool catalog with a unified
107
96
  # BM25 search tool and categorized call proxies (read/write/delete).
108
97
  # Dramatically reduces idle context token usage for LLMs.
@@ -124,18 +113,6 @@ class Settings(BaseSettings):
124
113
  # supervisor UI rejects out-of-range values before they reach env vars.
125
114
  tool_search_max_results: int = Field(5, ge=2, le=10, alias="TOOL_SEARCH_MAX_RESULTS")
126
115
 
127
- @model_validator(mode="after")
128
- def _skills_dependency(self) -> "Settings":
129
- """Auto-enable skills (resources) when skills-as-tools is on.
130
-
131
- skills_as_tools wraps ResourcesAsTools which requires skills to be
132
- registered as MCP resources first. Without this, enabling
133
- skills_as_tools alone would produce empty list_resources results.
134
- """
135
- if self.enable_skills_as_tools and not self.enable_skills:
136
- self.enable_skills = True
137
- return self
138
-
139
116
  @property
140
117
  def env_file_name(self) -> str:
141
118
  """Get the current environment file name."""
@@ -16,6 +16,7 @@ from typing import TYPE_CHECKING, Any, ClassVar, cast
16
16
 
17
17
  import yaml # type: ignore[import-untyped]
18
18
  from fastmcp import FastMCP
19
+ from fastmcp.server.transforms import ResourcesAsTools
19
20
  from mcp.types import Icon
20
21
 
21
22
  from .config import _PACKAGE_VERSION, get_global_settings
@@ -23,11 +24,84 @@ from .tools.enhanced import EnhancedToolsMixin
23
24
  from .transforms import DEFAULT_PINNED_TOOLS
24
25
 
25
26
  if TYPE_CHECKING:
27
+ from collections.abc import Sequence
28
+
29
+ from fastmcp.server.transforms import GetToolNext
30
+ from fastmcp.tools.base import Tool
31
+ from fastmcp.utilities.versions import VersionSpec
32
+
26
33
  from .client.rest_client import HomeAssistantClient
27
34
  from .tools.registry import ToolsRegistry
28
35
 
29
36
  logger = logging.getLogger(__name__)
30
37
 
38
+
39
+ class HaResourcesAsTools(ResourcesAsTools):
40
+ """ResourcesAsTools renamed to follow ha-mcp's ha_<verb>_<noun> convention.
41
+
42
+ FastMCP's ResourcesAsTools transform hardcodes ``list_resources`` and
43
+ ``read_resource``. This subclass renames them to ``ha_list_resources``
44
+ and ``ha_read_resource`` so they behave like every other tool in the
45
+ catalog (consistent prefix, discoverable in the web settings UI).
46
+
47
+ Upgrade fragility: depends on FastMCP's ``_make_list_resources_tool`` /
48
+ ``_make_read_resource_tool`` private factories and on the names
49
+ ``list_resources`` / ``read_resource`` produced by them. A FastMCP
50
+ upgrade that renames either factory or either tool name will require
51
+ a matching update here. ``list_tools`` logs a warning if the rename
52
+ fails to match exactly two tools so the regression is loud at boot.
53
+ """
54
+
55
+ LIST_TOOL_NAME = "ha_list_resources"
56
+ READ_TOOL_NAME = "ha_read_resource"
57
+ _RENAMES: ClassVar[dict[str, str]] = {
58
+ "list_resources": LIST_TOOL_NAME,
59
+ "read_resource": READ_TOOL_NAME,
60
+ }
61
+
62
+ async def list_tools(self, tools: Sequence[Tool]) -> Sequence[Tool]:
63
+ # Scan the entire result rather than slicing the tail so a future
64
+ # FastMCP change that reorders or expands the appended tool set
65
+ # surfaces as a logged warning instead of silently leaking the
66
+ # unprefixed names into the catalog.
67
+ result = list(await super().list_tools(tools))
68
+ renamed: list[Tool] = []
69
+ matches = 0
70
+ for tool in result:
71
+ new_name = self._RENAMES.get(tool.name)
72
+ if new_name is None:
73
+ renamed.append(tool)
74
+ continue
75
+ renamed.append(tool.model_copy(update={"name": new_name}))
76
+ matches += 1
77
+ if matches != len(self._RENAMES):
78
+ logger.warning(
79
+ "HaResourcesAsTools: expected to rename %d tools (%s) but "
80
+ "matched %d in the upstream tool list — fastmcp's "
81
+ "ResourcesAsTools contract may have changed",
82
+ len(self._RENAMES),
83
+ ", ".join(self._RENAMES),
84
+ matches,
85
+ )
86
+ return renamed
87
+
88
+ async def get_tool(
89
+ self,
90
+ name: str,
91
+ call_next: GetToolNext,
92
+ *,
93
+ version: VersionSpec | None = None,
94
+ ) -> Tool | None:
95
+ if name == self.LIST_TOOL_NAME:
96
+ return self._make_list_resources_tool().model_copy(
97
+ update={"name": self.LIST_TOOL_NAME}
98
+ )
99
+ if name == self.READ_TOOL_NAME:
100
+ return self._make_read_resource_tool().model_copy(
101
+ update={"name": self.READ_TOOL_NAME}
102
+ )
103
+ return await call_next(name, version=version)
104
+
31
105
  # Server icon configuration using GitHub-hosted images
32
106
  # These icons are bundled in packaging/mcpb/ and also available via GitHub raw URLs
33
107
  SERVER_ICONS = [
@@ -179,12 +253,9 @@ class HomeAssistantSmartMCPServer(EnhancedToolsMixin):
179
253
  is authored for LLM consumption and should not be parsed or
180
254
  restructured by code.
181
255
 
182
- Returns None when skills are disabled, leaving instructions unchanged
183
- from the default (None).
256
+ Returns None when no skills directory or no parseable skills are
257
+ present, leaving instructions unchanged from the default (None).
184
258
  """
185
- if not self.settings.enable_skills:
186
- return None
187
-
188
259
  skills_dir = self._get_skills_dir()
189
260
  if not skills_dir:
190
261
  return None
@@ -208,22 +279,15 @@ class HomeAssistantSmartMCPServer(EnhancedToolsMixin):
208
279
  if not skill_blocks:
209
280
  return None
210
281
 
211
- # Build the access method instruction based on config
212
- if self.settings.enable_skills_as_tools:
213
- access_method = (
214
- "Read the skill via MCP resources (resources/read with the "
215
- "skill:// URI) — if you can read these instructions, you "
216
- "should be able to access resources as well. If for any "
217
- "reason you cannot access MCP resources, use the "
218
- "list_resources and read_resource tools as a fallback. "
219
- "If you can access resources normally, do not waste "
220
- "time or tokens on those tools."
221
- )
222
- else:
223
- access_method = (
224
- "Read the skill via MCP resources (resources/read with the "
225
- "skill:// URI)."
226
- )
282
+ access_method = (
283
+ "Read the skill via MCP resources (resources/read with the "
284
+ "skill:// URI) — if you can read these instructions, you "
285
+ "should be able to access resources as well. If for any "
286
+ "reason you cannot access MCP resources, use the "
287
+ "ha_list_resources and ha_read_resource tools as a fallback. "
288
+ "If you can access resources normally, do not waste "
289
+ "time or tokens on those tools."
290
+ )
227
291
 
228
292
  header = (
229
293
  "IMPORTANT: This server provides best-practice skills that MUST "
@@ -517,25 +581,27 @@ class HomeAssistantSmartMCPServer(EnhancedToolsMixin):
517
581
  pinned = list(self._PINNED_TOOLS)
518
582
  pinned.extend(self._user_pinned_tools)
519
583
 
520
- # Pin ResourcesAsTools and skill guidance tools if skills-as-tools is enabled
521
- if self.settings.enable_skills_as_tools:
522
- pinned.extend(["list_resources", "read_resource"])
523
- # Forward-compatible: pin skill guidance tools registered by #732
524
- pinned.extend(getattr(self, "_skill_tool_names", []))
525
-
526
- # When skills-as-tools is enabled, the client likely doesn't support
527
- # resources or server instructions — add skills hint to the search
528
- # tool description (the one place the LLM is guaranteed to see).
529
- description = self._SEARCH_TOOL_DESCRIPTION
530
- if self.settings.enable_skills_as_tools:
531
- description += (
532
- "\n\nThis server also provides best-practice skills via "
533
- "skill:// resources. If your client supports MCP resources, "
534
- "prefer reading them directly. Otherwise, call "
535
- "list_resources and read_resource (directly, no proxy "
536
- "needed) to access the relevant SKILL.md before creating "
537
- "automations or configuring devices."
538
- )
584
+ # Pin the skills-as-tools transform pair and the per-skill guidance
585
+ # tools so they remain visible when search-based discovery is on.
586
+ # The settings UI's mcp.disable() flow runs after these transforms
587
+ # are appended, so a per-tool disable still wins over this pin.
588
+ pinned.extend(
589
+ [HaResourcesAsTools.LIST_TOOL_NAME, HaResourcesAsTools.READ_TOOL_NAME]
590
+ )
591
+ pinned.extend(getattr(self, "_skill_tool_names", []))
592
+
593
+ # The client may not support resources or server instructions — add
594
+ # skills hint to the search tool description (the one place the LLM
595
+ # is guaranteed to see).
596
+ description = self._SEARCH_TOOL_DESCRIPTION + (
597
+ "\n\nThis server also provides best-practice skills via "
598
+ "skill:// resources. If your client supports MCP resources, "
599
+ f"prefer reading them directly. Otherwise, call "
600
+ f"{HaResourcesAsTools.LIST_TOOL_NAME} and "
601
+ f"{HaResourcesAsTools.READ_TOOL_NAME} (directly, no proxy "
602
+ "needed) to access the relevant SKILL.md before creating "
603
+ "automations or configuring devices."
604
+ )
539
605
 
540
606
  try:
541
607
  self.mcp.add_transform(
@@ -556,14 +622,29 @@ class HomeAssistantSmartMCPServer(EnhancedToolsMixin):
556
622
  def _register_skills(self) -> None:
557
623
  """Register bundled HA best-practice skills as MCP resources.
558
624
 
559
- Uses FastMCP's SkillsDirectoryProvider to serve skill files via skill:// URIs.
560
- Optionally exposes skills as tools (list_resources/read_resource) for clients
561
- that don't support MCP resources natively.
562
-
563
- Controlled by ENABLE_SKILLS and ENABLE_SKILLS_AS_TOOLS settings.
625
+ Uses FastMCP's SkillsDirectoryProvider to serve skill files via
626
+ skill:// URIs and exposes them as tools (ha_list_resources /
627
+ ha_read_resource) for clients that don't support MCP resources
628
+ natively. Per-tool visibility is managed via the web settings UI;
629
+ users who want either tool off can disable it there.
630
+
631
+ Each phase tracks success in ``status`` so the final summary log
632
+ line tells operators at a glance whether the skill system is
633
+ healthy, partially degraded, or fully unavailable. Without that
634
+ summary, three independent ``logger.exception`` calls leave
635
+ operators reconstructing state from scattered log lines.
636
+
637
+ Failure modes degrade unevenly across clients: if Phase 3
638
+ (transform) fails, resource-capable clients still see skills,
639
+ but tool-only clients (claude.ai etc.) lose ha_list_resources
640
+ and ha_read_resource from their catalog with no protocol-level
641
+ error — only the warning summary signals it.
564
642
  """
565
- if not self.settings.enable_skills:
566
- return
643
+ status: dict[str, str | int] = {
644
+ "provider": "skipped",
645
+ "transform": "skipped",
646
+ "guidance_tools": 0,
647
+ }
567
648
 
568
649
  # Phase 1: Import SkillsDirectoryProvider
569
650
  try:
@@ -572,6 +653,7 @@ class HomeAssistantSmartMCPServer(EnhancedToolsMixin):
572
653
  logger.warning(
573
654
  "SkillsDirectoryProvider not available in fastmcp, skipping skills"
574
655
  )
656
+ self._log_skill_registration_summary(status)
575
657
  return
576
658
 
577
659
  # Phase 2: Register skills as MCP resources
@@ -582,6 +664,7 @@ class HomeAssistantSmartMCPServer(EnhancedToolsMixin):
582
664
  "Skills directory not found at %s, skipping skill registration",
583
665
  Path(__file__).parent / "resources" / "skills-vendor" / "skills",
584
666
  )
667
+ self._log_skill_registration_summary(status)
585
668
  return
586
669
 
587
670
  self.mcp.add_provider(
@@ -590,36 +673,61 @@ class HomeAssistantSmartMCPServer(EnhancedToolsMixin):
590
673
  )
591
674
  )
592
675
  logger.info("Registered bundled skills as MCP resources")
676
+ status["provider"] = "ok"
593
677
  except Exception:
594
678
  logger.exception("Failed to register skills as resources")
679
+ status["provider"] = "failed"
680
+ self._log_skill_registration_summary(status)
595
681
  return
596
682
 
597
- # Phase 3: Optionally expose skills as tools
598
- if not self.settings.enable_skills_as_tools:
599
- return
600
-
683
+ # Phase 3: Expose skills as tools so clients without resource
684
+ # support can still reach the documentation.
601
685
  try:
602
- from fastmcp.server.transforms import ResourcesAsTools
603
- except ImportError:
604
- logger.warning(
605
- "ResourcesAsTools not available in fastmcp, "
606
- "skills registered as resources but not exposed as tools"
686
+ self.mcp.add_transform(HaResourcesAsTools(self.mcp))
687
+ logger.info(
688
+ "Skills also exposed as tools (ha_list_resources / ha_read_resource)"
607
689
  )
608
- return
609
-
610
- try:
611
- self.mcp.add_transform(ResourcesAsTools(self.mcp))
612
- logger.info("Skills also exposed as tools (ResourcesAsTools)")
690
+ status["transform"] = "ok"
613
691
  except Exception:
614
692
  logger.exception(
615
693
  "Failed to expose skills as tools (resources still available)"
616
694
  )
695
+ status["transform"] = "failed"
617
696
 
618
697
  # Phase 4: Register skill guidance tools for clients that don't read
619
698
  # server instructions (e.g., claude.ai). The tool description contains
620
699
  # the trigger conditions so the AI sees them in the tool listing.
621
700
  # Names stored for pinning in search transforms (always-visible).
622
701
  self._register_skill_guidance_tools(skills_dir)
702
+ status["guidance_tools"] = len(self._skill_tool_names)
703
+
704
+ self._log_skill_registration_summary(status)
705
+
706
+ @staticmethod
707
+ def _log_skill_registration_summary(status: dict[str, str | int]) -> None:
708
+ """Emit one-line summary of skill registration outcome.
709
+
710
+ ``info`` when both provider and transform succeeded *and* at least
711
+ one guidance tool registered; ``warning`` otherwise. The
712
+ guidance>0 gate catches the "shipped but exposes nothing" case
713
+ (skills directory exists but is empty, or every SKILL.md fails to
714
+ parse) — both prior phases succeed yet no skill is actually
715
+ reachable. This is the line operators should grep for when a
716
+ user reports missing skill features.
717
+ """
718
+ provider = status.get("provider")
719
+ transform = status.get("transform")
720
+ raw_guidance = status.get("guidance_tools", 0)
721
+ guidance = raw_guidance if isinstance(raw_guidance, int) else 0
722
+
723
+ message = (
724
+ "Skill system summary: provider=%s, transform=%s, guidance_tools=%d"
725
+ )
726
+ args = (provider, transform, guidance)
727
+ if provider == "ok" and transform == "ok" and guidance > 0:
728
+ logger.info(message, *args)
729
+ else:
730
+ logger.warning(message, *args)
623
731
 
624
732
  def _register_skill_guidance_tools(self, skills_dir: Path) -> None:
625
733
  """Register a lightweight guidance tool per skill.
@@ -628,7 +736,9 @@ class HomeAssistantSmartMCPServer(EnhancedToolsMixin):
628
736
  so the bootstrap prompt (trigger conditions, symptoms) is invisible.
629
737
  This registers a tool per skill whose description contains the trigger
630
738
  conditions. The tool itself just lists available reference files —
631
- actual content is loaded on demand via read_resource.
739
+ actual content is loaded on demand via the resources/read MCP method
740
+ (or the ha_read_resource fallback tool when the client lacks resource
741
+ support).
632
742
  """
633
743
  try:
634
744
  entries = sorted(skills_dir.iterdir())
@@ -653,7 +763,8 @@ class HomeAssistantSmartMCPServer(EnhancedToolsMixin):
653
763
  tool_description = (
654
764
  f"CALL THIS FIRST before performing matching actions. "
655
765
  f"{description}\n\n"
656
- f"Returns available reference files. Use read_resource with "
766
+ f"Returns available reference files. Read each file via "
767
+ f"resources/read (or ha_read_resource as a fallback) using "
657
768
  f"the file URI to load specific guides as needed."
658
769
  )
659
770
 
@@ -670,9 +781,11 @@ class HomeAssistantSmartMCPServer(EnhancedToolsMixin):
670
781
  "skill": s_name,
671
782
  "skill_uri": s_uri,
672
783
  "how_to_use": (
673
- "Use read_resource with a file URI below to load "
674
- "the specific reference you need. Start with "
675
- "SKILL.md for the decision workflow."
784
+ "Read each file via resources/read (or "
785
+ "ha_read_resource as a fallback) with a file "
786
+ "URI below to load the specific reference you "
787
+ "need. Start with SKILL.md for the decision "
788
+ "workflow."
676
789
  ),
677
790
  "available_files": files,
678
791
  }
@@ -13,7 +13,7 @@ import json
13
13
  import logging
14
14
  import os
15
15
  from pathlib import Path
16
- from typing import TYPE_CHECKING, Any
16
+ from typing import TYPE_CHECKING, Any, NotRequired, TypedDict
17
17
 
18
18
  import httpx
19
19
  from starlette.requests import Request
@@ -30,6 +30,26 @@ if TYPE_CHECKING:
30
30
  from .config import Settings
31
31
  from .server import HomeAssistantSmartMCPServer
32
32
 
33
+
34
+ class ToolStub(TypedDict):
35
+ """Metadata advertised in the settings UI for a tool that isn't visible
36
+ in ``local_provider._list_tools()``.
37
+
38
+ Two reasons a tool needs a stub: it's added by a FastMCP transform at
39
+ runtime (``TRANSFORM_GENERATED_TOOLS``), or it's feature-gated and
40
+ only registers when a setting is on (``FEATURE_GATED_TOOLS``). The
41
+ consumer (`_get_tool_metadata`) renders the same shape for both;
42
+ ``disabled_by`` is the only field that differs and signals UI
43
+ placement of the "Beta — set X" hint.
44
+ """
45
+
46
+ title: str
47
+ primary_tag: str
48
+ description: str
49
+ readOnlyHint: NotRequired[bool]
50
+ destructiveHint: NotRequired[bool]
51
+ disabled_by: NotRequired[str]
52
+
33
53
  _VALID_STATES = frozenset({"enabled", "disabled", "pinned"})
34
54
 
35
55
  logger = logging.getLogger(__name__)
@@ -48,6 +68,39 @@ MANDATORY_TOOLS: set[str] = {
48
68
  "ha_report_issue",
49
69
  }
50
70
 
71
+ # Tools created by FastMCP transforms (not registered through
72
+ # local_provider). The ``ResourcesAsTools`` transform — subclassed in
73
+ # server.py as ``HaResourcesAsTools`` — appends ``ha_list_resources`` and
74
+ # ``ha_read_resource`` at runtime, so they never show up in
75
+ # ``local_provider._list_tools()``. Inject stub metadata so the UI can
76
+ # render them and ``mcp.disable()`` can hide them from the catalog.
77
+ #
78
+ # Keys MUST match ``HaResourcesAsTools.LIST_TOOL_NAME`` / ``READ_TOOL_NAME``;
79
+ # server.py is not imported here to avoid a top-level cycle, but the
80
+ # ``test_transform_generated_tool_names_match_class_constants`` unit test
81
+ # fails fast if either side drifts.
82
+ TRANSFORM_GENERATED_TOOLS: dict[str, ToolStub] = {
83
+ "ha_list_resources": {
84
+ "title": "List Resources",
85
+ "primary_tag": "System",
86
+ "description": (
87
+ "List bundled skill files and other MCP resources exposed via "
88
+ "skill:// URIs. Fallback for clients that do not support MCP "
89
+ "resources natively."
90
+ ),
91
+ "readOnlyHint": True,
92
+ },
93
+ "ha_read_resource": {
94
+ "title": "Read Resource",
95
+ "primary_tag": "System",
96
+ "description": (
97
+ "Read a skill or resource by URI. Fallback for clients that do "
98
+ "not support MCP resources natively."
99
+ ),
100
+ "readOnlyHint": True,
101
+ },
102
+ }
103
+
51
104
  # Tools that exist in the codebase but are only registered when a
52
105
  # corresponding feature flag/env var is set. When the flag is off, these
53
106
  # won't appear in local_provider._list_tools(), so we inject stub entries
@@ -55,48 +108,48 @@ MANDATORY_TOOLS: set[str] = {
55
108
  # it. Keep this dict in sync with the ``"beta"`` tag added to each tool's
56
109
  # source file (tools_yaml_config.py, tools_filesystem.py, tools_mcp_component.py)
57
110
  # — a future rename or removal needs to land in both places.
58
- FEATURE_GATED_TOOLS: dict[str, dict[str, str]] = {
111
+ FEATURE_GATED_TOOLS: dict[str, ToolStub] = {
59
112
  "ha_config_set_yaml": {
60
113
  "title": "Set YAML Config",
61
114
  "primary_tag": "System",
62
115
  "description": "Add, replace, or remove top-level keys in configuration.yaml or package files.",
63
116
  "disabled_by": "enable_yaml_config_editing",
64
- "destructiveHint": "true",
117
+ "destructiveHint": True,
65
118
  },
66
119
  "ha_list_files": {
67
120
  "title": "List Files",
68
121
  "primary_tag": "Files",
69
122
  "description": "List files in a directory within the Home Assistant config.",
70
123
  "disabled_by": "enable_filesystem_tools",
71
- "readOnlyHint": "true",
124
+ "readOnlyHint": True,
72
125
  },
73
126
  "ha_read_file": {
74
127
  "title": "Read File",
75
128
  "primary_tag": "Files",
76
129
  "description": "Read a file from the Home Assistant config directory.",
77
130
  "disabled_by": "enable_filesystem_tools",
78
- "readOnlyHint": "true",
131
+ "readOnlyHint": True,
79
132
  },
80
133
  "ha_write_file": {
81
134
  "title": "Write File",
82
135
  "primary_tag": "Files",
83
136
  "description": "Write a file to allowed directories in the Home Assistant config.",
84
137
  "disabled_by": "enable_filesystem_tools",
85
- "destructiveHint": "true",
138
+ "destructiveHint": True,
86
139
  },
87
140
  "ha_delete_file": {
88
141
  "title": "Delete File",
89
142
  "primary_tag": "Files",
90
143
  "description": "Delete a file from allowed directories.",
91
144
  "disabled_by": "enable_filesystem_tools",
92
- "destructiveHint": "true",
145
+ "destructiveHint": True,
93
146
  },
94
147
  "ha_install_mcp_tools": {
95
148
  "title": "Install MCP Tools Component",
96
149
  "primary_tag": "Utilities",
97
150
  "description": "Install the ha_mcp_tools custom component via HACS.",
98
151
  "disabled_by": "enable_custom_component_integration",
99
- "destructiveHint": "true",
152
+ "destructiveHint": True,
100
153
  },
101
154
  }
102
155
 
@@ -179,6 +232,35 @@ def save_tool_config(config: dict[str, Any]) -> bool:
179
232
  return True
180
233
 
181
234
 
235
+ def _render_stub(name: str, meta: ToolStub) -> dict[str, Any]:
236
+ """Render a ToolStub as the dict shape ``_get_tool_metadata`` returns.
237
+
238
+ Both transform-generated and feature-gated stubs share the same UI
239
+ representation; the only meaningful difference is whether
240
+ ``disabled_by`` carries the safety-toggle name (which the JS
241
+ template renders as a "Beta — set X" hint). Annotations come
242
+ through as bools and are dropped from the final dict when False
243
+ so the JSON payload stays small.
244
+ """
245
+ annotations: dict[str, bool] = {}
246
+ if meta.get("readOnlyHint"):
247
+ annotations["readOnlyHint"] = True
248
+ if meta.get("destructiveHint"):
249
+ annotations["destructiveHint"] = True
250
+
251
+ rendered: dict[str, Any] = {
252
+ "name": name,
253
+ "title": meta["title"],
254
+ "description": meta["description"],
255
+ "tags": [meta["primary_tag"]],
256
+ "primary_tag": meta["primary_tag"],
257
+ "annotations": annotations,
258
+ }
259
+ if "disabled_by" in meta:
260
+ rendered["disabled_by"] = meta["disabled_by"]
261
+ return rendered
262
+
263
+
182
264
  async def _get_tool_metadata(server: HomeAssistantSmartMCPServer) -> list[dict[str, Any]]:
183
265
  """Extract metadata for all registered tools from the server.
184
266
 
@@ -218,25 +300,21 @@ async def _get_tool_metadata(server: HomeAssistantSmartMCPServer) -> list[dict[s
218
300
  "annotations": annotations,
219
301
  })
220
302
 
221
- # Inject stub entries for feature-gated tools that aren't registered
222
303
  registered_names = {t["name"] for t in tools}
304
+
305
+ # Inject stub entries for tools generated by FastMCP transforms — these
306
+ # never reach local_provider so they have to be advertised explicitly.
307
+ for name, transform_meta in TRANSFORM_GENERATED_TOOLS.items():
308
+ if name in registered_names:
309
+ continue
310
+ tools.append(_render_stub(name, transform_meta))
311
+ registered_names.add(name)
312
+
313
+ # Inject stub entries for feature-gated tools that aren't registered
223
314
  for name, meta in FEATURE_GATED_TOOLS.items():
224
315
  if name in registered_names:
225
316
  continue
226
- stub_annotations: dict[str, bool] = {}
227
- if meta.get("readOnlyHint") == "true":
228
- stub_annotations["readOnlyHint"] = True
229
- if meta.get("destructiveHint") == "true":
230
- stub_annotations["destructiveHint"] = True
231
- tools.append({
232
- "name": name,
233
- "title": meta["title"],
234
- "description": meta["description"],
235
- "tags": [meta["primary_tag"]],
236
- "primary_tag": meta["primary_tag"],
237
- "annotations": stub_annotations,
238
- "disabled_by": meta["disabled_by"],
239
- })
317
+ tools.append(_render_stub(name, meta))
240
318
 
241
319
  tools.sort(key=lambda t: (t["primary_tag"], t["name"]))
242
320
  return tools
@@ -380,7 +458,7 @@ _SETTINGS_HTML = """\
380
458
  <span id="status" class="status">Loading...</span>
381
459
  </div>
382
460
  <div class="readonly-notice">
383
- Safety toggles (Enable Skills, Tool Search, YAML Config Editing) are managed in the
461
+ Safety toggles (Tool Search, YAML Config Editing) are managed in the
384
462
  add-on configuration page and require a restart to change.
385
463
  </div>
386
464
  <div class="pin-notice show" id="pinNotice">
@@ -3,10 +3,10 @@
3
3
  Stateless payload inspection — returns warnings pointing to skill reference
4
4
  files. Zero overhead on clean calls (returns empty list).
5
5
 
6
- When skills are enabled (ENABLE_SKILLS=true), warnings include skill:// URIs
7
- so the LLM can read the relevant reference file. When skills are disabled,
8
- callers should pass a fallback prefix (e.g. GitHub URLs) or None to omit
9
- references entirely.
6
+ Warnings include skill:// URIs so the LLM can read the relevant reference
7
+ file via the bundled SkillsDirectoryProvider. The ``skill_prefix`` kwarg
8
+ lets callers pass any URL prefix (e.g., a GitHub mirror) when skill://
9
+ isn't reachable, or ``None`` to omit references entirely.
10
10
 
11
11
  Anti-patterns sourced from:
12
12
  https://github.com/homeassistant-ai/skills
@@ -19,25 +19,8 @@ import re
19
19
  from typing import Any
20
20
 
21
21
  _SKILL_URI_PREFIX = "skill://home-assistant-best-practices/references"
22
- _GITHUB_URL_PREFIX = (
23
- "https://github.com/homeassistant-ai/skills/blob/main"
24
- "/skills/home-assistant-best-practices/references"
25
- )
26
22
  _DEFAULT_SKILL_PREFIX = _SKILL_URI_PREFIX
27
23
 
28
-
29
- def get_skill_prefix() -> str:
30
- """Return the appropriate skill prefix based on global settings.
31
-
32
- When skills are enabled (ENABLE_SKILLS=true), returns skill:// URIs.
33
- Otherwise falls back to GitHub URLs for the reference files.
34
- """
35
- from ..config import get_global_settings
36
-
37
- if get_global_settings().enable_skills:
38
- return _SKILL_URI_PREFIX
39
- return _GITHUB_URL_PREFIX
40
-
41
24
  # ---------------------------------------------------------------------------
42
25
  # Regex patterns for template anti-patterns
43
26
  # ---------------------------------------------------------------------------
@@ -28,9 +28,6 @@ from ..utils.python_sandbox import (
28
28
  from .best_practice_checker import (
29
29
  check_automation_config as _check_best_practices,
30
30
  )
31
- from .best_practice_checker import (
32
- get_skill_prefix as _get_skill_prefix,
33
- )
34
31
  from .helpers import (
35
32
  exception_to_structured_error,
36
33
  log_tool_usage,
@@ -575,9 +572,7 @@ class AutomationConfigTools:
575
572
  # Normalize and validate the transformed config
576
573
  transformed_config = _normalize_automation_config(transformed_config)
577
574
  self._validate_required_fields(transformed_config, identifier)
578
- bp_warnings = _check_best_practices(
579
- transformed_config, skill_prefix=_get_skill_prefix()
580
- )
575
+ bp_warnings = _check_best_practices(transformed_config)
581
576
 
582
577
  # Save transformed config
583
578
  result = await self._client.upsert_automation_config(
@@ -642,9 +637,7 @@ class AutomationConfigTools:
642
637
  self._validate_required_fields(config_dict, identifier)
643
638
 
644
639
  # Pre-check for best-practice issues.
645
- bp_warnings = _check_best_practices(
646
- config_dict, skill_prefix=_get_skill_prefix()
647
- )
640
+ bp_warnings = _check_best_practices(config_dict)
648
641
 
649
642
  # Cross-check literal service and entity references against
650
643
  # the live registries. Soft warnings only — the write still
@@ -22,9 +22,6 @@ from ..utils.python_sandbox import (
22
22
  from .best_practice_checker import (
23
23
  check_script_config as _check_best_practices,
24
24
  )
25
- from .best_practice_checker import (
26
- get_skill_prefix as _get_skill_prefix,
27
- )
28
25
  from .helpers import (
29
26
  exception_to_structured_error,
30
27
  log_tool_usage,
@@ -476,9 +473,7 @@ class ConfigScriptTools:
476
473
  context={"action": "python_transform", "script_id": script_id},
477
474
  )
478
475
  )
479
- bp_warnings = _check_best_practices(
480
- transformed_config, skill_prefix=_get_skill_prefix()
481
- )
476
+ bp_warnings = _check_best_practices(transformed_config)
482
477
 
483
478
  # Save transformed config
484
479
  result = await self._client.upsert_script_config(
@@ -524,9 +519,7 @@ class ConfigScriptTools:
524
519
  await self._fetch_and_verify_hash(script_id, config_hash, "set")
525
520
 
526
521
  # Pre-check for best-practice issues.
527
- bp_warnings = _check_best_practices(
528
- config_dict, skill_prefix=_get_skill_prefix()
529
- )
522
+ bp_warnings = _check_best_practices(config_dict)
530
523
 
531
524
  # Cross-check literal service and entity references against
532
525
  # the live registries. Soft warnings only — the write still
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ha-mcp-dev
3
- Version: 7.4.1.dev441
3
+ Version: 7.4.1.dev443
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
@@ -254,12 +254,9 @@ An MCP server can create automations, helpers, and dashboards, but it has no opi
254
254
 
255
255
  ### Bundled Skills (built-in)
256
256
 
257
- Skills from `homeassistant-ai/skills` are bundled and served as [MCP resources](https://modelcontextprotocol.io/docs/concepts/resources) via `skill://` URIs. Any MCP client that supports resources can discover them automatically — no manual installation needed.
257
+ Skills from `homeassistant-ai/skills` are bundled and served as [MCP resources](https://modelcontextprotocol.io/docs/concepts/resources) via `skill://` URIs. Any MCP client that supports resources can discover them automatically — no manual installation needed. For tool-only clients, the same skills are also exposed as `ha_list_resources` / `ha_read_resource` tools. Resources are not auto-injected into context — clients must explicitly request them, so idle context cost is just the metadata listing.
258
258
 
259
- | Setting | Default | Description |
260
- |---------|---------|-------------|
261
- | `ENABLE_SKILLS` | `true` | Serve skills as MCP resources. Resources are not auto-injected into context — clients must explicitly request them. |
262
- | `ENABLE_SKILLS_AS_TOOLS` | `true` | Expose skills and doc resources via `list_resources`/`read_resource` tools. Resource-capable clients can set to `false` to reduce tool count. |
259
+ If you want to hide either tool from the catalog, disable it from the web settings UI like any other tool.
263
260
 
264
261
  Skills can still be installed manually for clients that prefer local skill files — see the [skills repo](https://github.com/homeassistant-ai/skills) for instructions.
265
262
 
@@ -344,6 +341,8 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
344
341
  - **[@gcormier](https://github.com/gcormier)** — Windows installer improvements: removed unused variable and fixed terminal closing after install.
345
342
  - **[@ekobres](https://github.com/ekobres)** — Feature flags for `HAMCP_ENABLE_FILESYSTEM_TOOLS` and `HAMCP_ENABLE_CUSTOM_COMPONENT_INTEGRATION` in the add-on config, with beta tagging in source and docs.
346
343
  - **[@w3z315](https://github.com/w3z315)** — Financial support via [GitHub Sponsors](https://github.com/sponsors/julienld). Thank you! ☕
344
+ - **[@griffinmartin](https://github.com/griffinmartin)** — Added OpenCode (by Anomaly) as a selectable AI client in the setup wizard, with both stdio and streamable HTTP support.
345
+ - **[@hhopke](https://github.com/hhopke)** — Fixed addon API calls to route through HA Core ingress proxy instead of direct container connections, fixing `ha_manage_addon` proxy mode on addon installs.
347
346
 
348
347
  ---
349
348