ha-mcp-dev 7.12.3.dev1895__tar.gz → 7.12.3.dev1901__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.
- {ha_mcp_dev-7.12.3.dev1895/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.12.3.dev1901}/PKG-INFO +1 -1
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/pyproject.toml +1 -9
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/client/rest_client.py +43 -24
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/policy/evaluator.py +30 -33
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/tools/tools_addons.py +177 -72
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/tools/tools_hacs.py +152 -109
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/tools/tools_traces.py +283 -196
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/tools/tools_updates.py +153 -109
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/utils/data_paths.py +62 -57
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/utils/skill_loader.py +22 -16
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/LICENSE +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/MANIFEST.in +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/README.md +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/setup.cfg +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/__init__.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/__main__.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/_pypi_marker +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/_version.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/auth/__init__.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/auth/consent_form.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/auth/provider.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/backup_manager.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/browser_landing.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/client/__init__.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/client/supervisor_client.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/client/websocket_client.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/client/websocket_listener.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/config.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/dashboard_screenshot/__init__.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/dashboard_screenshot/capture.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/dashboard_screenshot/provision.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/errors.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/llm_exposure.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/policy/__init__.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/policy/approval_queue.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/policy/handlers.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/policy/middleware.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/policy/model.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/policy/persistence.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/policy/value_sources.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/py.typed +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/read_only.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/resources/skills-vendor/.github/pull_request_template.md +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/appdaemon.md +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/blueprint-guide.md +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/scenes.md +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/yaml-only-integrations.md +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/server.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/settings_ui/__init__.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/settings_ui/settings.css +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/settings_ui/settings.html +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/settings_ui/settings.js +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/smoke_test.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/stdio_settings_sidecar.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/strict_bps.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/tools/__init__.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/tools/auto_backup.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/tools/backup.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/tools/best_practice_checker.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/tools/component_api.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/tools/config_entry_flow.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/tools/device_control.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/tools/enhanced.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/tools/helpers.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/tools/radio/__init__.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/tools/radio/base.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/tools/radio/matter.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/tools/radio/thread.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/tools/radio/zigbee.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/tools/radio/zwave.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/tools/reference_validator.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/tools/registry.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/tools/smart_search/__init__.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/tools/smart_search/_base.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/tools/smart_search/_config.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/tools/smart_search/_deep.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/tools/smart_search/_entities.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/tools/smart_search/_fetch.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/tools/smart_search/_overview.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/tools/smart_search/_scenes.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/tools/smart_search/_scoring.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/tools/tool_search_hint_middleware.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/tools/tools_areas.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/tools/tools_blueprints.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/tools/tools_bug_report.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/tools/tools_calendar.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/tools/tools_camera.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/tools/tools_categories.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/tools/tools_code.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/tools/tools_config_automations.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/tools/tools_dashboard_screenshot.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/tools/tools_dev.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/tools/tools_energy.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/tools/tools_entities.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/tools/tools_filesystem.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/tools/tools_groups.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/tools/tools_history.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/tools/tools_integrations.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/tools/tools_labels.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/tools/tools_mcp_component.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/tools/tools_radio.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/tools/tools_registry.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/tools/tools_resources.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/tools/tools_search.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/tools/tools_service.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/tools/tools_services.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/tools/tools_system.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/tools/tools_themes.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/tools/tools_todo.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/tools/tools_utility.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/tools/tools_zones.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/tools/util_helpers.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/tools/validation_middleware.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/transforms/__init__.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/transforms/categorized_search.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/transport_security.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/update_check.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/utils/__init__.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/utils/config_hash.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/utils/domain_handlers.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/utils/fuzzy_search.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/utils/operation_manager.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/utils/python_sandbox.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/utils/usage_logger.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/visibility/__init__.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/visibility/model.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/visibility/persistence.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp/visibility/resolver.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/tests/__init__.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/tests/test_constants.py +0 -0
- {ha_mcp_dev-7.12.3.dev1895 → ha_mcp_dev-7.12.3.dev1901}/tests/test_env_manager.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "ha-mcp-dev"
|
|
7
|
-
version = "7.12.3.
|
|
7
|
+
version = "7.12.3.dev1901"
|
|
8
8
|
description = "Home Assistant MCP Server - Complete control of Home Assistant through MCP"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
# Upper bound widened to <3.15 so the package installs on Home Assistant's
|
|
@@ -176,9 +176,7 @@ ignore = [
|
|
|
176
176
|
"packaging/mcpb/generate_manifest.py" = ["C901"]
|
|
177
177
|
"scripts/extract_tools.py" = ["C901"]
|
|
178
178
|
"src/ha_mcp/backup_manager.py" = ["C901"]
|
|
179
|
-
"src/ha_mcp/client/rest_client.py" = ["C901"]
|
|
180
179
|
"src/ha_mcp/config.py" = ["C901"]
|
|
181
|
-
"src/ha_mcp/policy/evaluator.py" = ["C901"]
|
|
182
180
|
"src/ha_mcp/policy/handlers.py" = ["C901"]
|
|
183
181
|
"src/ha_mcp/server.py" = ["C901"]
|
|
184
182
|
"src/ha_mcp/settings_ui/__init__.py" = ["C901"]
|
|
@@ -188,7 +186,6 @@ ignore = [
|
|
|
188
186
|
"src/ha_mcp/tools/radio/zigbee.py" = ["C901"]
|
|
189
187
|
"src/ha_mcp/tools/radio/zwave.py" = ["C901"]
|
|
190
188
|
"src/ha_mcp/tools/reference_validator.py" = ["C901"]
|
|
191
|
-
"src/ha_mcp/tools/tools_addons.py" = ["C901"]
|
|
192
189
|
"src/ha_mcp/tools/tools_areas.py" = ["C901"]
|
|
193
190
|
"src/ha_mcp/tools/tools_code.py" = ["C901"]
|
|
194
191
|
"src/ha_mcp/tools/tools_config_automations.py" = ["C901"]
|
|
@@ -196,18 +193,13 @@ ignore = [
|
|
|
196
193
|
"src/ha_mcp/tools/tools_config_scripts.py" = ["C901"]
|
|
197
194
|
"src/ha_mcp/tools/tools_dev.py" = ["C901"]
|
|
198
195
|
"src/ha_mcp/tools/tools_energy.py" = ["C901"]
|
|
199
|
-
"src/ha_mcp/tools/tools_hacs.py" = ["C901"]
|
|
200
196
|
"src/ha_mcp/tools/tools_history.py" = ["C901"]
|
|
201
197
|
"src/ha_mcp/tools/tools_integrations.py" = ["C901"]
|
|
202
198
|
"src/ha_mcp/tools/tools_system.py" = ["C901"]
|
|
203
|
-
"src/ha_mcp/tools/tools_traces.py" = ["C901"]
|
|
204
|
-
"src/ha_mcp/tools/tools_updates.py" = ["C901"]
|
|
205
199
|
"src/ha_mcp/tools/tools_yaml_config.py" = ["C901"]
|
|
206
200
|
"src/ha_mcp/tools/tools_zones.py" = ["C901"]
|
|
207
201
|
"src/ha_mcp/transforms/categorized_search.py" = ["C901"]
|
|
208
|
-
"src/ha_mcp/utils/data_paths.py" = ["C901"]
|
|
209
202
|
"src/ha_mcp/utils/fuzzy_search.py" = ["C901"]
|
|
210
|
-
"src/ha_mcp/utils/skill_loader.py" = ["C901"]
|
|
211
203
|
"src/ha_mcp/visibility/resolver.py" = ["C901"]
|
|
212
204
|
"tests/src/e2e/conftest.py" = ["C901"]
|
|
213
205
|
"tests/src/e2e/error_handling/test_network_errors.py" = ["C901"]
|
|
@@ -191,6 +191,25 @@ class HomeAssistantClient:
|
|
|
191
191
|
await self.httpx_client.aclose()
|
|
192
192
|
logger.debug("Closed Home Assistant client")
|
|
193
193
|
|
|
194
|
+
@staticmethod
|
|
195
|
+
def _error_message_from_response(
|
|
196
|
+
response: httpx.Response,
|
|
197
|
+
) -> tuple[str, dict[str, Any]]:
|
|
198
|
+
"""Build a human message and structured error data from a 4xx/5xx body.
|
|
199
|
+
|
|
200
|
+
Prefers the JSON ``message`` field; falls back to reason phrase or a
|
|
201
|
+
placeholder when the body is empty or unparseable.
|
|
202
|
+
"""
|
|
203
|
+
try:
|
|
204
|
+
error_data = response.json()
|
|
205
|
+
except Exception:
|
|
206
|
+
error_data = {"message": response.text}
|
|
207
|
+
|
|
208
|
+
message = error_data.get("message")
|
|
209
|
+
if not message or not message.strip():
|
|
210
|
+
message = response.reason_phrase or "<empty body>"
|
|
211
|
+
return message, error_data
|
|
212
|
+
|
|
194
213
|
async def _raw_request(
|
|
195
214
|
self, method: str, endpoint: str, **kwargs: Any
|
|
196
215
|
) -> httpx.Response:
|
|
@@ -216,14 +235,7 @@ class HomeAssistantClient:
|
|
|
216
235
|
raise HomeAssistantAuthError("Invalid authentication token")
|
|
217
236
|
|
|
218
237
|
if response.status_code >= 400:
|
|
219
|
-
|
|
220
|
-
error_data = response.json()
|
|
221
|
-
except Exception:
|
|
222
|
-
error_data = {"message": response.text}
|
|
223
|
-
|
|
224
|
-
message = error_data.get("message")
|
|
225
|
-
if not message or not message.strip():
|
|
226
|
-
message = response.reason_phrase or "<empty body>"
|
|
238
|
+
message, error_data = self._error_message_from_response(response)
|
|
227
239
|
|
|
228
240
|
if (
|
|
229
241
|
response.status_code in _RETRYABLE_STATUS
|
|
@@ -665,6 +677,28 @@ class HomeAssistantClient:
|
|
|
665
677
|
)
|
|
666
678
|
return response.text
|
|
667
679
|
|
|
680
|
+
@staticmethod
|
|
681
|
+
def _supervisor_error_message(text_body: str, reason_phrase: str) -> str:
|
|
682
|
+
"""Extract a human message from a Supervisor 4xx/5xx body.
|
|
683
|
+
|
|
684
|
+
Supervisor returns ``{"result":"error","message":"..."}`` on some paths;
|
|
685
|
+
prefer that message, then fall back to the raw text, reason phrase, or a
|
|
686
|
+
placeholder.
|
|
687
|
+
"""
|
|
688
|
+
message = ""
|
|
689
|
+
try:
|
|
690
|
+
envelope = json.loads(text_body) if text_body else None
|
|
691
|
+
if isinstance(envelope, dict):
|
|
692
|
+
msg = envelope.get("message")
|
|
693
|
+
if isinstance(msg, str) and msg:
|
|
694
|
+
message = msg
|
|
695
|
+
except json.JSONDecodeError:
|
|
696
|
+
# Body wasn't a JSON envelope; fall back to raw text below.
|
|
697
|
+
pass
|
|
698
|
+
if not message:
|
|
699
|
+
message = text_body.strip() or reason_phrase or "<empty body>"
|
|
700
|
+
return message
|
|
701
|
+
|
|
668
702
|
async def _supervisor_logs_get(self, path: str, lines: int | None = None) -> str:
|
|
669
703
|
"""Fetch ``text/plain`` logs from a Supervisor REST endpoint.
|
|
670
704
|
|
|
@@ -751,22 +785,7 @@ class HomeAssistantClient:
|
|
|
751
785
|
)
|
|
752
786
|
if response.status_code >= 400:
|
|
753
787
|
text_body = response.text
|
|
754
|
-
|
|
755
|
-
# some 4xx paths. Try parsing that first so the user sees the
|
|
756
|
-
# human message instead of a JSON blob; then fall back to the
|
|
757
|
-
# text body, then reason_phrase, then a placeholder.
|
|
758
|
-
message = ""
|
|
759
|
-
try:
|
|
760
|
-
envelope = json.loads(text_body) if text_body else None
|
|
761
|
-
if isinstance(envelope, dict):
|
|
762
|
-
msg = envelope.get("message")
|
|
763
|
-
if isinstance(msg, str) and msg:
|
|
764
|
-
message = msg
|
|
765
|
-
except json.JSONDecodeError:
|
|
766
|
-
# Body wasn't a JSON envelope; fall back to raw text below.
|
|
767
|
-
pass
|
|
768
|
-
if not message:
|
|
769
|
-
message = text_body.strip() or response.reason_phrase or "<empty body>"
|
|
788
|
+
message = self._supervisor_error_message(text_body, response.reason_phrase)
|
|
770
789
|
logger.warning(
|
|
771
790
|
"Supervisor returned %s for /%s/logs: %s",
|
|
772
791
|
response.status_code,
|
|
@@ -56,6 +56,33 @@ def _ci(x: Any) -> Any:
|
|
|
56
56
|
return x.lower() if isinstance(x, str) else x
|
|
57
57
|
|
|
58
58
|
|
|
59
|
+
def _contains_matches(val: Any, pv: Any) -> bool:
|
|
60
|
+
if isinstance(val, str) and isinstance(pv, str):
|
|
61
|
+
return pv.lower() in val.lower()
|
|
62
|
+
# Mirror the case-insensitive treatment that ``eq`` / ``in`` /
|
|
63
|
+
# ``not_in`` already apply: a rule listing ``["light.kitchen"]``
|
|
64
|
+
# must match an LLM passing ``"Light.Kitchen"``. Per-element
|
|
65
|
+
# ``_ci`` guards non-string entries so mixed-type collections
|
|
66
|
+
# (e.g. ``[1, "two"]``) keep their natural equality semantics.
|
|
67
|
+
return isinstance(val, (list, tuple, set)) and any(_ci(pv) == _ci(x) for x in val)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _numeric_matches(val: Any, op: str, pv: Any) -> bool:
|
|
71
|
+
try:
|
|
72
|
+
return bool(val > pv) if op == "gt" else bool(val < pv)
|
|
73
|
+
except TypeError:
|
|
74
|
+
# Numeric rule against a non-numeric arg value — log so
|
|
75
|
+
# users can tell their "temperature > 30" rule isn't
|
|
76
|
+
# silently never firing because the arg is a string.
|
|
77
|
+
logger.debug(
|
|
78
|
+
"policy: %s type-mismatch (val=%r pv=%r) — predicate skipped",
|
|
79
|
+
op,
|
|
80
|
+
val,
|
|
81
|
+
pv,
|
|
82
|
+
)
|
|
83
|
+
return False
|
|
84
|
+
|
|
85
|
+
|
|
59
86
|
def _op_matches(val: Any, op: str, pv: Any) -> bool:
|
|
60
87
|
"""Apply one op to one concrete value. Predicate dispatches over
|
|
61
88
|
the candidate values (which may be many for wildcard paths).
|
|
@@ -82,39 +109,9 @@ def _op_matches(val: Any, op: str, pv: Any) -> bool:
|
|
|
82
109
|
and re.search(pv, val, re.IGNORECASE) is not None
|
|
83
110
|
)
|
|
84
111
|
case "contains":
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
# ``not_in`` already apply: a rule listing ``["light.kitchen"]``
|
|
89
|
-
# must match an LLM passing ``"Light.Kitchen"``. Per-element
|
|
90
|
-
# ``_ci`` guards non-string entries so mixed-type collections
|
|
91
|
-
# (e.g. ``[1, "two"]``) keep their natural equality semantics.
|
|
92
|
-
return isinstance(val, (list, tuple, set)) and any(
|
|
93
|
-
_ci(pv) == _ci(x) for x in val
|
|
94
|
-
)
|
|
95
|
-
case "gt":
|
|
96
|
-
try:
|
|
97
|
-
return bool(val > pv)
|
|
98
|
-
except TypeError:
|
|
99
|
-
# Numeric rule against a non-numeric arg value — log so
|
|
100
|
-
# users can tell their "temperature > 30" rule isn't
|
|
101
|
-
# silently never firing because the arg is a string.
|
|
102
|
-
logger.debug(
|
|
103
|
-
"policy: gt type-mismatch (val=%r pv=%r) — predicate skipped",
|
|
104
|
-
val,
|
|
105
|
-
pv,
|
|
106
|
-
)
|
|
107
|
-
return False
|
|
108
|
-
case "lt":
|
|
109
|
-
try:
|
|
110
|
-
return bool(val < pv)
|
|
111
|
-
except TypeError:
|
|
112
|
-
logger.debug(
|
|
113
|
-
"policy: lt type-mismatch (val=%r pv=%r) — predicate skipped",
|
|
114
|
-
val,
|
|
115
|
-
pv,
|
|
116
|
-
)
|
|
117
|
-
return False
|
|
112
|
+
return _contains_matches(val, pv)
|
|
113
|
+
case "gt" | "lt":
|
|
114
|
+
return _numeric_matches(val, op, pv)
|
|
118
115
|
return False
|
|
119
116
|
|
|
120
117
|
|
|
@@ -2273,6 +2273,151 @@ class AddOnTools:
|
|
|
2273
2273
|
)
|
|
2274
2274
|
return await self._execute_repository_action(action, repository.strip())
|
|
2275
2275
|
|
|
2276
|
+
@staticmethod
|
|
2277
|
+
def _reject_action_mode_conflicts(
|
|
2278
|
+
action: str,
|
|
2279
|
+
path: str | None,
|
|
2280
|
+
config_data: dict[str, Any],
|
|
2281
|
+
array_patch: dict[str, Any] | None,
|
|
2282
|
+
) -> None:
|
|
2283
|
+
"""Raise if lifecycle-action mode is combined with another mode's params."""
|
|
2284
|
+
conflicts = []
|
|
2285
|
+
if path is not None:
|
|
2286
|
+
conflicts.append("path")
|
|
2287
|
+
if config_data:
|
|
2288
|
+
conflicts.append("config parameters")
|
|
2289
|
+
if array_patch is not None:
|
|
2290
|
+
conflicts.append("array_patch")
|
|
2291
|
+
if conflicts:
|
|
2292
|
+
raise_tool_error(
|
|
2293
|
+
create_validation_error(
|
|
2294
|
+
f"action='{action}' (lifecycle mode) cannot be combined "
|
|
2295
|
+
f"with {', '.join(conflicts)}. Use one mode at a time.",
|
|
2296
|
+
parameter="action",
|
|
2297
|
+
)
|
|
2298
|
+
)
|
|
2299
|
+
|
|
2300
|
+
def _reject_config_mode_proxy_params(
|
|
2301
|
+
self,
|
|
2302
|
+
*,
|
|
2303
|
+
method: str,
|
|
2304
|
+
body: dict[str, Any] | str | None,
|
|
2305
|
+
debug: bool,
|
|
2306
|
+
port: int | None,
|
|
2307
|
+
offset: int,
|
|
2308
|
+
limit: int | None,
|
|
2309
|
+
websocket: bool,
|
|
2310
|
+
wait_for_close: bool,
|
|
2311
|
+
message_limit: int | None,
|
|
2312
|
+
message_offset: int,
|
|
2313
|
+
summarize: bool,
|
|
2314
|
+
python_transform: str | None,
|
|
2315
|
+
array_patch: dict[str, Any] | None,
|
|
2316
|
+
request_headers: dict[str, str] | None,
|
|
2317
|
+
) -> None:
|
|
2318
|
+
"""Raise if any proxy-mode-only param is set while config mode is active."""
|
|
2319
|
+
proxy_overrides = self._proxy_overrides_basic(
|
|
2320
|
+
method, body, debug, port, offset, limit, websocket
|
|
2321
|
+
) + self._proxy_overrides_ws_and_extra(
|
|
2322
|
+
wait_for_close,
|
|
2323
|
+
message_limit,
|
|
2324
|
+
message_offset,
|
|
2325
|
+
summarize,
|
|
2326
|
+
python_transform,
|
|
2327
|
+
array_patch,
|
|
2328
|
+
request_headers,
|
|
2329
|
+
)
|
|
2330
|
+
if proxy_overrides:
|
|
2331
|
+
raise_tool_error(
|
|
2332
|
+
create_validation_error(
|
|
2333
|
+
f"Proxy-mode parameters cannot be used in config mode: {', '.join(d for _, d in proxy_overrides)}. "
|
|
2334
|
+
"Remove these parameters or switch to proxy mode by providing 'path'.",
|
|
2335
|
+
parameter=proxy_overrides[0][0],
|
|
2336
|
+
)
|
|
2337
|
+
)
|
|
2338
|
+
|
|
2339
|
+
async def _execute_ws_proxy(
|
|
2340
|
+
self,
|
|
2341
|
+
slug: str,
|
|
2342
|
+
path: str,
|
|
2343
|
+
body: dict[str, Any] | str | None,
|
|
2344
|
+
debug: bool,
|
|
2345
|
+
port: int | None,
|
|
2346
|
+
wait_for_close: bool,
|
|
2347
|
+
message_limit: int | None,
|
|
2348
|
+
message_offset: int,
|
|
2349
|
+
summarize: bool,
|
|
2350
|
+
python_transform: str | None,
|
|
2351
|
+
) -> dict[str, Any]:
|
|
2352
|
+
result = await _call_addon_ws(
|
|
2353
|
+
client=self._client,
|
|
2354
|
+
slug=slug,
|
|
2355
|
+
path=path,
|
|
2356
|
+
body=body,
|
|
2357
|
+
timeout=120 if wait_for_close else 10,
|
|
2358
|
+
debug=debug,
|
|
2359
|
+
port=port,
|
|
2360
|
+
wait_for_close=wait_for_close,
|
|
2361
|
+
message_limit=message_limit,
|
|
2362
|
+
message_offset=message_offset,
|
|
2363
|
+
summarize=summarize,
|
|
2364
|
+
python_transform=python_transform,
|
|
2365
|
+
)
|
|
2366
|
+
if not result.get("success"):
|
|
2367
|
+
raise_tool_error(result)
|
|
2368
|
+
return result
|
|
2369
|
+
|
|
2370
|
+
async def _execute_http_proxy(
|
|
2371
|
+
self,
|
|
2372
|
+
*,
|
|
2373
|
+
slug: str,
|
|
2374
|
+
path: str,
|
|
2375
|
+
method: str,
|
|
2376
|
+
body: dict[str, Any] | str | None,
|
|
2377
|
+
debug: bool,
|
|
2378
|
+
port: int | None,
|
|
2379
|
+
offset: int,
|
|
2380
|
+
limit: int | None,
|
|
2381
|
+
python_transform: str | None,
|
|
2382
|
+
request_headers: dict[str, str] | None,
|
|
2383
|
+
message_limit: int | None,
|
|
2384
|
+
message_offset: int,
|
|
2385
|
+
summarize: bool,
|
|
2386
|
+
) -> dict[str, Any]:
|
|
2387
|
+
valid_methods = {"GET", "POST", "PUT", "DELETE", "PATCH"}
|
|
2388
|
+
if method.upper() not in valid_methods:
|
|
2389
|
+
raise_tool_error(
|
|
2390
|
+
create_validation_error(
|
|
2391
|
+
f"Invalid HTTP method: {method}. Must be one of: {', '.join(sorted(valid_methods))}",
|
|
2392
|
+
parameter="method",
|
|
2393
|
+
)
|
|
2394
|
+
)
|
|
2395
|
+
if message_limit is not None or message_offset != 0 or not summarize:
|
|
2396
|
+
raise_tool_error(
|
|
2397
|
+
create_validation_error(
|
|
2398
|
+
"message_limit / message_offset / summarize apply only to "
|
|
2399
|
+
"WebSocket mode. Set websocket=True or remove them.",
|
|
2400
|
+
parameter="message_limit",
|
|
2401
|
+
)
|
|
2402
|
+
)
|
|
2403
|
+
|
|
2404
|
+
result = await _call_addon_api(
|
|
2405
|
+
client=self._client,
|
|
2406
|
+
slug=slug,
|
|
2407
|
+
path=path,
|
|
2408
|
+
method=method,
|
|
2409
|
+
body=body,
|
|
2410
|
+
debug=debug,
|
|
2411
|
+
port=port,
|
|
2412
|
+
offset=offset,
|
|
2413
|
+
limit=limit,
|
|
2414
|
+
python_transform=python_transform,
|
|
2415
|
+
extra_headers=request_headers,
|
|
2416
|
+
)
|
|
2417
|
+
if not result.get("success"):
|
|
2418
|
+
raise_tool_error(result)
|
|
2419
|
+
return result
|
|
2420
|
+
|
|
2276
2421
|
async def manage_addon(
|
|
2277
2422
|
self,
|
|
2278
2423
|
slug: str,
|
|
@@ -2326,45 +2471,28 @@ class AddOnTools:
|
|
|
2326
2471
|
# Lifecycle mode takes precedence and is mutually exclusive with the
|
|
2327
2472
|
# proxy / config / array-patch modes.
|
|
2328
2473
|
if action is not None:
|
|
2329
|
-
|
|
2330
|
-
if path is not None:
|
|
2331
|
-
conflicts.append("path")
|
|
2332
|
-
if config_data:
|
|
2333
|
-
conflicts.append("config parameters")
|
|
2334
|
-
if array_patch is not None:
|
|
2335
|
-
conflicts.append("array_patch")
|
|
2336
|
-
if conflicts:
|
|
2337
|
-
raise_tool_error(
|
|
2338
|
-
create_validation_error(
|
|
2339
|
-
f"action='{action}' (lifecycle mode) cannot be combined "
|
|
2340
|
-
f"with {', '.join(conflicts)}. Use one mode at a time.",
|
|
2341
|
-
parameter="action",
|
|
2342
|
-
)
|
|
2343
|
-
)
|
|
2474
|
+
self._reject_action_mode_conflicts(action, path, config_data, array_patch)
|
|
2344
2475
|
return await self._execute_action_mode(slug, action)
|
|
2345
2476
|
|
|
2346
2477
|
self._validate_manage_mode(path, config_data)
|
|
2347
2478
|
|
|
2348
2479
|
if config_data:
|
|
2349
|
-
|
|
2350
|
-
method,
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2480
|
+
self._reject_config_mode_proxy_params(
|
|
2481
|
+
method=method,
|
|
2482
|
+
body=body,
|
|
2483
|
+
debug=debug,
|
|
2484
|
+
port=port,
|
|
2485
|
+
offset=offset,
|
|
2486
|
+
limit=limit,
|
|
2487
|
+
websocket=websocket,
|
|
2488
|
+
wait_for_close=wait_for_close,
|
|
2489
|
+
message_limit=message_limit,
|
|
2490
|
+
message_offset=message_offset,
|
|
2491
|
+
summarize=summarize,
|
|
2492
|
+
python_transform=python_transform,
|
|
2493
|
+
array_patch=array_patch,
|
|
2494
|
+
request_headers=request_headers,
|
|
2359
2495
|
)
|
|
2360
|
-
if proxy_overrides:
|
|
2361
|
-
raise_tool_error(
|
|
2362
|
-
create_validation_error(
|
|
2363
|
-
f"Proxy-mode parameters cannot be used in config mode: {', '.join(d for _, d in proxy_overrides)}. "
|
|
2364
|
-
"Remove these parameters or switch to proxy mode by providing 'path'.",
|
|
2365
|
-
parameter=proxy_overrides[0][0],
|
|
2366
|
-
)
|
|
2367
|
-
)
|
|
2368
2496
|
return await self._execute_config_mode(slug, config_data)
|
|
2369
2497
|
|
|
2370
2498
|
# _call_addon_ws does not accept caller headers — reject the combo rather
|
|
@@ -2399,43 +2527,20 @@ class AddOnTools:
|
|
|
2399
2527
|
)
|
|
2400
2528
|
|
|
2401
2529
|
if websocket:
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
summarize=summarize,
|
|
2414
|
-
python_transform=python_transform,
|
|
2415
|
-
)
|
|
2416
|
-
if not result.get("success"):
|
|
2417
|
-
raise_tool_error(result)
|
|
2418
|
-
return result
|
|
2419
|
-
|
|
2420
|
-
valid_methods = {"GET", "POST", "PUT", "DELETE", "PATCH"}
|
|
2421
|
-
if method.upper() not in valid_methods:
|
|
2422
|
-
raise_tool_error(
|
|
2423
|
-
create_validation_error(
|
|
2424
|
-
f"Invalid HTTP method: {method}. Must be one of: {', '.join(sorted(valid_methods))}",
|
|
2425
|
-
parameter="method",
|
|
2426
|
-
)
|
|
2427
|
-
)
|
|
2428
|
-
if message_limit is not None or message_offset != 0 or not summarize:
|
|
2429
|
-
raise_tool_error(
|
|
2430
|
-
create_validation_error(
|
|
2431
|
-
"message_limit / message_offset / summarize apply only to "
|
|
2432
|
-
"WebSocket mode. Set websocket=True or remove them.",
|
|
2433
|
-
parameter="message_limit",
|
|
2434
|
-
)
|
|
2530
|
+
return await self._execute_ws_proxy(
|
|
2531
|
+
slug,
|
|
2532
|
+
path,
|
|
2533
|
+
body,
|
|
2534
|
+
debug,
|
|
2535
|
+
port,
|
|
2536
|
+
wait_for_close,
|
|
2537
|
+
message_limit,
|
|
2538
|
+
message_offset,
|
|
2539
|
+
summarize,
|
|
2540
|
+
python_transform,
|
|
2435
2541
|
)
|
|
2436
2542
|
|
|
2437
|
-
|
|
2438
|
-
client=self._client,
|
|
2543
|
+
return await self._execute_http_proxy(
|
|
2439
2544
|
slug=slug,
|
|
2440
2545
|
path=path,
|
|
2441
2546
|
method=method,
|
|
@@ -2445,11 +2550,11 @@ class AddOnTools:
|
|
|
2445
2550
|
offset=offset,
|
|
2446
2551
|
limit=limit,
|
|
2447
2552
|
python_transform=python_transform,
|
|
2448
|
-
|
|
2553
|
+
request_headers=request_headers,
|
|
2554
|
+
message_limit=message_limit,
|
|
2555
|
+
message_offset=message_offset,
|
|
2556
|
+
summarize=summarize,
|
|
2449
2557
|
)
|
|
2450
|
-
if not result.get("success"):
|
|
2451
|
-
raise_tool_error(result)
|
|
2452
|
-
return result
|
|
2453
2558
|
|
|
2454
2559
|
|
|
2455
2560
|
def register_addon_tools(mcp: Any, client: HomeAssistantClient, **kwargs: Any) -> None:
|