ha-mcp-dev 7.4.1.dev454__tar.gz → 7.4.1.dev455__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.4.1.dev454/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.4.1.dev455}/PKG-INFO +1 -1
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/pyproject.toml +1 -1
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/tools/device_control.py +64 -39
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/tools/helpers.py +44 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/tools/smart_search.py +43 -40
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/tools/tools_hacs.py +31 -28
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/tools/tools_history.py +26 -23
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/tools/tools_traces.py +37 -36
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/LICENSE +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/MANIFEST.in +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/README.md +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/setup.cfg +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/__init__.py +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/__main__.py +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/_pypi_marker +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/_version.py +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/auth/__init__.py +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/auth/consent_form.py +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/auth/provider.py +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/client/__init__.py +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/client/rest_client.py +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/client/websocket_client.py +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/client/websocket_listener.py +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/config.py +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/errors.py +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/py.typed +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/server.py +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/settings_ui.py +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/smoke_test.py +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/tools/__init__.py +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/tools/backup.py +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/tools/best_practice_checker.py +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/tools/enhanced.py +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/tools/reference_validator.py +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/tools/registry.py +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/tools/tools_addons.py +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/tools/tools_areas.py +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/tools/tools_blueprints.py +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/tools/tools_bug_report.py +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/tools/tools_calendar.py +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/tools/tools_camera.py +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/tools/tools_categories.py +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/tools/tools_code.py +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/tools/tools_config_automations.py +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/tools/tools_config_entry_flow.py +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/tools/tools_energy.py +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/tools/tools_entities.py +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/tools/tools_filesystem.py +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/tools/tools_groups.py +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/tools/tools_integrations.py +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/tools/tools_labels.py +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/tools/tools_mcp_component.py +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/tools/tools_registry.py +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/tools/tools_resources.py +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/tools/tools_search.py +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/tools/tools_service.py +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/tools/tools_services.py +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/tools/tools_system.py +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/tools/tools_todo.py +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/tools/tools_updates.py +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/tools/tools_utility.py +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/tools/tools_zones.py +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/tools/util_helpers.py +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/transforms/__init__.py +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/transforms/categorized_search.py +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/utils/__init__.py +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/utils/config_hash.py +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/utils/data_paths.py +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/utils/domain_handlers.py +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/utils/fuzzy_search.py +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/utils/operation_manager.py +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/utils/python_sandbox.py +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/utils/usage_logger.py +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/tests/__init__.py +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/tests/test_constants.py +0 -0
- {ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/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.4.1.
|
|
7
|
+
version = "7.4.1.dev455"
|
|
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"
|
|
@@ -8,6 +8,7 @@ and async operation verification through WebSocket monitoring.
|
|
|
8
8
|
import asyncio
|
|
9
9
|
import json
|
|
10
10
|
import logging
|
|
11
|
+
import time
|
|
11
12
|
from typing import Any, ClassVar
|
|
12
13
|
|
|
13
14
|
from fastmcp import Context
|
|
@@ -19,7 +20,12 @@ from ..config import get_global_settings
|
|
|
19
20
|
from ..errors import ErrorCode, create_error_response
|
|
20
21
|
from ..utils.domain_handlers import get_domain_handler
|
|
21
22
|
from ..utils.operation_manager import get_operation_from_memory, store_pending_operation
|
|
22
|
-
from .helpers import
|
|
23
|
+
from .helpers import (
|
|
24
|
+
exception_to_structured_error,
|
|
25
|
+
raise_tool_error,
|
|
26
|
+
safe_info,
|
|
27
|
+
safe_progress,
|
|
28
|
+
)
|
|
23
29
|
|
|
24
30
|
logger = logging.getLogger(__name__)
|
|
25
31
|
|
|
@@ -394,18 +400,13 @@ class DeviceControlTools:
|
|
|
394
400
|
async def get_device_operation_status(
|
|
395
401
|
self, operation_id: str, timeout_seconds: int = 10
|
|
396
402
|
) -> dict[str, Any]:
|
|
397
|
-
"""
|
|
398
|
-
Check status of device operation with async verification.
|
|
399
|
-
|
|
400
|
-
This tool checks the status of operations initiated by control_device_smart.
|
|
401
|
-
Results come from real-time WebSocket monitoring of Home Assistant state changes.
|
|
402
|
-
|
|
403
|
-
Args:
|
|
404
|
-
operation_id: Operation ID returned by control_device_smart
|
|
405
|
-
timeout_seconds: Maximum time to wait for completion
|
|
403
|
+
"""Check status of a device operation, waiting up to ``timeout_seconds`` for completion.
|
|
406
404
|
|
|
407
|
-
|
|
408
|
-
|
|
405
|
+
Polls the in-memory operation registry (mutated by the WebSocket
|
|
406
|
+
listener as state changes arrive) every 0.2s while the operation is
|
|
407
|
+
pending, up to ``timeout_seconds``. Returns the final structured status
|
|
408
|
+
— completed/failed/timeout/pending — produced by
|
|
409
|
+
``control_device_smart``.
|
|
409
410
|
"""
|
|
410
411
|
operation = get_operation_from_memory(operation_id)
|
|
411
412
|
|
|
@@ -421,6 +422,30 @@ class DeviceControlTools:
|
|
|
421
422
|
context={"operation_id": operation_id},
|
|
422
423
|
))
|
|
423
424
|
|
|
425
|
+
# Wait up to timeout_seconds for the operation to leave the pending state.
|
|
426
|
+
# The WebSocket listener mutates operation.status as state changes arrive,
|
|
427
|
+
# so polling memory is sufficient — no need to subscribe again. Uses
|
|
428
|
+
# time.monotonic() so the deadline can be cleanly patched in tests.
|
|
429
|
+
if operation.status.value == "pending" and timeout_seconds > 0:
|
|
430
|
+
deadline = time.monotonic() + timeout_seconds
|
|
431
|
+
while operation.status.value == "pending":
|
|
432
|
+
if time.monotonic() >= deadline:
|
|
433
|
+
break
|
|
434
|
+
await asyncio.sleep(0.2)
|
|
435
|
+
refreshed = get_operation_from_memory(operation_id)
|
|
436
|
+
if refreshed is None:
|
|
437
|
+
raise_tool_error(create_error_response(
|
|
438
|
+
ErrorCode.RESOURCE_NOT_FOUND,
|
|
439
|
+
"Operation cleaned up during status poll",
|
|
440
|
+
suggestions=[
|
|
441
|
+
"Operation may have completed and been purged before "
|
|
442
|
+
"verification finished",
|
|
443
|
+
"Use control_device_smart to start new operation",
|
|
444
|
+
],
|
|
445
|
+
context={"operation_id": operation_id},
|
|
446
|
+
))
|
|
447
|
+
operation = refreshed
|
|
448
|
+
|
|
424
449
|
# Check operation status
|
|
425
450
|
if operation.status.value == "completed":
|
|
426
451
|
return {
|
|
@@ -546,7 +571,6 @@ class DeviceControlTools:
|
|
|
546
571
|
Args:
|
|
547
572
|
operations: List of device control operations
|
|
548
573
|
parallel: Whether to execute operations in parallel
|
|
549
|
-
ctx: Optional FastMCP Context for progress reporting
|
|
550
574
|
|
|
551
575
|
Returns:
|
|
552
576
|
Bulk operation results
|
|
@@ -568,17 +592,18 @@ class DeviceControlTools:
|
|
|
568
592
|
operations, skipped_operations
|
|
569
593
|
)
|
|
570
594
|
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
595
|
+
await safe_info(
|
|
596
|
+
ctx,
|
|
597
|
+
f"bulk_device_control: {len(valid_operations)} valid op(s), "
|
|
598
|
+
f"{len(skipped_operations)} skipped, "
|
|
599
|
+
f"mode={'parallel' if parallel else 'sequential'}",
|
|
600
|
+
)
|
|
601
|
+
await safe_progress(
|
|
602
|
+
ctx,
|
|
603
|
+
progress=0,
|
|
604
|
+
total=len(valid_operations),
|
|
605
|
+
message="dispatching operations",
|
|
606
|
+
)
|
|
582
607
|
|
|
583
608
|
# Execute only valid operations
|
|
584
609
|
if parallel:
|
|
@@ -588,15 +613,15 @@ class DeviceControlTools:
|
|
|
588
613
|
valid_operations, results, operation_ids, ctx=ctx
|
|
589
614
|
)
|
|
590
615
|
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
616
|
+
await safe_progress(
|
|
617
|
+
ctx,
|
|
618
|
+
progress=len(valid_operations),
|
|
619
|
+
total=len(valid_operations),
|
|
620
|
+
message=(
|
|
621
|
+
f"dispatched {len(operation_ids)} op(s); "
|
|
622
|
+
"use get_bulk_operation_status to verify completion"
|
|
623
|
+
),
|
|
624
|
+
)
|
|
600
625
|
|
|
601
626
|
return self._build_bulk_response(
|
|
602
627
|
operations, results, operation_ids, skipped_operations, parallel
|
|
@@ -682,12 +707,12 @@ class DeviceControlTools:
|
|
|
682
707
|
ErrorCode.SERVICE_CALL_FAILED,
|
|
683
708
|
f"Exception during execution: {e!s}",
|
|
684
709
|
))
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
710
|
+
await safe_progress(
|
|
711
|
+
ctx,
|
|
712
|
+
progress=i + 1,
|
|
713
|
+
total=total,
|
|
714
|
+
message=f"{entity_id} {action} dispatched",
|
|
715
|
+
)
|
|
691
716
|
|
|
692
717
|
def _build_bulk_response(
|
|
693
718
|
self,
|
|
@@ -12,6 +12,7 @@ import sys
|
|
|
12
12
|
import time
|
|
13
13
|
from typing import Any, Literal, NoReturn, overload
|
|
14
14
|
|
|
15
|
+
from fastmcp import Context
|
|
15
16
|
from fastmcp.exceptions import ToolError
|
|
16
17
|
|
|
17
18
|
from ..client.rest_client import (
|
|
@@ -371,6 +372,49 @@ def log_tool_usage(func: Any) -> Any:
|
|
|
371
372
|
return wrapper
|
|
372
373
|
|
|
373
374
|
|
|
375
|
+
async def safe_progress(
|
|
376
|
+
ctx: Context | None,
|
|
377
|
+
*,
|
|
378
|
+
progress: float,
|
|
379
|
+
total: float | None = None,
|
|
380
|
+
message: str | None = None,
|
|
381
|
+
) -> None:
|
|
382
|
+
"""Report progress via ``ctx.report_progress`` with best-effort error handling.
|
|
383
|
+
|
|
384
|
+
A transport hiccup on a progress notification must never convert a
|
|
385
|
+
successful tool result into a ``ToolError``. Transport errors are logged
|
|
386
|
+
at debug; ``TypeError``/``AttributeError`` are escalated to ``warning``
|
|
387
|
+
because they signal a signature/interface mismatch (call-site bug or
|
|
388
|
+
Context object missing the expected method), not a flaky client.
|
|
389
|
+
``ctx is None`` short-circuits without I/O.
|
|
390
|
+
"""
|
|
391
|
+
if ctx is None:
|
|
392
|
+
return
|
|
393
|
+
try:
|
|
394
|
+
await ctx.report_progress(progress=progress, total=total, message=message)
|
|
395
|
+
except (TypeError, AttributeError) as e:
|
|
396
|
+
logger.warning(
|
|
397
|
+
"ctx.report_progress signature error (%s): %s", type(e).__name__, e
|
|
398
|
+
)
|
|
399
|
+
except Exception as e:
|
|
400
|
+
logger.debug("ctx.report_progress failed (%s): %s", type(e).__name__, e)
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
async def safe_info(ctx: Context | None, message: str) -> None:
|
|
404
|
+
"""Emit an info message via ``ctx.info`` with best-effort error handling.
|
|
405
|
+
|
|
406
|
+
Shares the rationale and exception-handling contract of ``safe_progress``.
|
|
407
|
+
"""
|
|
408
|
+
if ctx is None:
|
|
409
|
+
return
|
|
410
|
+
try:
|
|
411
|
+
await ctx.info(message)
|
|
412
|
+
except (TypeError, AttributeError) as e:
|
|
413
|
+
logger.warning("ctx.info signature error (%s): %s", type(e).__name__, e)
|
|
414
|
+
except Exception as e:
|
|
415
|
+
logger.debug("ctx.info failed (%s): %s", type(e).__name__, e)
|
|
416
|
+
|
|
417
|
+
|
|
374
418
|
def register_tool_methods(mcp: Any, instance: Any) -> None:
|
|
375
419
|
"""Register all @tool-decorated methods from a class instance with the MCP server.
|
|
376
420
|
|
|
@@ -10,6 +10,7 @@ import time
|
|
|
10
10
|
from typing import Any
|
|
11
11
|
|
|
12
12
|
from fastmcp import Context
|
|
13
|
+
from fastmcp.exceptions import ToolError
|
|
13
14
|
|
|
14
15
|
from ..client.rest_client import HomeAssistantClient
|
|
15
16
|
from ..config import get_global_settings
|
|
@@ -20,7 +21,7 @@ from ..utils.fuzzy_search import (
|
|
|
20
21
|
create_fuzzy_searcher,
|
|
21
22
|
tokenize,
|
|
22
23
|
)
|
|
23
|
-
from .helpers import exception_to_structured_error
|
|
24
|
+
from .helpers import exception_to_structured_error, safe_info, safe_progress
|
|
24
25
|
|
|
25
26
|
logger = logging.getLogger(__name__)
|
|
26
27
|
|
|
@@ -838,25 +839,25 @@ class SmartSearchTools:
|
|
|
838
839
|
query_lower = query.lower().strip()
|
|
839
840
|
|
|
840
841
|
total_phases = len(search_types) + 1 # +1 for initial state fetch
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
842
|
+
await safe_info(
|
|
843
|
+
ctx, f"deep_search starting: query={query!r} types={search_types}"
|
|
844
|
+
)
|
|
845
|
+
await safe_progress(
|
|
846
|
+
ctx,
|
|
847
|
+
progress=0,
|
|
848
|
+
total=total_phases,
|
|
849
|
+
message="fetching entity states",
|
|
850
|
+
)
|
|
850
851
|
|
|
851
852
|
# Fetch all entities once at the beginning to avoid repeated calls
|
|
852
853
|
all_entities = await self.client.get_states()
|
|
853
854
|
phase_done = 1
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
855
|
+
await safe_progress(
|
|
856
|
+
ctx,
|
|
857
|
+
progress=phase_done,
|
|
858
|
+
total=total_phases,
|
|
859
|
+
message=f"fetched {len(all_entities)} entity states",
|
|
860
|
+
)
|
|
860
861
|
|
|
861
862
|
# Pre-resolve unique_ids from cached entity states to avoid redundant API calls
|
|
862
863
|
automation_unique_id_map = {}
|
|
@@ -1031,12 +1032,12 @@ class SmartSearchTools:
|
|
|
1031
1032
|
)
|
|
1032
1033
|
|
|
1033
1034
|
phase_done += 1
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1035
|
+
await safe_progress(
|
|
1036
|
+
ctx,
|
|
1037
|
+
progress=phase_done,
|
|
1038
|
+
total=total_phases,
|
|
1039
|
+
message=f"automations searched ({len(results['automations'])} matches)",
|
|
1040
|
+
)
|
|
1040
1041
|
|
|
1041
1042
|
# ================================================================
|
|
1042
1043
|
# SCRIPT SEARCH (same 3-tier strategy: REST bulk -> WS bulk -> individual)
|
|
@@ -1195,12 +1196,12 @@ class SmartSearchTools:
|
|
|
1195
1196
|
)
|
|
1196
1197
|
|
|
1197
1198
|
phase_done += 1
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1199
|
+
await safe_progress(
|
|
1200
|
+
ctx,
|
|
1201
|
+
progress=phase_done,
|
|
1202
|
+
total=total_phases,
|
|
1203
|
+
message=f"scripts searched ({len(results['scripts'])} matches)",
|
|
1204
|
+
)
|
|
1204
1205
|
|
|
1205
1206
|
# Search helpers with parallel WebSocket calls
|
|
1206
1207
|
if "helper" in search_types:
|
|
@@ -1286,12 +1287,12 @@ class SmartSearchTools:
|
|
|
1286
1287
|
logger.debug(f"Helper list fetch failed: {result}")
|
|
1287
1288
|
|
|
1288
1289
|
phase_done += 1
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1290
|
+
await safe_progress(
|
|
1291
|
+
ctx,
|
|
1292
|
+
progress=phase_done,
|
|
1293
|
+
total=total_phases,
|
|
1294
|
+
message=f"helpers searched ({len(results['helpers'])} matches)",
|
|
1295
|
+
)
|
|
1295
1296
|
|
|
1296
1297
|
# ================================================================
|
|
1297
1298
|
# DASHBOARD SEARCH
|
|
@@ -1386,12 +1387,12 @@ class SmartSearchTools:
|
|
|
1386
1387
|
raise
|
|
1387
1388
|
|
|
1388
1389
|
phase_done += 1
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1390
|
+
await safe_progress(
|
|
1391
|
+
ctx,
|
|
1392
|
+
progress=phase_done,
|
|
1393
|
+
total=total_phases,
|
|
1394
|
+
message=f"dashboards searched ({len(results['dashboards'])} matches)",
|
|
1395
|
+
)
|
|
1395
1396
|
|
|
1396
1397
|
# Merge all results with their category, sort by score, and paginate
|
|
1397
1398
|
tagged_results: list[tuple[str, dict[str, Any]]] = []
|
|
@@ -1438,6 +1439,8 @@ class SmartSearchTools:
|
|
|
1438
1439
|
|
|
1439
1440
|
return response
|
|
1440
1441
|
|
|
1442
|
+
except ToolError:
|
|
1443
|
+
raise
|
|
1441
1444
|
except Exception as e:
|
|
1442
1445
|
logger.error(f"Error in deep_search: {e}")
|
|
1443
1446
|
exception_to_structured_error(
|
|
@@ -19,6 +19,8 @@ from .helpers import (
|
|
|
19
19
|
log_tool_usage,
|
|
20
20
|
raise_tool_error,
|
|
21
21
|
register_tool_methods,
|
|
22
|
+
safe_info,
|
|
23
|
+
safe_progress,
|
|
22
24
|
)
|
|
23
25
|
from .util_helpers import add_timezone_metadata, coerce_bool_param, coerce_int_param
|
|
24
26
|
|
|
@@ -183,16 +185,17 @@ class HacsTools:
|
|
|
183
185
|
min_value=0,
|
|
184
186
|
)
|
|
185
187
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
188
|
+
await safe_info(
|
|
189
|
+
ctx,
|
|
190
|
+
f"ha_hacs_search starting: query={query!r} "
|
|
191
|
+
f"category={category} installed_only={installed_only_bool}",
|
|
192
|
+
)
|
|
193
|
+
await safe_progress(
|
|
194
|
+
ctx,
|
|
195
|
+
progress=0,
|
|
196
|
+
total=3,
|
|
197
|
+
message="checking HACS availability",
|
|
198
|
+
)
|
|
196
199
|
|
|
197
200
|
# Check if HACS is available
|
|
198
201
|
await _assert_hacs_available()
|
|
@@ -208,12 +211,12 @@ class HacsTools:
|
|
|
208
211
|
hacs_category = CATEGORY_MAP.get(category, category)
|
|
209
212
|
kwargs_cmd["categories"] = [hacs_category]
|
|
210
213
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
214
|
+
await safe_progress(
|
|
215
|
+
ctx,
|
|
216
|
+
progress=1,
|
|
217
|
+
total=3,
|
|
218
|
+
message="fetching HACS repository list",
|
|
219
|
+
)
|
|
217
220
|
|
|
218
221
|
response = await ws_client.send_command(
|
|
219
222
|
"hacs/repositories/list", **kwargs_cmd
|
|
@@ -231,21 +234,21 @@ class HacsTools:
|
|
|
231
234
|
)
|
|
232
235
|
|
|
233
236
|
all_repositories = response.get("result", [])
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
237
|
+
await safe_progress(
|
|
238
|
+
ctx,
|
|
239
|
+
progress=2,
|
|
240
|
+
total=3,
|
|
241
|
+
message=f"filtering {len(all_repositories)} repositories",
|
|
242
|
+
)
|
|
240
243
|
matches = _filter_and_score_repos(
|
|
241
244
|
all_repositories, query, installed_only_bool
|
|
242
245
|
)
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
246
|
+
await safe_progress(
|
|
247
|
+
ctx,
|
|
248
|
+
progress=3,
|
|
249
|
+
total=3,
|
|
250
|
+
message=f"matched {len(matches)} repositories",
|
|
251
|
+
)
|
|
249
252
|
|
|
250
253
|
limited_matches = matches[offset_int : offset_int + max_results_int]
|
|
251
254
|
has_more = (offset_int + len(limited_matches)) < len(matches)
|
|
@@ -26,6 +26,8 @@ from .helpers import (
|
|
|
26
26
|
log_tool_usage,
|
|
27
27
|
raise_tool_error,
|
|
28
28
|
register_tool_methods,
|
|
29
|
+
safe_info,
|
|
30
|
+
safe_progress,
|
|
29
31
|
)
|
|
30
32
|
from .util_helpers import (
|
|
31
33
|
add_timezone_metadata,
|
|
@@ -290,17 +292,18 @@ class HistoryTools:
|
|
|
290
292
|
# Parse time parameters
|
|
291
293
|
start_dt, end_dt = _parse_time_range(start_time, end_time, default_hours)
|
|
292
294
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
295
|
+
await safe_info(
|
|
296
|
+
ctx,
|
|
297
|
+
f"ha_get_history starting: source={source} "
|
|
298
|
+
f"entities={len(entity_id_list)} "
|
|
299
|
+
f"window={start_dt.isoformat()}..{end_dt.isoformat()}",
|
|
300
|
+
)
|
|
301
|
+
await safe_progress(
|
|
302
|
+
ctx,
|
|
303
|
+
progress=0,
|
|
304
|
+
total=3,
|
|
305
|
+
message="connecting to Home Assistant WebSocket",
|
|
306
|
+
)
|
|
304
307
|
|
|
305
308
|
# Connect to WebSocket (shared by both sources)
|
|
306
309
|
ws_client, error = await get_connected_ws_client(
|
|
@@ -314,12 +317,12 @@ class HistoryTools:
|
|
|
314
317
|
"Failed to connect to Home Assistant WebSocket",
|
|
315
318
|
))
|
|
316
319
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
320
|
+
await safe_progress(
|
|
321
|
+
ctx,
|
|
322
|
+
progress=1,
|
|
323
|
+
total=3,
|
|
324
|
+
message=f"querying recorder ({source})",
|
|
325
|
+
)
|
|
323
326
|
|
|
324
327
|
try:
|
|
325
328
|
if source == "statistics":
|
|
@@ -335,12 +338,12 @@ class HistoryTools:
|
|
|
335
338
|
significant_changes_only, limit, offset,
|
|
336
339
|
_DEFAULT_HISTORY_LIMIT, _MAX_HISTORY_LIMIT,
|
|
337
340
|
)
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
341
|
+
await safe_progress(
|
|
342
|
+
ctx,
|
|
343
|
+
progress=3,
|
|
344
|
+
total=3,
|
|
345
|
+
message="recorder query complete",
|
|
346
|
+
)
|
|
344
347
|
return result
|
|
345
348
|
finally:
|
|
346
349
|
if ws_client:
|
|
@@ -22,6 +22,8 @@ from .helpers import (
|
|
|
22
22
|
log_tool_usage,
|
|
23
23
|
raise_tool_error,
|
|
24
24
|
register_tool_methods,
|
|
25
|
+
safe_info,
|
|
26
|
+
safe_progress,
|
|
25
27
|
)
|
|
26
28
|
|
|
27
29
|
logger = logging.getLogger(__name__)
|
|
@@ -167,16 +169,17 @@ class TraceTools:
|
|
|
167
169
|
# Extract the object_id (part after the domain) as fallback
|
|
168
170
|
object_id = automation_id.split(".", 1)[1]
|
|
169
171
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
172
|
+
await safe_info(
|
|
173
|
+
ctx,
|
|
174
|
+
f"ha_get_automation_traces starting: id={automation_id} "
|
|
175
|
+
f"run_id={run_id or '<list>'}",
|
|
176
|
+
)
|
|
177
|
+
await safe_progress(
|
|
178
|
+
ctx,
|
|
179
|
+
progress=0,
|
|
180
|
+
total=3,
|
|
181
|
+
message="connecting to Home Assistant WebSocket",
|
|
182
|
+
)
|
|
180
183
|
|
|
181
184
|
# Connect to WebSocket
|
|
182
185
|
ws_client, error = await get_connected_ws_client(
|
|
@@ -198,12 +201,12 @@ class TraceTools:
|
|
|
198
201
|
ws_client, automation_id, object_id
|
|
199
202
|
)
|
|
200
203
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
204
|
+
await safe_progress(
|
|
205
|
+
ctx,
|
|
206
|
+
progress=1,
|
|
207
|
+
total=3,
|
|
208
|
+
message=f"fetching trace {'detail' if run_id else 'list'}",
|
|
209
|
+
)
|
|
207
210
|
|
|
208
211
|
if run_id:
|
|
209
212
|
# Get specific trace details
|
|
@@ -225,10 +228,9 @@ class TraceTools:
|
|
|
225
228
|
))
|
|
226
229
|
|
|
227
230
|
trace_data = result.get("result", {})
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
)
|
|
231
|
+
await safe_progress(
|
|
232
|
+
ctx, progress=3, total=3, message="formatting trace"
|
|
233
|
+
)
|
|
232
234
|
return _format_detailed_trace(
|
|
233
235
|
automation_id, run_id, trace_data,
|
|
234
236
|
deduplicate=deduplicate, detailed=detailed,
|
|
@@ -253,29 +255,28 @@ class TraceTools:
|
|
|
253
255
|
|
|
254
256
|
# If traces are empty, gather diagnostic information
|
|
255
257
|
if not traces_data:
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
258
|
+
await safe_progress(
|
|
259
|
+
ctx,
|
|
260
|
+
progress=2,
|
|
261
|
+
total=3,
|
|
262
|
+
message="no traces; gathering diagnostics",
|
|
263
|
+
)
|
|
262
264
|
diagnostics = await _gather_diagnostics(
|
|
263
265
|
ws_client, self._client, automation_id, domain
|
|
264
266
|
)
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
)
|
|
267
|
+
await safe_progress(
|
|
268
|
+
ctx, progress=3, total=3, message="diagnostics complete"
|
|
269
|
+
)
|
|
269
270
|
return _format_trace_list(
|
|
270
271
|
automation_id, traces_data, limit, diagnostics
|
|
271
272
|
)
|
|
272
273
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
274
|
+
await safe_progress(
|
|
275
|
+
ctx,
|
|
276
|
+
progress=3,
|
|
277
|
+
total=3,
|
|
278
|
+
message=f"listed {len(traces_data)} traces",
|
|
279
|
+
)
|
|
279
280
|
return _format_trace_list(automation_id, traces_data, limit)
|
|
280
281
|
|
|
281
282
|
finally:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/resources/skills-vendor/AGENTS.md
RENAMED
|
File without changes
|
{ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/resources/skills-vendor/CLAUDE.md
RENAMED
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/resources/skills-vendor/LICENSE
RENAMED
|
File without changes
|
{ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/resources/skills-vendor/README.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/tools/best_practice_checker.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/tools/tools_config_automations.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/tools/tools_config_dashboards.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/tools/tools_config_entry_flow.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/tools/tools_config_helpers.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/tools/tools_config_scripts.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/tools/tools_voice_assistant.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/transforms/categorized_search.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp/utils/kill_signal_diagnostics.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp_dev.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{ha_mcp_dev-7.4.1.dev454 → ha_mcp_dev-7.4.1.dev455}/src/ha_mcp_dev.egg-info/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|