ha-mcp-dev 7.6.0.dev662__tar.gz → 7.6.0.dev664__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 (140) hide show
  1. {ha_mcp_dev-7.6.0.dev662/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.6.0.dev664}/PKG-INFO +1 -1
  2. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/pyproject.toml +1 -1
  3. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/__main__.py +86 -1
  4. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/tools/tools_utility.py +36 -17
  5. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
  6. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/LICENSE +0 -0
  7. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/MANIFEST.in +0 -0
  8. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/README.md +0 -0
  9. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/setup.cfg +0 -0
  10. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/__init__.py +0 -0
  11. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/_pypi_marker +0 -0
  12. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/_version.py +0 -0
  13. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/auth/__init__.py +0 -0
  14. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/auth/consent_form.py +0 -0
  15. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/auth/provider.py +0 -0
  16. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/backup_manager.py +0 -0
  17. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/client/__init__.py +0 -0
  18. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/client/rest_client.py +0 -0
  19. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/client/supervisor_client.py +0 -0
  20. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/client/websocket_client.py +0 -0
  21. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/client/websocket_listener.py +0 -0
  22. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/config.py +0 -0
  23. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/dashboard_screenshot/__init__.py +0 -0
  24. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/dashboard_screenshot/capture.py +0 -0
  25. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/dashboard_screenshot/provision.py +0 -0
  26. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/errors.py +0 -0
  27. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/policy/__init__.py +0 -0
  28. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/policy/approval_queue.py +0 -0
  29. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/policy/evaluator.py +0 -0
  30. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/policy/handlers.py +0 -0
  31. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/policy/middleware.py +0 -0
  32. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/policy/model.py +0 -0
  33. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/policy/persistence.py +0 -0
  34. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/policy/value_sources.py +0 -0
  35. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/py.typed +0 -0
  36. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
  37. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
  38. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
  39. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
  40. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/resources/skills-vendor/.github/pull_request_template.md +0 -0
  41. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
  42. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
  43. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
  44. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
  45. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
  46. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
  47. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
  48. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
  49. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
  50. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
  51. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
  52. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
  53. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
  54. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
  55. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
  56. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
  57. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/yaml-only-integrations.md +0 -0
  58. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/server.py +0 -0
  59. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/settings.css +0 -0
  60. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/settings.js +0 -0
  61. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/settings_ui.py +0 -0
  62. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/smoke_test.py +0 -0
  63. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/stdio_settings_sidecar.py +0 -0
  64. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/tools/__init__.py +0 -0
  65. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/tools/auto_backup.py +0 -0
  66. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/tools/backup.py +0 -0
  67. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/tools/best_practice_checker.py +0 -0
  68. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/tools/device_control.py +0 -0
  69. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/tools/enhanced.py +0 -0
  70. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/tools/helpers.py +0 -0
  71. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/tools/reference_validator.py +0 -0
  72. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/tools/registry.py +0 -0
  73. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/tools/smart_search/__init__.py +0 -0
  74. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/tools/smart_search/_base.py +0 -0
  75. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/tools/smart_search/_config.py +0 -0
  76. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/tools/smart_search/_deep.py +0 -0
  77. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/tools/smart_search/_entities.py +0 -0
  78. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/tools/smart_search/_fetch.py +0 -0
  79. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/tools/smart_search/_overview.py +0 -0
  80. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/tools/smart_search/_scenes.py +0 -0
  81. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/tools/smart_search/_scoring.py +0 -0
  82. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/tools/tools_addons.py +0 -0
  83. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/tools/tools_areas.py +0 -0
  84. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/tools/tools_blueprints.py +0 -0
  85. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/tools/tools_bug_report.py +0 -0
  86. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/tools/tools_calendar.py +0 -0
  87. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/tools/tools_camera.py +0 -0
  88. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/tools/tools_categories.py +0 -0
  89. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/tools/tools_code.py +0 -0
  90. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/tools/tools_config_automations.py +0 -0
  91. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
  92. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/tools/tools_config_entry_flow.py +0 -0
  93. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
  94. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
  95. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
  96. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/tools/tools_dashboard_screenshot.py +0 -0
  97. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/tools/tools_energy.py +0 -0
  98. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/tools/tools_entities.py +0 -0
  99. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/tools/tools_filesystem.py +0 -0
  100. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/tools/tools_groups.py +0 -0
  101. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/tools/tools_hacs.py +0 -0
  102. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/tools/tools_history.py +0 -0
  103. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/tools/tools_integrations.py +0 -0
  104. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/tools/tools_labels.py +0 -0
  105. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/tools/tools_mcp_component.py +0 -0
  106. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/tools/tools_registry.py +0 -0
  107. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/tools/tools_resources.py +0 -0
  108. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/tools/tools_search.py +0 -0
  109. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/tools/tools_service.py +0 -0
  110. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/tools/tools_services.py +0 -0
  111. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/tools/tools_system.py +0 -0
  112. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/tools/tools_todo.py +0 -0
  113. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/tools/tools_traces.py +0 -0
  114. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/tools/tools_updates.py +0 -0
  115. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
  116. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
  117. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/tools/tools_zones.py +0 -0
  118. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/tools/util_helpers.py +0 -0
  119. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/tools/validation_middleware.py +0 -0
  120. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/transforms/__init__.py +0 -0
  121. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/transforms/categorized_search.py +0 -0
  122. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
  123. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/utils/__init__.py +0 -0
  124. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/utils/config_hash.py +0 -0
  125. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/utils/data_paths.py +0 -0
  126. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/utils/domain_handlers.py +0 -0
  127. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/utils/fuzzy_search.py +0 -0
  128. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
  129. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/utils/operation_manager.py +0 -0
  130. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/utils/python_sandbox.py +0 -0
  131. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/utils/skill_loader.py +0 -0
  132. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp/utils/usage_logger.py +0 -0
  133. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
  134. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
  135. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
  136. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
  137. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
  138. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/tests/__init__.py +0 -0
  139. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/tests/test_constants.py +0 -0
  140. {ha_mcp_dev-7.6.0.dev662 → ha_mcp_dev-7.6.0.dev664}/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.6.0.dev662
3
+ Version: 7.6.0.dev664
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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ha-mcp-dev"
7
- version = "7.6.0.dev662"
7
+ version = "7.6.0.dev664"
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"
@@ -22,6 +22,7 @@ truststore.inject_into_ssl()
22
22
  import asyncio # noqa: E402
23
23
  import copy # noqa: E402
24
24
  import hashlib # noqa: E402
25
+ import ipaddress # noqa: E402
25
26
  import logging # noqa: E402
26
27
  import os # noqa: E402
27
28
  import signal # noqa: E402
@@ -755,10 +756,93 @@ def _get_http_runtime(default_port: int = 8086) -> tuple[str, int, str]:
755
756
  except ValueError:
756
757
  logger.error(f"Invalid MCP_PORT value: {port_str!r}. Must be an integer.")
757
758
  sys.exit(1)
758
- path = os.getenv("MCP_SECRET_PATH", "/mcp")
759
+ path = os.getenv("MCP_SECRET_PATH", DEFAULT_MCP_PATH)
759
760
  return host, port, path
760
761
 
761
762
 
763
+ # Default ``MCP_SECRET_PATH`` value, shared by ``_get_http_runtime`` (the
764
+ # read-from-env fallback) and ``_warn_if_default_path_exposed`` (the
765
+ # hardening-nudge predicate). Single source of truth so the two sites
766
+ # can't drift.
767
+ DEFAULT_MCP_PATH = "/mcp"
768
+
769
+ # Hostname literals (not IP addresses) treated as loopback by
770
+ # ``_is_loopback_host``. IP literals — the whole ``127.0.0.0/8`` block,
771
+ # ``::1``, bracketed forms, zone-suffixed forms, and IPv4-mapped IPv6 — are
772
+ # handled by the ``ipaddress`` parse before this set is consulted.
773
+ _LOOPBACK_HOSTNAMES = frozenset({"localhost", "ip6-localhost", "ip6-loopback"})
774
+
775
+
776
+ def _is_loopback_host(host: str) -> bool:
777
+ """Return True when ``host`` names the local machine only.
778
+
779
+ Accepts IPv6 hosts in bracketed (``[::1]``) or zone-suffixed
780
+ (``::1%eth0``) form, the full ``127.0.0.0/8`` range, IPv4-mapped IPv6
781
+ loopback (``::ffff:127.0.0.1``, which ``is_loopback`` resolves on its
782
+ own), and the names in ``_LOOPBACK_HOSTNAMES``. A value that is neither
783
+ an IP literal nor a known loopback name (a real hostname, or a malformed
784
+ string) is treated as non-loopback.
785
+ """
786
+ try:
787
+ candidate = host.strip("[]").split("%", 1)[0]
788
+ return ipaddress.ip_address(candidate).is_loopback
789
+ except ValueError:
790
+ # Not an IP literal — fall back to the known loopback hostnames.
791
+ return host.lower() in _LOOPBACK_HOSTNAMES
792
+
793
+
794
+ def _is_running_in_container() -> bool:
795
+ """Best-effort detection of containerized execution.
796
+
797
+ Inside a container the server binds ``0.0.0.0`` regardless of how the
798
+ operator restricted host-side exposure (``docker run -p 127.0.0.1:...``),
799
+ so the bind host alone can't tell a loopback-only deployment from a
800
+ LAN-reachable one — the default-path warning would be a false positive
801
+ for every container. Container deployments are hardened through the
802
+ published guidance instead (AGENTS.md -> Docker; the add-on
803
+ auto-generates a secret path).
804
+ """
805
+ # Docker writes /.dockerenv; Podman writes /run/.containerenv.
806
+ if os.path.exists("/.dockerenv") or os.path.exists("/run/.containerenv"):
807
+ return True
808
+ # The Home Assistant add-on runs under the Supervisor.
809
+ return bool(os.getenv("SUPERVISOR_TOKEN"))
810
+
811
+
812
+ def _warn_if_default_path_exposed(host: str, port: int, path: str) -> None:
813
+ """Warn on a direct run that leaves the default path on a LAN bind.
814
+
815
+ Standard-mode HTTP/SSE authenticates by URL-path secrecy (see
816
+ SECURITY.md → Threat Model). The default ``/mcp`` is not the
817
+ high-entropy secret that model assumes once the bind leaves loopback.
818
+
819
+ Fires only for a direct ``ha-mcp-web`` / ``ha-mcp-sse`` start (uvx, pip,
820
+ source) that uses the default path on a non-loopback host. Operators
821
+ silence it the same way they harden — bind ``MCP_HOST=127.0.0.1`` or set
822
+ a high-entropy ``MCP_SECRET_PATH``. Containers are skipped: an
823
+ in-container ``0.0.0.0`` bind says nothing about real exposure, which is
824
+ set by the ``docker -p`` mapping the process can't observe (see
825
+ ``_is_running_in_container``).
826
+ """
827
+ if path != DEFAULT_MCP_PATH:
828
+ return
829
+ if _is_running_in_container():
830
+ return
831
+ if _is_loopback_host(host):
832
+ return
833
+ logger.warning(
834
+ "ha-mcp listening on %s:%s%s with default MCP_SECRET_PATH. "
835
+ "Standard-mode HTTP/SSE authenticates by URL-path secrecy and assumes "
836
+ "a high-entropy MCP_SECRET_PATH for non-loopback binds "
837
+ "(see SECURITY.md → Threat Model). "
838
+ "Either bind loopback (MCP_HOST=127.0.0.1) or set MCP_SECRET_PATH "
839
+ "to a high-entropy value (e.g. /private_<token_urlsafe(16)>).",
840
+ host,
841
+ port,
842
+ path,
843
+ )
844
+
845
+
762
846
  async def _run_http_with_graceful_shutdown(
763
847
  transport: str,
764
848
  host: str,
@@ -861,6 +945,7 @@ def _run_http_server(transport: str, default_port: int = 8086) -> None:
861
945
  from ha_mcp.settings_ui import register_settings_routes
862
946
 
863
947
  host, port, path = _get_http_runtime(default_port)
948
+ _warn_if_default_path_exposed(host, port, path)
864
949
  register_browser_landing(_get_mcp(), path)
865
950
  register_settings_routes(_get_mcp(), _get_server(), secret_path=path)
866
951
  _log_settings_url(host, port, path)
@@ -1099,6 +1099,39 @@ def register_utility_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
1099
1099
  Home Assistant automations, scripts, and configurations. It provides real-time evaluation with
1100
1100
  access to all Home Assistant states, functions, and template variables.
1101
1101
 
1102
+ **When NOT to use this for automation/script logic:**
1103
+ Templates have legitimate uses (notification bodies, dynamic `data.*` values,
1104
+ debugging existing templates), but `condition:` / `trigger:` positions and
1105
+ action service names are better expressed as native HA constructs:
1106
+ native constructs are schema-validated at config load and surface
1107
+ structural errors loudly, whereas equivalent template logic only errors
1108
+ at runtime — and a template that renders a non-truthy value is silently
1109
+ treated as false.
1110
+ Prefer:
1111
+ - `condition: numeric_state` over `{{ states('x') | float > N }}`
1112
+ - `condition: state` over `{{ is_state(...) }}`
1113
+ - `condition: time` / `condition: sun` over `now().hour` / `is_state('sun.sun', ...)`
1114
+ - Native `for:` field on state/numeric_state triggers and state conditions over
1115
+ `{{ now() - X.last_changed > timedelta(...) }}` duration math
1116
+ - `choose` action over templated `service:` / `action:` strings
1117
+ See `ha_get_skill_guide` (best-practices skill) for the full anti-pattern list.
1118
+
1119
+ **When to use (reach for this tool, don't compute it yourself):**
1120
+ Any one-shot question whose answer is DERIVED from current HA state — an
1121
+ average/sum/min/max across sensors, a count of entities matching a
1122
+ condition, a boolean comparison, or a rendered message with live values.
1123
+ One render call beats fetching N states and doing the math yourself, and
1124
+ it is the canonical way to *test* a template before embedding it. This is
1125
+ for one-shot answers and template testing only — NOT for putting templates
1126
+ into automation logic; for `condition:` / `trigger:` positions native
1127
+ constructs win.
1128
+ - "average temperature across the bedroom sensors"
1129
+ -> `{{ ([states('sensor.a'), states('sensor.b')] | map('float', 0) | sum) / 2 }}`
1130
+ - "how many lights are on"
1131
+ -> `{{ states.light | selectattr('state', 'eq', 'on') | list | count }}`
1132
+ NOT for a plain single-entity value ("what's the state of X") — that is
1133
+ `ha_get_state` / `ha_search`; rendering `{{ states('X') }}` there is over-use.
1134
+
1102
1135
  **Parameters:**
1103
1136
  - template: The Jinja2 template string to evaluate
1104
1137
  - timeout: Maximum evaluation time in seconds (default: 3)
@@ -1117,8 +1150,8 @@ def register_utility_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
1117
1150
  **Numeric Operations:**
1118
1151
  ```jinja2
1119
1152
  {{ states('sensor.temperature') | float(0) }} # Convert to float with default
1120
- {{ states('sensor.humidity') | int }} # Convert to integer
1121
- {{ (states('sensor.temp') | float + 5) | round(1) }} # Math operations
1153
+ {{ states('sensor.humidity') | int(0) }} # Convert to integer with default
1154
+ {{ (states('sensor.temp') | float(0) + 5) | round(1) }} # Math operations
1122
1155
  ```
1123
1156
 
1124
1157
  **Time and Date:**
@@ -1163,20 +1196,6 @@ def register_utility_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
1163
1196
  {{ device_id('light.bedroom') }} # Get device ID for entity
1164
1197
  ```
1165
1198
 
1166
- **When NOT to use this for automation/script logic:**
1167
- Templates have legitimate uses (notification bodies, dynamic `data.*` values,
1168
- debugging existing templates), but `condition:` / `trigger:` positions and
1169
- action service names are better expressed as native HA constructs — they
1170
- validate at config load, fail loudly, and avoid silent runtime failures.
1171
- Prefer:
1172
- - `condition: numeric_state` over `{{ states('x') | float > N }}`
1173
- - `condition: state` over `{{ is_state(...) }}`
1174
- - `condition: time` / `condition: sun` over `now().hour` / `is_state('sun.sun', ...)`
1175
- - Native `for:` field on state/numeric_state triggers and state conditions over
1176
- `{{ now() - X.last_changed > timedelta(...) }}` duration math
1177
- - `choose` action over templated `service:` / `action:` strings
1178
- See `ha_get_skill_guide` (best-practices skill) for the full anti-pattern list.
1179
-
1180
1199
  **Common Use Cases (legitimate template positions):**
1181
1200
 
1182
1201
  **Dynamic Service Data:**
@@ -1202,7 +1221,7 @@ def register_utility_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
1202
1221
 
1203
1222
  **Test mathematical operations:**
1204
1223
  ```python
1205
- ha_eval_template("{{ (states('sensor.temperature') | float + 5) | round(1) }}")
1224
+ ha_eval_template("{{ (states('sensor.temperature') | float(0) + 5) | round(1) }}")
1206
1225
  ```
1207
1226
 
1208
1227
  **Test entity counting:**
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ha-mcp-dev
3
- Version: 7.6.0.dev662
3
+ Version: 7.6.0.dev664
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