ha-mcp-dev 7.5.0.dev593__tar.gz → 7.5.0.dev594__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 (124) hide show
  1. {ha_mcp_dev-7.5.0.dev593/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.5.0.dev594}/PKG-INFO +2 -2
  2. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/README.md +1 -1
  3. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/pyproject.toml +1 -1
  4. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/policy/value_sources.py +0 -1
  5. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/tools_code.py +115 -115
  6. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/tools_registry.py +9 -9
  7. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594/src/ha_mcp_dev.egg-info}/PKG-INFO +2 -2
  8. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/LICENSE +0 -0
  9. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/MANIFEST.in +0 -0
  10. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/setup.cfg +0 -0
  11. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/__init__.py +0 -0
  12. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/__main__.py +0 -0
  13. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/_pypi_marker +0 -0
  14. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/_version.py +0 -0
  15. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/auth/__init__.py +0 -0
  16. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/auth/consent_form.py +0 -0
  17. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/auth/provider.py +0 -0
  18. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/backup_manager.py +0 -0
  19. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/client/__init__.py +0 -0
  20. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/client/rest_client.py +0 -0
  21. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/client/supervisor_client.py +0 -0
  22. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/client/websocket_client.py +0 -0
  23. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/client/websocket_listener.py +0 -0
  24. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/config.py +0 -0
  25. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/errors.py +0 -0
  26. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/policy/__init__.py +0 -0
  27. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/policy/approval_queue.py +0 -0
  28. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/policy/evaluator.py +0 -0
  29. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/policy/handlers.py +0 -0
  30. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/policy/middleware.py +0 -0
  31. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/policy/model.py +0 -0
  32. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/policy/persistence.py +0 -0
  33. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/py.typed +0 -0
  34. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
  35. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
  36. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
  37. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
  38. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/resources/skills-vendor/.github/pull_request_template.md +0 -0
  39. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
  40. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
  41. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
  42. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
  43. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
  44. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
  45. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
  46. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
  47. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
  48. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
  49. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
  50. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
  51. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
  52. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
  53. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
  54. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
  55. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/yaml-only-integrations.md +0 -0
  56. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/server.py +0 -0
  57. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/settings_ui.py +0 -0
  58. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/smoke_test.py +0 -0
  59. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/stdio_settings_sidecar.py +0 -0
  60. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/__init__.py +0 -0
  61. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/auto_backup.py +0 -0
  62. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/backup.py +0 -0
  63. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/best_practice_checker.py +0 -0
  64. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/device_control.py +0 -0
  65. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/enhanced.py +0 -0
  66. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/helpers.py +0 -0
  67. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/reference_validator.py +0 -0
  68. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/registry.py +0 -0
  69. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/smart_search.py +0 -0
  70. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/tools_addons.py +0 -0
  71. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/tools_areas.py +0 -0
  72. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/tools_blueprints.py +0 -0
  73. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/tools_bug_report.py +0 -0
  74. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/tools_calendar.py +0 -0
  75. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/tools_camera.py +0 -0
  76. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/tools_categories.py +0 -0
  77. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/tools_config_automations.py +0 -0
  78. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
  79. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/tools_config_entry_flow.py +0 -0
  80. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
  81. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
  82. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
  83. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/tools_energy.py +0 -0
  84. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/tools_entities.py +0 -0
  85. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/tools_filesystem.py +0 -0
  86. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/tools_groups.py +0 -0
  87. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/tools_hacs.py +0 -0
  88. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/tools_history.py +0 -0
  89. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/tools_integrations.py +0 -0
  90. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/tools_labels.py +0 -0
  91. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/tools_mcp_component.py +0 -0
  92. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/tools_resources.py +0 -0
  93. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/tools_search.py +0 -0
  94. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/tools_service.py +0 -0
  95. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/tools_services.py +0 -0
  96. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/tools_system.py +0 -0
  97. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/tools_todo.py +0 -0
  98. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/tools_traces.py +0 -0
  99. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/tools_updates.py +0 -0
  100. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/tools_utility.py +0 -0
  101. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
  102. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
  103. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/tools_zones.py +0 -0
  104. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/tools/util_helpers.py +0 -0
  105. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/transforms/__init__.py +0 -0
  106. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/transforms/categorized_search.py +0 -0
  107. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
  108. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/utils/__init__.py +0 -0
  109. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/utils/config_hash.py +0 -0
  110. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/utils/data_paths.py +0 -0
  111. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/utils/domain_handlers.py +0 -0
  112. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/utils/fuzzy_search.py +0 -0
  113. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
  114. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/utils/operation_manager.py +0 -0
  115. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/utils/python_sandbox.py +0 -0
  116. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp/utils/usage_logger.py +0 -0
  117. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
  118. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
  119. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
  120. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
  121. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
  122. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/tests/__init__.py +0 -0
  123. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/tests/test_constants.py +0 -0
  124. {ha_mcp_dev-7.5.0.dev593 → ha_mcp_dev-7.5.0.dev594}/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.dev593
3
+ Version: 7.5.0.dev594
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
@@ -193,7 +193,7 @@ Spend less time configuring, more time enjoying your smart home.
193
193
  | **Calendar** | `ha_config_get_calendar_events`, `ha_config_remove_calendar_event`, `ha_config_set_calendar_event` |
194
194
  | **Camera** | `ha_get_camera_image` |
195
195
  | **Dashboards** | `ha_config_delete_dashboard_resource`, `ha_config_delete_dashboard`, `ha_config_get_dashboard`, `ha_config_list_dashboard_resources`, `ha_config_set_dashboard_resource`, `ha_config_set_dashboard` |
196
- | **Device Registry** | `ha_get_device`, `ha_remove_device`, `ha_update_device` |
196
+ | **Device Registry** | `ha_get_device`, `ha_remove_device`, `ha_set_device` |
197
197
  | **Energy** | `ha_manage_energy_prefs` |
198
198
  | **Entity Registry** | `ha_get_entity_exposure`, `ha_get_entity`, `ha_remove_entity`, `ha_set_entity` |
199
199
  | **Files** | `ha_delete_file` *(beta)*, `ha_list_files` *(beta)*, `ha_read_file` *(beta)*, `ha_write_file` *(beta)* |
@@ -163,7 +163,7 @@ Spend less time configuring, more time enjoying your smart home.
163
163
  | **Calendar** | `ha_config_get_calendar_events`, `ha_config_remove_calendar_event`, `ha_config_set_calendar_event` |
164
164
  | **Camera** | `ha_get_camera_image` |
165
165
  | **Dashboards** | `ha_config_delete_dashboard_resource`, `ha_config_delete_dashboard`, `ha_config_get_dashboard`, `ha_config_list_dashboard_resources`, `ha_config_set_dashboard_resource`, `ha_config_set_dashboard` |
166
- | **Device Registry** | `ha_get_device`, `ha_remove_device`, `ha_update_device` |
166
+ | **Device Registry** | `ha_get_device`, `ha_remove_device`, `ha_set_device` |
167
167
  | **Energy** | `ha_manage_energy_prefs` |
168
168
  | **Entity Registry** | `ha_get_entity_exposure`, `ha_get_entity`, `ha_remove_entity`, `ha_set_entity` |
169
169
  | **Files** | `ha_delete_file` *(beta)*, `ha_list_files` *(beta)*, `ha_read_file` *(beta)*, `ha_write_file` *(beta)* |
@@ -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.dev593"
7
+ version = "7.5.0.dev594"
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"
@@ -39,7 +39,6 @@ VALUE_SOURCE_REGISTRY: dict[tuple[str, str], str] = {
39
39
  ("ha_get_entity", "args.entity_id"): "ha_entities",
40
40
  ("ha_get_history", "args.entity_ids"): "ha_entities",
41
41
  ("ha_remove_entity", "args.entity_id"): "ha_entities",
42
- ("ha_update_device", "args.entity_id"): "ha_entities",
43
42
  ("ha_get_entity_exposure", "args.entity_id"): "ha_entities",
44
43
  ("ha_set_integration_enabled", "args.entity_id"): "ha_entities",
45
44
  }
@@ -67,13 +67,15 @@ _saved_tools: dict[str, dict[str, str]] = {}
67
67
  # ``CategorizedSearchTransform`` (excludes pinned tools from category sets
68
68
  # when code mode is on); this set is the defense-in-depth that closes the
69
69
  # inner-call path even if the proxy is reachable some other way.
70
- _BLOCKED_TOOLS = frozenset({
71
- "ha_manage_custom_tool",
72
- "ha_search_tools",
73
- "ha_call_read_tool",
74
- "ha_call_write_tool",
75
- "ha_call_delete_tool",
76
- })
70
+ _BLOCKED_TOOLS = frozenset(
71
+ {
72
+ "ha_manage_custom_tool",
73
+ "ha_search_tools",
74
+ "ha_call_read_tool",
75
+ "ha_call_write_tool",
76
+ "ha_call_delete_tool",
77
+ }
78
+ )
77
79
 
78
80
  # Validation for save_as names
79
81
  _SAVE_NAME_PATTERN = re.compile(r"^[a-zA-Z_][a-zA-Z0-9_]{0,63}$")
@@ -119,83 +121,87 @@ _API_POST_BLOCKED_PREFIXES: tuple[tuple[str, str, str], ...] = (
119
121
  # listening for ``state_changed`` / ``automation_reloaded`` / etc. without
120
122
  # the real subsystem ever having fired. Custom event types stay allowed —
121
123
  # only the names HA Core itself emits are blocked.
122
- _BLOCKED_HA_INTERNAL_EVENTS: frozenset[str] = frozenset({
123
- "state_changed",
124
- "service_registered",
125
- "service_removed",
126
- "service_executed",
127
- "automation_reloaded",
128
- "script_started",
129
- "script_finished",
130
- "homeassistant_start",
131
- "homeassistant_started",
132
- "homeassistant_stop",
133
- "homeassistant_close",
134
- "homeassistant_final_write",
135
- "core_config_updated",
136
- "device_registry_updated",
137
- "entity_registry_updated",
138
- "area_registry_updated",
139
- "category_registry_updated",
140
- "floor_registry_updated",
141
- "label_registry_updated",
142
- # ``logbook_entry`` is the documented logbook write API — the Logbook
143
- # integration consumes it to render rows. Sandbox code firing this
144
- # event would inject attacker-fabricated rows directly into the
145
- # user's primary investigation tool, which is a data-integrity issue.
146
- "logbook_entry",
147
- "lovelace_updated",
148
- "panels_updated",
149
- "themes_updated",
150
- "component_loaded",
151
- "recorder_5min_statistics_generated",
152
- "recorder_hourly_statistics_generated",
153
- })
124
+ _BLOCKED_HA_INTERNAL_EVENTS: frozenset[str] = frozenset(
125
+ {
126
+ "state_changed",
127
+ "service_registered",
128
+ "service_removed",
129
+ "service_executed",
130
+ "automation_reloaded",
131
+ "script_started",
132
+ "script_finished",
133
+ "homeassistant_start",
134
+ "homeassistant_started",
135
+ "homeassistant_stop",
136
+ "homeassistant_close",
137
+ "homeassistant_final_write",
138
+ "core_config_updated",
139
+ "device_registry_updated",
140
+ "entity_registry_updated",
141
+ "area_registry_updated",
142
+ "category_registry_updated",
143
+ "floor_registry_updated",
144
+ "label_registry_updated",
145
+ # ``logbook_entry`` is the documented logbook write API the Logbook
146
+ # integration consumes it to render rows. Sandbox code firing this
147
+ # event would inject attacker-fabricated rows directly into the
148
+ # user's primary investigation tool, which is a data-integrity issue.
149
+ "logbook_entry",
150
+ "lovelace_updated",
151
+ "panels_updated",
152
+ "themes_updated",
153
+ "component_loaded",
154
+ "recorder_5min_statistics_generated",
155
+ "recorder_hourly_statistics_generated",
156
+ }
157
+ )
154
158
 
155
159
  # WebSocket commands the sandbox must not send. Each one either changes
156
160
  # persistent state in a way that bypasses a wrapping tool's validation
157
161
  # (lovelace, registry mutations) or has no sandbox-appropriate use case
158
162
  # at all (``config/core/update`` rewrites the HA installation's location/
159
163
  # timezone/currency/lat-long).
160
- _BLOCKED_WS_COMMANDS: frozenset[str] = frozenset({
161
- "config/core/update",
162
- "lovelace/config/save",
163
- "lovelace/dashboards/create",
164
- "lovelace/dashboards/delete",
165
- "lovelace/dashboards/update",
166
- "config/area_registry/delete",
167
- "config/area_registry/disable",
168
- "config/area_registry/update",
169
- "config/device_registry/delete",
170
- "config/device_registry/disable",
171
- "config/device_registry/update",
172
- # Device registry deletion is registered as ``remove_config_entry`` on
173
- # HA Core, not ``delete`` see ``tools_registry.py:753`` for the
174
- # actually-emitted command. ``ha_remove_device`` wraps it; raw
175
- # ``ws_send`` would skip those checks.
176
- "config/device_registry/remove_config_entry",
177
- "config/entity_registry/delete",
178
- "config/entity_registry/disable",
179
- "config/entity_registry/update",
180
- # Entity registry deletion is registered as ``remove`` on HA Core,
181
- # not ``delete`` see ``tools_entities.py:1130`` for the
182
- # actually-emitted command. ``ha_remove_entity`` wraps it.
183
- "config/entity_registry/remove",
184
- # Floor / label / category registries follow the same rationale as
185
- # area / device / entity above: each has a wrapping MCP tool
186
- # (``ha_set_area_or_floor``, ``ha_config_set_label``,
187
- # ``ha_config_set_category``) that performs invariant checks the
188
- # raw WS command skips.
189
- "config/floor_registry/create",
190
- "config/floor_registry/delete",
191
- "config/floor_registry/update",
192
- "config/label_registry/create",
193
- "config/label_registry/delete",
194
- "config/label_registry/update",
195
- "config/category_registry/create",
196
- "config/category_registry/delete",
197
- "config/category_registry/update",
198
- })
164
+ _BLOCKED_WS_COMMANDS: frozenset[str] = frozenset(
165
+ {
166
+ "config/core/update",
167
+ "lovelace/config/save",
168
+ "lovelace/dashboards/create",
169
+ "lovelace/dashboards/delete",
170
+ "lovelace/dashboards/update",
171
+ "config/area_registry/delete",
172
+ "config/area_registry/disable",
173
+ "config/area_registry/update",
174
+ "config/device_registry/delete",
175
+ "config/device_registry/disable",
176
+ "config/device_registry/update",
177
+ # Device registry deletion is registered as ``remove_config_entry`` on
178
+ # HA Core, not ``delete`` see ``tools_registry.py:753`` for the
179
+ # actually-emitted command. ``ha_remove_device`` wraps it; raw
180
+ # ``ws_send`` would skip those checks.
181
+ "config/device_registry/remove_config_entry",
182
+ "config/entity_registry/delete",
183
+ "config/entity_registry/disable",
184
+ "config/entity_registry/update",
185
+ # Entity registry deletion is registered as ``remove`` on HA Core,
186
+ # not ``delete`` — see ``tools_entities.py:1130`` for the
187
+ # actually-emitted command. ``ha_remove_entity`` wraps it.
188
+ "config/entity_registry/remove",
189
+ # Floor / label / category registries follow the same rationale as
190
+ # area / device / entity above: each has a wrapping MCP tool
191
+ # (``ha_set_area_or_floor``, ``ha_config_set_label``,
192
+ # ``ha_config_set_category``) that performs invariant checks the
193
+ # raw WS command skips.
194
+ "config/floor_registry/create",
195
+ "config/floor_registry/delete",
196
+ "config/floor_registry/update",
197
+ "config/label_registry/create",
198
+ "config/label_registry/delete",
199
+ "config/label_registry/update",
200
+ "config/category_registry/create",
201
+ "config/category_registry/delete",
202
+ "config/category_registry/update",
203
+ }
204
+ )
199
205
 
200
206
 
201
207
  def _classify_sandbox_error(exc: Exception) -> tuple[ErrorCode, str, list[str]]:
@@ -297,10 +303,8 @@ def _classify_sandbox_error(exc: Exception) -> tuple[ErrorCode, str, list[str]]:
297
303
  f"Exception type: {exc_type}",
298
304
  "Some Python builtins behave differently in Monty (e.g. "
299
305
  "next() requires an iterator, not a list).",
300
- "Check the values you're passing to api_get/api_post/"
301
- "ws_send/call_tool.",
302
- "Use 'await' before any call to api_get/api_post/ws_send/"
303
- "call_tool.",
306
+ "Check the values you're passing to api_get/api_post/ws_send/call_tool.",
307
+ "Use 'await' before any call to api_get/api_post/ws_send/call_tool.",
304
308
  ],
305
309
  )
306
310
 
@@ -317,7 +321,7 @@ def _check_api_post_blocked(normalized: str) -> str | None:
317
321
  if normalized.startswith(prefix) or normalized == prefix.rstrip("/"):
318
322
  return f"{what} are blocked from the sandbox; {alternative}."
319
323
  if normalized.startswith("events/"):
320
- event_name = normalized[len("events/"):]
324
+ event_name = normalized[len("events/") :]
321
325
  if event_name in _BLOCKED_HA_INTERNAL_EVENTS:
322
326
  return (
323
327
  f"Firing HA-internal event {event_name!r} from the sandbox "
@@ -327,6 +331,7 @@ def _check_api_post_blocked(normalized: str) -> str | None:
327
331
  )
328
332
  return None
329
333
 
334
+
330
335
  # Cap on the number of saved tools to prevent runaway growth. A buggy
331
336
  # LLM loop could otherwise fill the on-disk file with unique save_as
332
337
  # names. Enforced both at load (truncate-with-warning) and at save
@@ -444,9 +449,7 @@ def _load_saved_tools(path_str: str) -> dict[str, dict[str, str]]:
444
449
  valid: dict[str, dict[str, str]] = {}
445
450
  for name, info in tools_raw.items():
446
451
  if not (isinstance(name, str) and _SAVE_NAME_PATTERN.match(name)):
447
- logger.warning(
448
- "Skipping saved tool with invalid name %r in %s", name, path
449
- )
452
+ logger.warning("Skipping saved tool with invalid name %r in %s", name, path)
450
453
  continue
451
454
  if not isinstance(info, dict):
452
455
  logger.warning(
@@ -477,9 +480,7 @@ def _load_saved_tools(path_str: str) -> dict[str, dict[str, str]]:
477
480
  return valid
478
481
 
479
482
 
480
- def _save_saved_tools(
481
- path_str: str, tools: dict[str, dict[str, str]]
482
- ) -> bool:
483
+ def _save_saved_tools(path_str: str, tools: dict[str, dict[str, str]]) -> bool:
483
484
  """Persist the saved-tools cache to a JSON file atomically.
484
485
 
485
486
  Returns ``True`` if persistence succeeded (or was disabled because
@@ -594,9 +595,7 @@ def _extract_tool_result(result: Any) -> Any:
594
595
  except (json.JSONDecodeError, TypeError):
595
596
  payload = combined
596
597
  if is_error:
597
- message = (
598
- payload if isinstance(payload, str) else json.dumps(payload)
599
- )
598
+ message = payload if isinstance(payload, str) else json.dumps(payload)
600
599
  return {"error": message}
601
600
  return payload
602
601
 
@@ -690,9 +689,7 @@ async def _run_sandboxed_code(
690
689
  )
691
690
  first_slash = endpoint.find("/")
692
691
  userinfo_marker = endpoint.find("@")
693
- if userinfo_marker >= 0 and (
694
- first_slash < 0 or userinfo_marker < first_slash
695
- ):
692
+ if userinfo_marker >= 0 and (first_slash < 0 or userinfo_marker < first_slash):
696
693
  raise ValueError("endpoint must not contain userinfo")
697
694
  ep = endpoint.lstrip("/")
698
695
  if ep.startswith("api/"):
@@ -716,7 +713,9 @@ async def _run_sandboxed_code(
716
713
  nonlocal call_count
717
714
  call_count += 1
718
715
  if call_count > settings.code_mode_max_invocations:
719
- return {"error": f"API call limit exceeded ({settings.code_mode_max_invocations})"}
716
+ return {
717
+ "error": f"API call limit exceeded ({settings.code_mode_max_invocations})"
718
+ }
720
719
  try:
721
720
  normalized = _normalize_endpoint(endpoint)
722
721
  except ValueError as exc:
@@ -737,7 +736,9 @@ async def _run_sandboxed_code(
737
736
  nonlocal call_count
738
737
  call_count += 1
739
738
  if call_count > settings.code_mode_max_invocations:
740
- return {"error": f"API call limit exceeded ({settings.code_mode_max_invocations})"}
739
+ return {
740
+ "error": f"API call limit exceeded ({settings.code_mode_max_invocations})"
741
+ }
741
742
  try:
742
743
  normalized = _normalize_endpoint(endpoint)
743
744
  except ValueError as exc:
@@ -770,7 +771,9 @@ async def _run_sandboxed_code(
770
771
  post_kwargs: dict[str, Any] = {}
771
772
  if data is not None:
772
773
  post_kwargs["json"] = data
773
- response = await client.httpx_client.request("POST", normalized, **post_kwargs)
774
+ response = await client.httpx_client.request(
775
+ "POST", normalized, **post_kwargs
776
+ )
774
777
  try:
775
778
  return response.json()
776
779
  except json.JSONDecodeError:
@@ -800,7 +803,9 @@ async def _run_sandboxed_code(
800
803
  nonlocal call_count
801
804
  call_count += 1
802
805
  if call_count > settings.code_mode_max_invocations:
803
- return {"error": f"WebSocket call limit exceeded ({settings.code_mode_max_invocations})"}
806
+ return {
807
+ "error": f"WebSocket call limit exceeded ({settings.code_mode_max_invocations})"
808
+ }
804
809
  if not isinstance(message, dict):
805
810
  return {"error": "ws_send(message) requires a dict with a 'type' field"}
806
811
  msg_type = message.get("type")
@@ -813,7 +818,7 @@ async def _run_sandboxed_code(
813
818
  f"WebSocket command {msg_type!r} is blocked from the "
814
819
  "sandbox. Use the corresponding wrapping tool via "
815
820
  "call_tool (e.g. ha_config_set_dashboard, "
816
- "ha_set_area_or_floor, ha_update_device, ha_set_entity) "
821
+ "ha_set_area_or_floor, ha_set_device, ha_set_entity) "
817
822
  "so validation runs."
818
823
  )
819
824
  }
@@ -821,9 +826,7 @@ async def _run_sandboxed_code(
821
826
  try:
822
827
  return await client.send_websocket_message(message)
823
828
  except Exception as exc:
824
- logger.warning(
825
- "ws_send(type=%r) failed", msg_type, exc_info=True
826
- )
829
+ logger.warning("ws_send(type=%r) failed", msg_type, exc_info=True)
827
830
  return {"error": str(exc)[:200]}
828
831
 
829
832
  async def _call_tool(tool_name: str, arguments: dict[str, Any]) -> Any:
@@ -855,9 +858,7 @@ async def _run_sandboxed_code(
855
858
  except (json.JSONDecodeError, TypeError):
856
859
  return _sandbox_error(ErrorCode.INTERNAL_ERROR, str(te))
857
860
  except Exception as exc:
858
- logger.warning(
859
- "call_tool(%r) failed", tool_name, exc_info=True
860
- )
861
+ logger.warning("call_tool(%r) failed", tool_name, exc_info=True)
861
862
  return _sandbox_error(
862
863
  ErrorCode.INTERNAL_ERROR,
863
864
  f"Tool call failed: {str(exc)[:200]}",
@@ -894,9 +895,7 @@ async def _run_sandboxed_code(
894
895
  # entry the LLM already saw "deleted").
895
896
  previous = _saved_tools[name]
896
897
  del _saved_tools[name]
897
- if not _save_saved_tools(
898
- settings.code_mode_saved_tools_path, _saved_tools
899
- ):
898
+ if not _save_saved_tools(settings.code_mode_saved_tools_path, _saved_tools):
900
899
  _saved_tools[name] = previous
901
900
  return {
902
901
  "error": (
@@ -1077,7 +1076,8 @@ def register_code_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
1077
1076
  # ``save_as`` and ``justification`` are modifiers for the ``code``
1078
1077
  # mode and don't count as a "mode" on their own.
1079
1078
  modes_active = sum(
1080
- 1 for v in (
1079
+ 1
1080
+ for v in (
1081
1081
  bool(code and code.strip()),
1082
1082
  bool(run_saved is not None),
1083
1083
  bool(list_saved),
@@ -1166,7 +1166,10 @@ def register_code_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
1166
1166
  )
1167
1167
  )
1168
1168
 
1169
- return {"success": True, "data": {"result": result, "saved_tool": run_saved}}
1169
+ return {
1170
+ "success": True,
1171
+ "data": {"result": result, "saved_tool": run_saved},
1172
+ }
1170
1173
 
1171
1174
  # --- Mode: execute code ---
1172
1175
  if not code or not code.strip():
@@ -1247,10 +1250,7 @@ def register_code_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
1247
1250
  }
1248
1251
 
1249
1252
  if save_as:
1250
- if (
1251
- save_as not in _saved_tools
1252
- and len(_saved_tools) >= _MAX_SAVED_TOOLS
1253
- ):
1253
+ if save_as not in _saved_tools and len(_saved_tools) >= _MAX_SAVED_TOOLS:
1254
1254
  raise_tool_error(
1255
1255
  create_error_response(
1256
1256
  ErrorCode.VALIDATION_FAILED,
@@ -593,10 +593,10 @@ def register_registry_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
593
593
 
594
594
  @mcp.tool(
595
595
  tags={"Device Registry"},
596
- annotations={"destructiveHint": True, "title": "Update Device"},
596
+ annotations={"destructiveHint": True, "title": "Set Device"},
597
597
  )
598
598
  @log_tool_usage
599
- async def ha_update_device(
599
+ async def ha_set_device(
600
600
  device_id: Annotated[
601
601
  str,
602
602
  Field(description="Device ID to update"),
@@ -637,7 +637,7 @@ def register_registry_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
637
637
  Device and entity names are independent. To rename entities, use ha_set_entity(new_entity_id=...).
638
638
 
639
639
  Common workflow for full rename:
640
- 1. ha_update_device(device_id="abc", name="Living Room Sensor") # Rename device
640
+ 1. ha_set_device(device_id="abc", name="Living Room Sensor") # Rename device
641
641
  2. ha_set_entity("sensor.old", new_entity_id="sensor.living_room") # Rename entities separately
642
642
 
643
643
  PARAMETERS:
@@ -647,11 +647,11 @@ def register_registry_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
647
647
  - labels: List of labels (replaces existing labels)
648
648
 
649
649
  EXAMPLES:
650
- - Rename device: ha_update_device("abc123", name="Living Room Hub")
651
- - Move to area: ha_update_device("abc123", area_id="living_room")
652
- - Disable device: ha_update_device("abc123", disabled_by="user")
653
- - Enable device: ha_update_device("abc123", disabled_by="")
654
- - Add labels: ha_update_device("abc123", labels=["important", "sensor"])
650
+ - Rename device: ha_set_device("abc123", name="Living Room Hub")
651
+ - Move to area: ha_set_device("abc123", area_id="living_room")
652
+ - Disable device: ha_set_device("abc123", disabled_by="user")
653
+ - Enable device: ha_set_device("abc123", disabled_by="")
654
+ - Add labels: ha_set_device("abc123", labels=["important", "sensor"])
655
655
  """
656
656
  # Parse labels if provided as string
657
657
  parsed_labels = None
@@ -717,7 +717,7 @@ def register_registry_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
717
717
  - Remove orphaned device: ha_remove_device("abc123def456")
718
718
 
719
719
  NOTE: For most use cases, consider disabling the device instead:
720
- ha_update_device(device_id="abc123", disabled_by="user")
720
+ ha_set_device(device_id="abc123", disabled_by="user")
721
721
  """
722
722
  try:
723
723
  # Empty/whitespace device_id would slip past the local-filter
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ha-mcp-dev
3
- Version: 7.5.0.dev593
3
+ Version: 7.5.0.dev594
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
@@ -193,7 +193,7 @@ Spend less time configuring, more time enjoying your smart home.
193
193
  | **Calendar** | `ha_config_get_calendar_events`, `ha_config_remove_calendar_event`, `ha_config_set_calendar_event` |
194
194
  | **Camera** | `ha_get_camera_image` |
195
195
  | **Dashboards** | `ha_config_delete_dashboard_resource`, `ha_config_delete_dashboard`, `ha_config_get_dashboard`, `ha_config_list_dashboard_resources`, `ha_config_set_dashboard_resource`, `ha_config_set_dashboard` |
196
- | **Device Registry** | `ha_get_device`, `ha_remove_device`, `ha_update_device` |
196
+ | **Device Registry** | `ha_get_device`, `ha_remove_device`, `ha_set_device` |
197
197
  | **Energy** | `ha_manage_energy_prefs` |
198
198
  | **Entity Registry** | `ha_get_entity_exposure`, `ha_get_entity`, `ha_remove_entity`, `ha_set_entity` |
199
199
  | **Files** | `ha_delete_file` *(beta)*, `ha_list_files` *(beta)*, `ha_read_file` *(beta)*, `ha_write_file` *(beta)* |