ha-mcp-dev 7.4.1.dev452__tar.gz → 7.4.1.dev454__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.dev452/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.4.1.dev454}/PKG-INFO +1 -1
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/pyproject.toml +1 -1
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/tools/device_control.py +40 -3
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/tools/smart_search.py +53 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/tools/tools_hacs.py +32 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/tools/tools_history.py +30 -2
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/tools/tools_search.py +3 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/tools/tools_service.py +5 -2
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/tools/tools_traces.py +43 -3
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/LICENSE +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/MANIFEST.in +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/README.md +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/setup.cfg +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/__init__.py +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/__main__.py +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/_pypi_marker +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/_version.py +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/auth/__init__.py +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/auth/consent_form.py +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/auth/provider.py +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/client/__init__.py +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/client/rest_client.py +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/client/websocket_client.py +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/client/websocket_listener.py +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/config.py +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/errors.py +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/py.typed +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/server.py +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/settings_ui.py +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/smoke_test.py +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/tools/__init__.py +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/tools/backup.py +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/tools/best_practice_checker.py +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/tools/enhanced.py +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/tools/helpers.py +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/tools/reference_validator.py +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/tools/registry.py +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/tools/tools_addons.py +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/tools/tools_areas.py +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/tools/tools_blueprints.py +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/tools/tools_bug_report.py +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/tools/tools_calendar.py +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/tools/tools_camera.py +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/tools/tools_categories.py +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/tools/tools_code.py +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/tools/tools_config_automations.py +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/tools/tools_config_entry_flow.py +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/tools/tools_energy.py +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/tools/tools_entities.py +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/tools/tools_filesystem.py +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/tools/tools_groups.py +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/tools/tools_integrations.py +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/tools/tools_labels.py +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/tools/tools_mcp_component.py +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/tools/tools_registry.py +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/tools/tools_resources.py +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/tools/tools_services.py +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/tools/tools_system.py +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/tools/tools_todo.py +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/tools/tools_updates.py +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/tools/tools_utility.py +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/tools/tools_zones.py +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/tools/util_helpers.py +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/transforms/__init__.py +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/transforms/categorized_search.py +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/utils/__init__.py +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/utils/config_hash.py +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/utils/data_paths.py +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/utils/domain_handlers.py +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/utils/fuzzy_search.py +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/utils/operation_manager.py +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/utils/python_sandbox.py +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/utils/usage_logger.py +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/tests/__init__.py +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/tests/test_constants.py +0 -0
- {ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/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.dev454"
|
|
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"
|
|
@@ -10,6 +10,7 @@ import json
|
|
|
10
10
|
import logging
|
|
11
11
|
from typing import Any, ClassVar
|
|
12
12
|
|
|
13
|
+
from fastmcp import Context
|
|
13
14
|
from fastmcp.exceptions import ToolError
|
|
14
15
|
|
|
15
16
|
from ..client.rest_client import HomeAssistantClient
|
|
@@ -534,7 +535,10 @@ class DeviceControlTools:
|
|
|
534
535
|
return valid
|
|
535
536
|
|
|
536
537
|
async def bulk_device_control(
|
|
537
|
-
self,
|
|
538
|
+
self,
|
|
539
|
+
operations: list[dict[str, Any]],
|
|
540
|
+
parallel: bool = True,
|
|
541
|
+
ctx: Context | None = None,
|
|
538
542
|
) -> dict[str, Any]:
|
|
539
543
|
"""
|
|
540
544
|
Control multiple devices with bulk operation support.
|
|
@@ -542,6 +546,7 @@ class DeviceControlTools:
|
|
|
542
546
|
Args:
|
|
543
547
|
operations: List of device control operations
|
|
544
548
|
parallel: Whether to execute operations in parallel
|
|
549
|
+
ctx: Optional FastMCP Context for progress reporting
|
|
545
550
|
|
|
546
551
|
Returns:
|
|
547
552
|
Bulk operation results
|
|
@@ -563,11 +568,35 @@ class DeviceControlTools:
|
|
|
563
568
|
operations, skipped_operations
|
|
564
569
|
)
|
|
565
570
|
|
|
571
|
+
if ctx is not None:
|
|
572
|
+
await ctx.info(
|
|
573
|
+
f"bulk_device_control: {len(valid_operations)} valid op(s), "
|
|
574
|
+
f"{len(skipped_operations)} skipped, "
|
|
575
|
+
f"mode={'parallel' if parallel else 'sequential'}"
|
|
576
|
+
)
|
|
577
|
+
await ctx.report_progress(
|
|
578
|
+
progress=0,
|
|
579
|
+
total=len(valid_operations),
|
|
580
|
+
message="dispatching operations",
|
|
581
|
+
)
|
|
582
|
+
|
|
566
583
|
# Execute only valid operations
|
|
567
584
|
if parallel:
|
|
568
585
|
await self._execute_parallel(valid_operations, results, operation_ids)
|
|
569
586
|
else:
|
|
570
|
-
await self._execute_sequential(
|
|
587
|
+
await self._execute_sequential(
|
|
588
|
+
valid_operations, results, operation_ids, ctx=ctx
|
|
589
|
+
)
|
|
590
|
+
|
|
591
|
+
if ctx is not None:
|
|
592
|
+
await ctx.report_progress(
|
|
593
|
+
progress=len(valid_operations),
|
|
594
|
+
total=len(valid_operations),
|
|
595
|
+
message=(
|
|
596
|
+
f"dispatched {len(operation_ids)} op(s); "
|
|
597
|
+
"use get_bulk_operation_status to verify completion"
|
|
598
|
+
),
|
|
599
|
+
)
|
|
571
600
|
|
|
572
601
|
return self._build_bulk_response(
|
|
573
602
|
operations, results, operation_ids, skipped_operations, parallel
|
|
@@ -631,8 +660,10 @@ class DeviceControlTools:
|
|
|
631
660
|
valid_operations: list[tuple[int, dict[str, Any], str, str]],
|
|
632
661
|
results: list[dict[str, Any]],
|
|
633
662
|
operation_ids: list[str],
|
|
663
|
+
ctx: Context | None = None,
|
|
634
664
|
) -> None:
|
|
635
|
-
|
|
665
|
+
total = len(valid_operations)
|
|
666
|
+
for i, (_orig_index, op, entity_id, action) in enumerate(valid_operations):
|
|
636
667
|
try:
|
|
637
668
|
result = await self.control_device_smart(
|
|
638
669
|
entity_id=entity_id,
|
|
@@ -651,6 +682,12 @@ class DeviceControlTools:
|
|
|
651
682
|
ErrorCode.SERVICE_CALL_FAILED,
|
|
652
683
|
f"Exception during execution: {e!s}",
|
|
653
684
|
))
|
|
685
|
+
if ctx is not None:
|
|
686
|
+
await ctx.report_progress(
|
|
687
|
+
progress=i + 1,
|
|
688
|
+
total=total,
|
|
689
|
+
message=f"{entity_id} {action} dispatched",
|
|
690
|
+
)
|
|
654
691
|
|
|
655
692
|
def _build_bulk_response(
|
|
656
693
|
self,
|
|
@@ -9,6 +9,8 @@ import random
|
|
|
9
9
|
import time
|
|
10
10
|
from typing import Any
|
|
11
11
|
|
|
12
|
+
from fastmcp import Context
|
|
13
|
+
|
|
12
14
|
from ..client.rest_client import HomeAssistantClient
|
|
13
15
|
from ..config import get_global_settings
|
|
14
16
|
from ..utils.fuzzy_search import (
|
|
@@ -802,6 +804,7 @@ class SmartSearchTools:
|
|
|
802
804
|
include_config: bool = False,
|
|
803
805
|
concurrency_limit: int = DEFAULT_CONCURRENCY_LIMIT,
|
|
804
806
|
exact_match: bool = True,
|
|
807
|
+
ctx: Context | None = None,
|
|
805
808
|
) -> dict[str, Any]:
|
|
806
809
|
"""
|
|
807
810
|
Deep search across automation, script, helper, and dashboard definitions.
|
|
@@ -834,8 +837,26 @@ class SmartSearchTools:
|
|
|
834
837
|
|
|
835
838
|
query_lower = query.lower().strip()
|
|
836
839
|
|
|
840
|
+
total_phases = len(search_types) + 1 # +1 for initial state fetch
|
|
841
|
+
if ctx is not None:
|
|
842
|
+
await ctx.info(
|
|
843
|
+
f"deep_search starting: query={query!r} types={search_types}"
|
|
844
|
+
)
|
|
845
|
+
await ctx.report_progress(
|
|
846
|
+
progress=0,
|
|
847
|
+
total=total_phases,
|
|
848
|
+
message="fetching entity states",
|
|
849
|
+
)
|
|
850
|
+
|
|
837
851
|
# Fetch all entities once at the beginning to avoid repeated calls
|
|
838
852
|
all_entities = await self.client.get_states()
|
|
853
|
+
phase_done = 1
|
|
854
|
+
if ctx is not None:
|
|
855
|
+
await ctx.report_progress(
|
|
856
|
+
progress=phase_done,
|
|
857
|
+
total=total_phases,
|
|
858
|
+
message=f"fetched {len(all_entities)} entity states",
|
|
859
|
+
)
|
|
839
860
|
|
|
840
861
|
# Pre-resolve unique_ids from cached entity states to avoid redundant API calls
|
|
841
862
|
automation_unique_id_map = {}
|
|
@@ -1009,6 +1030,14 @@ class SmartSearchTools:
|
|
|
1009
1030
|
}
|
|
1010
1031
|
)
|
|
1011
1032
|
|
|
1033
|
+
phase_done += 1
|
|
1034
|
+
if ctx is not None:
|
|
1035
|
+
await ctx.report_progress(
|
|
1036
|
+
progress=phase_done,
|
|
1037
|
+
total=total_phases,
|
|
1038
|
+
message=f"automations searched ({len(results['automations'])} matches)",
|
|
1039
|
+
)
|
|
1040
|
+
|
|
1012
1041
|
# ================================================================
|
|
1013
1042
|
# SCRIPT SEARCH (same 3-tier strategy: REST bulk -> WS bulk -> individual)
|
|
1014
1043
|
# ================================================================
|
|
@@ -1165,6 +1194,14 @@ class SmartSearchTools:
|
|
|
1165
1194
|
}
|
|
1166
1195
|
)
|
|
1167
1196
|
|
|
1197
|
+
phase_done += 1
|
|
1198
|
+
if ctx is not None:
|
|
1199
|
+
await ctx.report_progress(
|
|
1200
|
+
progress=phase_done,
|
|
1201
|
+
total=total_phases,
|
|
1202
|
+
message=f"scripts searched ({len(results['scripts'])} matches)",
|
|
1203
|
+
)
|
|
1204
|
+
|
|
1168
1205
|
# Search helpers with parallel WebSocket calls
|
|
1169
1206
|
if "helper" in search_types:
|
|
1170
1207
|
helper_types = [
|
|
@@ -1248,6 +1285,14 @@ class SmartSearchTools:
|
|
|
1248
1285
|
elif isinstance(result, Exception):
|
|
1249
1286
|
logger.debug(f"Helper list fetch failed: {result}")
|
|
1250
1287
|
|
|
1288
|
+
phase_done += 1
|
|
1289
|
+
if ctx is not None:
|
|
1290
|
+
await ctx.report_progress(
|
|
1291
|
+
progress=phase_done,
|
|
1292
|
+
total=total_phases,
|
|
1293
|
+
message=f"helpers searched ({len(results['helpers'])} matches)",
|
|
1294
|
+
)
|
|
1295
|
+
|
|
1251
1296
|
# ================================================================
|
|
1252
1297
|
# DASHBOARD SEARCH
|
|
1253
1298
|
# Fetches all storage-mode dashboards and the default dashboard,
|
|
@@ -1340,6 +1385,14 @@ class SmartSearchTools:
|
|
|
1340
1385
|
logger.error(f"Dashboard search error: {e}")
|
|
1341
1386
|
raise
|
|
1342
1387
|
|
|
1388
|
+
phase_done += 1
|
|
1389
|
+
if ctx is not None:
|
|
1390
|
+
await ctx.report_progress(
|
|
1391
|
+
progress=phase_done,
|
|
1392
|
+
total=total_phases,
|
|
1393
|
+
message=f"dashboards searched ({len(results['dashboards'])} matches)",
|
|
1394
|
+
)
|
|
1395
|
+
|
|
1343
1396
|
# Merge all results with their category, sort by score, and paginate
|
|
1344
1397
|
tagged_results: list[tuple[str, dict[str, Any]]] = []
|
|
1345
1398
|
for category, items in results.items():
|
|
@@ -8,6 +8,7 @@ to discover custom integrations, Lovelace cards, themes, and more.
|
|
|
8
8
|
import logging
|
|
9
9
|
from typing import Annotated, Any, Literal
|
|
10
10
|
|
|
11
|
+
from fastmcp import Context
|
|
11
12
|
from fastmcp.exceptions import ToolError
|
|
12
13
|
from fastmcp.tools import tool
|
|
13
14
|
from pydantic import Field
|
|
@@ -137,6 +138,7 @@ class HacsTools:
|
|
|
137
138
|
description="Number of results to skip for pagination (default: 0)",
|
|
138
139
|
),
|
|
139
140
|
] = 0,
|
|
141
|
+
ctx: Context | None = None,
|
|
140
142
|
) -> dict[str, Any]:
|
|
141
143
|
"""Search HACS store for repositories, or list installed repositories.
|
|
142
144
|
|
|
@@ -181,6 +183,17 @@ class HacsTools:
|
|
|
181
183
|
min_value=0,
|
|
182
184
|
)
|
|
183
185
|
|
|
186
|
+
if ctx is not None:
|
|
187
|
+
await ctx.info(
|
|
188
|
+
f"ha_hacs_search starting: query={query!r} "
|
|
189
|
+
f"category={category} installed_only={installed_only_bool}"
|
|
190
|
+
)
|
|
191
|
+
await ctx.report_progress(
|
|
192
|
+
progress=0,
|
|
193
|
+
total=3,
|
|
194
|
+
message="checking HACS availability",
|
|
195
|
+
)
|
|
196
|
+
|
|
184
197
|
# Check if HACS is available
|
|
185
198
|
await _assert_hacs_available()
|
|
186
199
|
|
|
@@ -195,6 +208,13 @@ class HacsTools:
|
|
|
195
208
|
hacs_category = CATEGORY_MAP.get(category, category)
|
|
196
209
|
kwargs_cmd["categories"] = [hacs_category]
|
|
197
210
|
|
|
211
|
+
if ctx is not None:
|
|
212
|
+
await ctx.report_progress(
|
|
213
|
+
progress=1,
|
|
214
|
+
total=3,
|
|
215
|
+
message="fetching HACS repository list",
|
|
216
|
+
)
|
|
217
|
+
|
|
198
218
|
response = await ws_client.send_command(
|
|
199
219
|
"hacs/repositories/list", **kwargs_cmd
|
|
200
220
|
)
|
|
@@ -211,9 +231,21 @@ class HacsTools:
|
|
|
211
231
|
)
|
|
212
232
|
|
|
213
233
|
all_repositories = response.get("result", [])
|
|
234
|
+
if ctx is not None:
|
|
235
|
+
await ctx.report_progress(
|
|
236
|
+
progress=2,
|
|
237
|
+
total=3,
|
|
238
|
+
message=f"filtering {len(all_repositories)} repositories",
|
|
239
|
+
)
|
|
214
240
|
matches = _filter_and_score_repos(
|
|
215
241
|
all_repositories, query, installed_only_bool
|
|
216
242
|
)
|
|
243
|
+
if ctx is not None:
|
|
244
|
+
await ctx.report_progress(
|
|
245
|
+
progress=3,
|
|
246
|
+
total=3,
|
|
247
|
+
message=f"matched {len(matches)} repositories",
|
|
248
|
+
)
|
|
217
249
|
|
|
218
250
|
limited_matches = matches[offset_int : offset_int + max_results_int]
|
|
219
251
|
has_more = (offset_int + len(limited_matches)) < len(matches)
|
|
@@ -14,6 +14,7 @@ import re
|
|
|
14
14
|
from datetime import UTC, datetime, timedelta
|
|
15
15
|
from typing import Annotated, Any, Literal
|
|
16
16
|
|
|
17
|
+
from fastmcp import Context
|
|
17
18
|
from fastmcp.exceptions import ToolError
|
|
18
19
|
from fastmcp.tools import tool
|
|
19
20
|
from pydantic import Field
|
|
@@ -205,6 +206,7 @@ class HistoryTools:
|
|
|
205
206
|
default=None,
|
|
206
207
|
),
|
|
207
208
|
] = None,
|
|
209
|
+
ctx: Context | None = None,
|
|
208
210
|
) -> dict[str, Any]:
|
|
209
211
|
"""
|
|
210
212
|
Retrieve historical data from Home Assistant's recorder.
|
|
@@ -288,6 +290,18 @@ class HistoryTools:
|
|
|
288
290
|
# Parse time parameters
|
|
289
291
|
start_dt, end_dt = _parse_time_range(start_time, end_time, default_hours)
|
|
290
292
|
|
|
293
|
+
if ctx is not None:
|
|
294
|
+
await ctx.info(
|
|
295
|
+
f"ha_get_history starting: source={source} "
|
|
296
|
+
f"entities={len(entity_id_list)} "
|
|
297
|
+
f"window={start_dt.isoformat()}..{end_dt.isoformat()}"
|
|
298
|
+
)
|
|
299
|
+
await ctx.report_progress(
|
|
300
|
+
progress=0,
|
|
301
|
+
total=3,
|
|
302
|
+
message="connecting to Home Assistant WebSocket",
|
|
303
|
+
)
|
|
304
|
+
|
|
291
305
|
# Connect to WebSocket (shared by both sources)
|
|
292
306
|
ws_client, error = await get_connected_ws_client(
|
|
293
307
|
self._client.base_url,
|
|
@@ -300,20 +314,34 @@ class HistoryTools:
|
|
|
300
314
|
"Failed to connect to Home Assistant WebSocket",
|
|
301
315
|
))
|
|
302
316
|
|
|
317
|
+
if ctx is not None:
|
|
318
|
+
await ctx.report_progress(
|
|
319
|
+
progress=1,
|
|
320
|
+
total=3,
|
|
321
|
+
message=f"querying recorder ({source})",
|
|
322
|
+
)
|
|
323
|
+
|
|
303
324
|
try:
|
|
304
325
|
if source == "statistics":
|
|
305
|
-
|
|
326
|
+
result = await _fetch_statistics(
|
|
306
327
|
ws_client, self._client, entity_id_list,
|
|
307
328
|
start_dt, end_dt, period, statistic_types,
|
|
308
329
|
limit, offset,
|
|
309
330
|
)
|
|
310
331
|
else:
|
|
311
|
-
|
|
332
|
+
result = await _fetch_history(
|
|
312
333
|
ws_client, self._client, entity_id_list,
|
|
313
334
|
start_dt, end_dt, minimal_response,
|
|
314
335
|
significant_changes_only, limit, offset,
|
|
315
336
|
_DEFAULT_HISTORY_LIMIT, _MAX_HISTORY_LIMIT,
|
|
316
337
|
)
|
|
338
|
+
if ctx is not None:
|
|
339
|
+
await ctx.report_progress(
|
|
340
|
+
progress=3,
|
|
341
|
+
total=3,
|
|
342
|
+
message="recorder query complete",
|
|
343
|
+
)
|
|
344
|
+
return result
|
|
317
345
|
finally:
|
|
318
346
|
if ws_client:
|
|
319
347
|
await ws_client.disconnect()
|
|
@@ -8,6 +8,7 @@ import asyncio
|
|
|
8
8
|
import logging
|
|
9
9
|
from typing import Annotated, Any, Literal, cast
|
|
10
10
|
|
|
11
|
+
from fastmcp import Context
|
|
11
12
|
from fastmcp.exceptions import ToolError
|
|
12
13
|
from pydantic import Field
|
|
13
14
|
|
|
@@ -823,6 +824,7 @@ def register_search_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
|
|
|
823
824
|
),
|
|
824
825
|
),
|
|
825
826
|
] = True,
|
|
827
|
+
ctx: Context | None = None,
|
|
826
828
|
) -> dict[str, Any]:
|
|
827
829
|
"""Search inside automation, script, helper, and dashboard *configurations* — not for finding entity IDs.
|
|
828
830
|
|
|
@@ -865,6 +867,7 @@ def register_search_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
|
|
|
865
867
|
offset,
|
|
866
868
|
include_config_bool,
|
|
867
869
|
exact_match=exact_match_bool,
|
|
870
|
+
ctx=ctx,
|
|
868
871
|
)
|
|
869
872
|
return cast(dict[str, Any], result)
|
|
870
873
|
except ToolError:
|
|
@@ -8,6 +8,7 @@ import logging
|
|
|
8
8
|
from typing import Annotated, Any, cast
|
|
9
9
|
|
|
10
10
|
import httpx
|
|
11
|
+
from fastmcp import Context
|
|
11
12
|
from fastmcp.exceptions import ToolError
|
|
12
13
|
from fastmcp.tools import tool
|
|
13
14
|
from pydantic import Field
|
|
@@ -393,7 +394,9 @@ class ServiceTools:
|
|
|
393
394
|
@log_tool_usage
|
|
394
395
|
async def ha_bulk_control(
|
|
395
396
|
self,
|
|
396
|
-
operations: str | list[dict[str, Any]],
|
|
397
|
+
operations: str | list[dict[str, Any]],
|
|
398
|
+
parallel: bool | str = True,
|
|
399
|
+
ctx: Context | None = None,
|
|
397
400
|
) -> dict[str, Any]:
|
|
398
401
|
"""Control multiple devices with bulk operation support and WebSocket tracking."""
|
|
399
402
|
# Coerce boolean parameter that may come as string from XML-style calls
|
|
@@ -424,7 +427,7 @@ class ServiceTools:
|
|
|
424
427
|
|
|
425
428
|
operations_list = cast(list[dict[str, Any]], parsed_operations)
|
|
426
429
|
result = await self._device_tools.bulk_device_control(
|
|
427
|
-
operations=operations_list, parallel=parallel_bool
|
|
430
|
+
operations=operations_list, parallel=parallel_bool, ctx=ctx
|
|
428
431
|
)
|
|
429
432
|
return cast(dict[str, Any], result)
|
|
430
433
|
|
|
@@ -9,6 +9,7 @@ import json
|
|
|
9
9
|
import logging
|
|
10
10
|
from typing import Annotated, Any
|
|
11
11
|
|
|
12
|
+
from fastmcp import Context
|
|
12
13
|
from fastmcp.exceptions import ToolError
|
|
13
14
|
from fastmcp.tools import tool
|
|
14
15
|
from pydantic import Field
|
|
@@ -91,6 +92,7 @@ class TraceTools:
|
|
|
91
92
|
default=None,
|
|
92
93
|
),
|
|
93
94
|
] = None,
|
|
95
|
+
ctx: Context | None = None,
|
|
94
96
|
) -> dict[str, Any]:
|
|
95
97
|
"""
|
|
96
98
|
Retrieve execution traces for automations and scripts to debug issues.
|
|
@@ -165,6 +167,17 @@ class TraceTools:
|
|
|
165
167
|
# Extract the object_id (part after the domain) as fallback
|
|
166
168
|
object_id = automation_id.split(".", 1)[1]
|
|
167
169
|
|
|
170
|
+
if ctx is not None:
|
|
171
|
+
await ctx.info(
|
|
172
|
+
f"ha_get_automation_traces starting: id={automation_id} "
|
|
173
|
+
f"run_id={run_id or '<list>'}"
|
|
174
|
+
)
|
|
175
|
+
await ctx.report_progress(
|
|
176
|
+
progress=0,
|
|
177
|
+
total=3,
|
|
178
|
+
message="connecting to Home Assistant WebSocket",
|
|
179
|
+
)
|
|
180
|
+
|
|
168
181
|
# Connect to WebSocket
|
|
169
182
|
ws_client, error = await get_connected_ws_client(
|
|
170
183
|
self._client.base_url,
|
|
@@ -185,6 +198,13 @@ class TraceTools:
|
|
|
185
198
|
ws_client, automation_id, object_id
|
|
186
199
|
)
|
|
187
200
|
|
|
201
|
+
if ctx is not None:
|
|
202
|
+
await ctx.report_progress(
|
|
203
|
+
progress=1,
|
|
204
|
+
total=3,
|
|
205
|
+
message=f"fetching trace {'detail' if run_id else 'list'}",
|
|
206
|
+
)
|
|
207
|
+
|
|
188
208
|
if run_id:
|
|
189
209
|
# Get specific trace details
|
|
190
210
|
result = await ws_client.send_command(
|
|
@@ -195,16 +215,20 @@ class TraceTools:
|
|
|
195
215
|
)
|
|
196
216
|
|
|
197
217
|
if not result.get("success"):
|
|
198
|
-
|
|
218
|
+
err_ctx: dict[str, str] = {"automation_id": automation_id}
|
|
199
219
|
if run_id:
|
|
200
|
-
|
|
220
|
+
err_ctx["run_id"] = run_id
|
|
201
221
|
raise_tool_error(create_error_response(
|
|
202
222
|
ErrorCode.SERVICE_CALL_FAILED,
|
|
203
223
|
result.get("error", "Failed to retrieve trace"),
|
|
204
|
-
context=
|
|
224
|
+
context=err_ctx,
|
|
205
225
|
))
|
|
206
226
|
|
|
207
227
|
trace_data = result.get("result", {})
|
|
228
|
+
if ctx is not None:
|
|
229
|
+
await ctx.report_progress(
|
|
230
|
+
progress=3, total=3, message="formatting trace"
|
|
231
|
+
)
|
|
208
232
|
return _format_detailed_trace(
|
|
209
233
|
automation_id, run_id, trace_data,
|
|
210
234
|
deduplicate=deduplicate, detailed=detailed,
|
|
@@ -229,13 +253,29 @@ class TraceTools:
|
|
|
229
253
|
|
|
230
254
|
# If traces are empty, gather diagnostic information
|
|
231
255
|
if not traces_data:
|
|
256
|
+
if ctx is not None:
|
|
257
|
+
await ctx.report_progress(
|
|
258
|
+
progress=2,
|
|
259
|
+
total=3,
|
|
260
|
+
message="no traces; gathering diagnostics",
|
|
261
|
+
)
|
|
232
262
|
diagnostics = await _gather_diagnostics(
|
|
233
263
|
ws_client, self._client, automation_id, domain
|
|
234
264
|
)
|
|
265
|
+
if ctx is not None:
|
|
266
|
+
await ctx.report_progress(
|
|
267
|
+
progress=3, total=3, message="diagnostics complete"
|
|
268
|
+
)
|
|
235
269
|
return _format_trace_list(
|
|
236
270
|
automation_id, traces_data, limit, diagnostics
|
|
237
271
|
)
|
|
238
272
|
|
|
273
|
+
if ctx is not None:
|
|
274
|
+
await ctx.report_progress(
|
|
275
|
+
progress=3,
|
|
276
|
+
total=3,
|
|
277
|
+
message=f"listed {len(traces_data)} traces",
|
|
278
|
+
)
|
|
239
279
|
return _format_trace_list(automation_id, traces_data, limit)
|
|
240
280
|
|
|
241
281
|
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.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/resources/skills-vendor/AGENTS.md
RENAMED
|
File without changes
|
{ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/resources/skills-vendor/CLAUDE.md
RENAMED
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/resources/skills-vendor/LICENSE
RENAMED
|
File without changes
|
{ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/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.dev452 → ha_mcp_dev-7.4.1.dev454}/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
|
|
File without changes
|
{ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/tools/tools_config_automations.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/tools/tools_config_dashboards.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/tools/tools_config_entry_flow.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp/tools/tools_config_helpers.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/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
|
{ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/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.dev452 → ha_mcp_dev-7.4.1.dev454}/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.dev452 → ha_mcp_dev-7.4.1.dev454}/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.dev452 → ha_mcp_dev-7.4.1.dev454}/src/ha_mcp_dev.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{ha_mcp_dev-7.4.1.dev452 → ha_mcp_dev-7.4.1.dev454}/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
|