ha-mcp-dev 7.5.0.dev515__tar.gz → 7.5.0.dev517__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 (111) hide show
  1. {ha_mcp_dev-7.5.0.dev515/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.5.0.dev517}/PKG-INFO +3 -3
  2. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/README.md +2 -2
  3. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/pyproject.toml +1 -1
  4. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/tools/tools_config_helpers.py +90 -30
  5. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517/src/ha_mcp_dev.egg-info}/PKG-INFO +3 -3
  6. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/LICENSE +0 -0
  7. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/MANIFEST.in +0 -0
  8. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/setup.cfg +0 -0
  9. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/__init__.py +0 -0
  10. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/__main__.py +0 -0
  11. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/_pypi_marker +0 -0
  12. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/_version.py +0 -0
  13. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/auth/__init__.py +0 -0
  14. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/auth/consent_form.py +0 -0
  15. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/auth/provider.py +0 -0
  16. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/client/__init__.py +0 -0
  17. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/client/rest_client.py +0 -0
  18. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/client/supervisor_client.py +0 -0
  19. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/client/websocket_client.py +0 -0
  20. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/client/websocket_listener.py +0 -0
  21. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/config.py +0 -0
  22. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/errors.py +0 -0
  23. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/py.typed +0 -0
  24. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
  25. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
  26. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
  27. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
  28. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
  29. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
  30. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
  31. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
  32. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
  33. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
  34. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
  35. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
  36. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
  37. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
  38. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
  39. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
  40. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
  41. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
  42. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
  43. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
  44. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/server.py +0 -0
  45. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/settings_ui.py +0 -0
  46. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/smoke_test.py +0 -0
  47. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/tools/__init__.py +0 -0
  48. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/tools/backup.py +0 -0
  49. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/tools/best_practice_checker.py +0 -0
  50. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/tools/device_control.py +0 -0
  51. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/tools/enhanced.py +0 -0
  52. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/tools/helpers.py +0 -0
  53. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/tools/reference_validator.py +0 -0
  54. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/tools/registry.py +0 -0
  55. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/tools/smart_search.py +0 -0
  56. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/tools/tools_addons.py +0 -0
  57. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/tools/tools_areas.py +0 -0
  58. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/tools/tools_blueprints.py +0 -0
  59. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/tools/tools_bug_report.py +0 -0
  60. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/tools/tools_calendar.py +0 -0
  61. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/tools/tools_camera.py +0 -0
  62. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/tools/tools_categories.py +0 -0
  63. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/tools/tools_code.py +0 -0
  64. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/tools/tools_config_automations.py +0 -0
  65. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
  66. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/tools/tools_config_entry_flow.py +0 -0
  67. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
  68. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
  69. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/tools/tools_energy.py +0 -0
  70. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/tools/tools_entities.py +0 -0
  71. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/tools/tools_filesystem.py +0 -0
  72. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/tools/tools_groups.py +0 -0
  73. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/tools/tools_hacs.py +0 -0
  74. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/tools/tools_history.py +0 -0
  75. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/tools/tools_integrations.py +0 -0
  76. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/tools/tools_labels.py +0 -0
  77. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/tools/tools_mcp_component.py +0 -0
  78. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/tools/tools_registry.py +0 -0
  79. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/tools/tools_resources.py +0 -0
  80. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/tools/tools_search.py +0 -0
  81. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/tools/tools_service.py +0 -0
  82. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/tools/tools_services.py +0 -0
  83. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/tools/tools_system.py +0 -0
  84. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/tools/tools_todo.py +0 -0
  85. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/tools/tools_traces.py +0 -0
  86. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/tools/tools_updates.py +0 -0
  87. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/tools/tools_utility.py +0 -0
  88. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
  89. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
  90. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/tools/tools_zones.py +0 -0
  91. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/tools/util_helpers.py +0 -0
  92. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/transforms/__init__.py +0 -0
  93. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/transforms/categorized_search.py +0 -0
  94. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
  95. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/utils/__init__.py +0 -0
  96. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/utils/config_hash.py +0 -0
  97. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/utils/data_paths.py +0 -0
  98. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/utils/domain_handlers.py +0 -0
  99. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/utils/fuzzy_search.py +0 -0
  100. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
  101. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/utils/operation_manager.py +0 -0
  102. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/utils/python_sandbox.py +0 -0
  103. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp/utils/usage_logger.py +0 -0
  104. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
  105. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
  106. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
  107. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
  108. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
  109. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/tests/__init__.py +0 -0
  110. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/tests/test_constants.py +0 -0
  111. {ha_mcp_dev-7.5.0.dev515 → ha_mcp_dev-7.5.0.dev517}/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.5.0.dev515
3
+ Version: 7.5.0.dev517
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
@@ -256,9 +256,9 @@ An MCP server can create automations, helpers, and dashboards, but it has no opi
256
256
 
257
257
  ### Bundled Skills (built-in)
258
258
 
259
- 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.
259
+ 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 (claude.ai, etc.), the same skills are reachable through the polymorphic `ha_get_skill_guide` tool — call it with no args to list bundled skills, with a `skill` arg to list its files, or with `skill` + `file` to read content. Resources are not auto-injected into context — clients must explicitly request them, so idle context cost is just the metadata listing.
260
260
 
261
- If you want to hide either tool from the catalog, disable it from the web settings UI like any other tool.
261
+ `ha_get_skill_guide` is mandatory-pinned: the catalog always exposes it so tool-only clients never see a silently missing skill surface.
262
262
 
263
263
  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.
264
264
 
@@ -226,9 +226,9 @@ An MCP server can create automations, helpers, and dashboards, but it has no opi
226
226
 
227
227
  ### Bundled Skills (built-in)
228
228
 
229
- 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
+ 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 (claude.ai, etc.), the same skills are reachable through the polymorphic `ha_get_skill_guide` tool — call it with no args to list bundled skills, with a `skill` arg to list its files, or with `skill` + `file` to read content. Resources are not auto-injected into context — clients must explicitly request them, so idle context cost is just the metadata listing.
230
230
 
231
- If you want to hide either tool from the catalog, disable it from the web settings UI like any other tool.
231
+ `ha_get_skill_guide` is mandatory-pinned: the catalog always exposes it so tool-only clients never see a silently missing skill surface.
232
232
 
233
233
  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.
234
234
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ha-mcp-dev"
7
- version = "7.5.0.dev515"
7
+ version = "7.5.0.dev517"
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"
@@ -867,6 +867,69 @@ def _validate_numeric_range(
867
867
  )
868
868
 
869
869
 
870
+ def _validate_initial_in_options(
871
+ options: Any, initial: Any, helper_type: str = "input_select"
872
+ ) -> None:
873
+ """Reject ``initial`` values not in ``options``.
874
+
875
+ Called from both create and update branches with the resolved values —
876
+ caller-supplied on create, merged with the existing config on update.
877
+ ``initial=None`` is the unset case and passes through. The
878
+ ``isinstance(options, list)`` early-return mirrors the defensive shape
879
+ check in ``_validate_input_select_options`` below — both validators are
880
+ invariant gates, not type contracts; a future non-list caller is
881
+ silently skipped rather than raising a confusing ``TypeError`` on
882
+ ``initial not in options``.
883
+ """
884
+ if not isinstance(options, list) or initial is None:
885
+ return
886
+ if initial not in options:
887
+ raise_tool_error(
888
+ create_error_response(
889
+ ErrorCode.VALIDATION_INVALID_PARAMETER,
890
+ f"initial={initial!r} must be one of options "
891
+ f"{options!r} for {helper_type}.",
892
+ context=_simple_helper_error_context(
893
+ helper_type,
894
+ initial=initial,
895
+ options=options,
896
+ ),
897
+ suggestions=[
898
+ "Pick an `initial` value that's in `options`.",
899
+ "Or omit `initial` to use the default or existing value.",
900
+ ],
901
+ )
902
+ )
903
+
904
+
905
+ def _validate_datetime_has_date_or_time(
906
+ has_date: bool | None, has_time: bool | None
907
+ ) -> None:
908
+ """Reject ``input_datetime`` payloads where both components are False.
909
+
910
+ Treats ``None`` as "not constrained" — only the explicit (False, False)
911
+ case is flagged, since that's what reaches HA as the broken-entity
912
+ payload. Both the create and update branches call this with the
913
+ resolved-after-merge ``has_date`` / ``has_time`` pair.
914
+ """
915
+ if has_date is False and has_time is False:
916
+ raise_tool_error(
917
+ create_error_response(
918
+ ErrorCode.VALIDATION_INVALID_PARAMETER,
919
+ "At least one of has_date or has_time must be True for input_datetime",
920
+ context=_simple_helper_error_context(
921
+ "input_datetime",
922
+ has_date=has_date,
923
+ has_time=has_time,
924
+ ),
925
+ suggestions=[
926
+ "Set has_date=True to keep the date component.",
927
+ "Set has_time=True to keep the time component.",
928
+ ],
929
+ )
930
+ )
931
+
932
+
870
933
  def _validate_input_select_options(options: Any) -> None:
871
934
  """Reject input_select option lists containing duplicates (Bug 17, issue #1150).
872
935
 
@@ -2398,28 +2461,12 @@ def register_config_helper_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
2398
2461
  )
2399
2462
  )
2400
2463
  message["options"] = options
2401
- # Bug 4a (issue #1150): if `initial` was passed but isn't
2402
- # one of the options, reject explicitly instead of silently
2403
- # dropping. The previous `if initial and initial in options`
2404
- # check stripped invalid initials with `success: true`.
2464
+ # If `initial` was passed but isn't one of the options,
2465
+ # reject explicitly instead of silently dropping. Shared
2466
+ # with the update branch via the helper so the same
2467
+ # invariant fires on both code paths.
2468
+ _validate_initial_in_options(options, initial)
2405
2469
  if initial is not None:
2406
- if initial not in options:
2407
- raise_tool_error(
2408
- create_error_response(
2409
- ErrorCode.VALIDATION_INVALID_PARAMETER,
2410
- f"initial={initial!r} must be one of options "
2411
- f"{options!r} for input_select.",
2412
- context=_simple_helper_error_context(
2413
- helper_type,
2414
- initial=initial,
2415
- options=options,
2416
- ),
2417
- suggestions=[
2418
- "Pick an `initial` value that's in `options`.",
2419
- "Or omit `initial` so the entity starts unset.",
2420
- ],
2421
- )
2422
- )
2423
2470
  message["initial"] = initial
2424
2471
 
2425
2472
  elif helper_type == "input_number":
@@ -2477,15 +2524,12 @@ def register_config_helper_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
2477
2524
  message["has_date"] = has_date
2478
2525
  message["has_time"] = has_time
2479
2526
 
2480
- # Validate that at least one is True
2481
- if not message["has_date"] and not message["has_time"]:
2482
- raise_tool_error(
2483
- create_error_response(
2484
- ErrorCode.VALIDATION_INVALID_PARAMETER,
2485
- "At least one of has_date or has_time must be True for input_datetime",
2486
- context=_simple_helper_error_context(helper_type),
2487
- )
2488
- )
2527
+ # Validate that at least one is True — shared with the
2528
+ # update branch via the helper so the same invariant
2529
+ # fires on both code paths.
2530
+ _validate_datetime_has_date_or_time(
2531
+ message["has_date"], message["has_time"]
2532
+ )
2489
2533
 
2490
2534
  if initial is not None:
2491
2535
  message["initial"] = initial
@@ -3048,6 +3092,14 @@ def register_config_helper_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
3048
3092
  if initial is not None
3049
3093
  else existing.get("initial")
3050
3094
  )
3095
+ # Parity with the create-branch guard. Resolves to
3096
+ # (new options, new initial) / (new options, old
3097
+ # initial) / (old options, new initial) — any
3098
+ # combination that excludes initial from the final
3099
+ # options list is caught.
3100
+ _validate_initial_in_options(
3101
+ update_msg["options"], initial_val
3102
+ )
3051
3103
  if initial_val is not None:
3052
3104
  update_msg["initial"] = initial_val
3053
3105
 
@@ -3140,6 +3192,14 @@ def register_config_helper_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
3140
3192
  if has_time is not None
3141
3193
  else existing.get("has_time", False)
3142
3194
  )
3195
+ # Parity with the create-branch guard. A merge
3196
+ # that resolves to (False, False) — caller
3197
+ # disabling the one component the existing entity
3198
+ # had — would otherwise write a broken-entity
3199
+ # payload and surface HA's cryptic generic error.
3200
+ _validate_datetime_has_date_or_time(
3201
+ update_msg["has_date"], update_msg["has_time"]
3202
+ )
3143
3203
  initial_val = (
3144
3204
  initial
3145
3205
  if initial is not None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ha-mcp-dev
3
- Version: 7.5.0.dev515
3
+ Version: 7.5.0.dev517
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
@@ -256,9 +256,9 @@ An MCP server can create automations, helpers, and dashboards, but it has no opi
256
256
 
257
257
  ### Bundled Skills (built-in)
258
258
 
259
- 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.
259
+ 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 (claude.ai, etc.), the same skills are reachable through the polymorphic `ha_get_skill_guide` tool — call it with no args to list bundled skills, with a `skill` arg to list its files, or with `skill` + `file` to read content. Resources are not auto-injected into context — clients must explicitly request them, so idle context cost is just the metadata listing.
260
260
 
261
- If you want to hide either tool from the catalog, disable it from the web settings UI like any other tool.
261
+ `ha_get_skill_guide` is mandatory-pinned: the catalog always exposes it so tool-only clients never see a silently missing skill surface.
262
262
 
263
263
  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.
264
264