ha-mcp-dev 7.3.0.dev395__tar.gz → 7.3.0.dev397__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.3.0.dev395/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.3.0.dev397}/PKG-INFO +1 -1
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/pyproject.toml +1 -1
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/server.py +71 -17
- ha_mcp_dev-7.3.0.dev397/src/ha_mcp/tools/reference_validator.py +275 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/tools/tools_config_automations.py +11 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/tools/tools_config_scripts.py +11 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/tools/util_helpers.py +31 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp_dev.egg-info/SOURCES.txt +1 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/LICENSE +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/MANIFEST.in +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/README.md +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/setup.cfg +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/__init__.py +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/__main__.py +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/_pypi_marker +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/auth/__init__.py +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/auth/consent_form.py +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/auth/provider.py +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/client/__init__.py +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/client/rest_client.py +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/client/websocket_client.py +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/client/websocket_listener.py +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/config.py +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/errors.py +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/py.typed +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/smoke_test.py +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/tools/__init__.py +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/tools/backup.py +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/tools/best_practice_checker.py +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/tools/device_control.py +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/tools/enhanced.py +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/tools/helpers.py +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/tools/registry.py +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/tools/smart_search.py +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/tools/tools_addons.py +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/tools/tools_areas.py +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/tools/tools_blueprints.py +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/tools/tools_bug_report.py +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/tools/tools_calendar.py +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/tools/tools_camera.py +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/tools/tools_categories.py +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/tools/tools_config_entry_flow.py +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/tools/tools_energy.py +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/tools/tools_entities.py +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/tools/tools_filesystem.py +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/tools/tools_groups.py +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/tools/tools_hacs.py +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/tools/tools_history.py +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/tools/tools_integrations.py +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/tools/tools_labels.py +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/tools/tools_mcp_component.py +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/tools/tools_registry.py +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/tools/tools_resources.py +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/tools/tools_search.py +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/tools/tools_service.py +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/tools/tools_services.py +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/tools/tools_system.py +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/tools/tools_todo.py +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/tools/tools_traces.py +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/tools/tools_updates.py +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/tools/tools_utility.py +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/tools/tools_zones.py +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/transforms/__init__.py +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/transforms/categorized_search.py +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/utils/__init__.py +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/utils/config_hash.py +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/utils/domain_handlers.py +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/utils/fuzzy_search.py +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/utils/operation_manager.py +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/utils/python_sandbox.py +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/utils/usage_logger.py +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/tests/__init__.py +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/tests/test_constants.py +0 -0
- {ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/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.3.0.
|
|
7
|
+
version = "7.3.0.dev397"
|
|
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"
|
|
@@ -143,6 +143,13 @@ class HomeAssistantSmartMCPServer(EnhancedToolsMixin):
|
|
|
143
143
|
# Register bundled skills as MCP resources
|
|
144
144
|
self._register_skills()
|
|
145
145
|
|
|
146
|
+
# Enrich tool descriptions with BM25 keyword boosts. Runs
|
|
147
|
+
# unconditionally so Claude's native deferred-tool search
|
|
148
|
+
# (claude.ai) benefits even when ENABLE_TOOL_SEARCH is off.
|
|
149
|
+
# Must come before _apply_tool_search so CategorizedSearchTransform
|
|
150
|
+
# indexes the enriched descriptions.
|
|
151
|
+
self._apply_search_keyword_enrichment()
|
|
152
|
+
|
|
146
153
|
# Apply tool search transform (must come after all tools and
|
|
147
154
|
# ResourcesAsTools are registered so it can wrap everything)
|
|
148
155
|
self._apply_tool_search()
|
|
@@ -336,8 +343,11 @@ class HomeAssistantSmartMCPServer(EnhancedToolsMixin):
|
|
|
336
343
|
)
|
|
337
344
|
|
|
338
345
|
# Extra keywords appended to tool descriptions for BM25 ranking.
|
|
339
|
-
#
|
|
340
|
-
#
|
|
346
|
+
# Applied unconditionally via SearchKeywordsTransform so they also
|
|
347
|
+
# improve retrieval for Claude's native deferred-tool search on
|
|
348
|
+
# claude.ai, which indexes tool names and descriptions with BM25
|
|
349
|
+
# (no semantic matching). Original tool docstrings stay unchanged;
|
|
350
|
+
# these keywords are appended by the transform at list-tools time.
|
|
341
351
|
_SEARCH_KEYWORDS: ClassVar[dict[str, str]] = {
|
|
342
352
|
# s02: "find entities" → ha_search_entities should outrank ha_deep_search
|
|
343
353
|
"ha_search_entities": (
|
|
@@ -390,8 +400,11 @@ class HomeAssistantSmartMCPServer(EnhancedToolsMixin):
|
|
|
390
400
|
}
|
|
391
401
|
|
|
392
402
|
# Description overrides that REPLACE the original description for BM25.
|
|
393
|
-
# Used to narrow overly broad tools so they stop matching generic queries
|
|
394
|
-
#
|
|
403
|
+
# Used to narrow overly broad tools so they stop matching generic queries
|
|
404
|
+
# against ha-mcp's internal BM25 search tool. Only applied when
|
|
405
|
+
# enable_tool_search=True, because they are tuned specifically for the
|
|
406
|
+
# categorized search transform and replacing the base description would
|
|
407
|
+
# unnecessarily trim context for other clients.
|
|
395
408
|
_SEARCH_DESCRIPTION_OVERRIDES: ClassVar[dict[str, str]] = {
|
|
396
409
|
"ha_deep_search": (
|
|
397
410
|
"Search INSIDE automation, script, and helper YAML configurations. "
|
|
@@ -402,6 +415,53 @@ class HomeAssistantSmartMCPServer(EnhancedToolsMixin):
|
|
|
402
415
|
),
|
|
403
416
|
}
|
|
404
417
|
|
|
418
|
+
def _apply_search_keyword_enrichment(self) -> None:
|
|
419
|
+
"""Append BM25 keyword boosts to tool descriptions.
|
|
420
|
+
|
|
421
|
+
Applied unconditionally so Claude's native deferred-tool search
|
|
422
|
+
(claude.ai uses BM25 over tool names and descriptions) can find
|
|
423
|
+
ha-mcp tools for common natural-language queries like "create
|
|
424
|
+
automation" — the scenario in #940. The original tool docstrings
|
|
425
|
+
in ``src/ha_mcp/tools/`` are unchanged; keywords are appended at
|
|
426
|
+
list-tools time via ``SearchKeywordsTransform``.
|
|
427
|
+
|
|
428
|
+
Description overrides (``_SEARCH_DESCRIPTION_OVERRIDES``) are only
|
|
429
|
+
applied when ``enable_tool_search`` is also set, because they
|
|
430
|
+
REPLACE the original description and are tuned specifically for
|
|
431
|
+
ha-mcp's internal BM25 search tool.
|
|
432
|
+
|
|
433
|
+
Runs before ``_apply_tool_search`` so downstream transforms
|
|
434
|
+
index the enriched descriptions.
|
|
435
|
+
"""
|
|
436
|
+
try:
|
|
437
|
+
from .transforms import SearchKeywordsTransform
|
|
438
|
+
except ImportError:
|
|
439
|
+
logger.warning(
|
|
440
|
+
"SearchKeywordsTransform not available; skipping description "
|
|
441
|
+
"enrichment (tool discoverability on claude.ai may be degraded)."
|
|
442
|
+
)
|
|
443
|
+
return
|
|
444
|
+
|
|
445
|
+
overrides = (
|
|
446
|
+
self._SEARCH_DESCRIPTION_OVERRIDES
|
|
447
|
+
if self.settings.enable_tool_search
|
|
448
|
+
else None
|
|
449
|
+
)
|
|
450
|
+
try:
|
|
451
|
+
self.mcp.add_transform(
|
|
452
|
+
SearchKeywordsTransform(
|
|
453
|
+
keywords=self._SEARCH_KEYWORDS,
|
|
454
|
+
overrides=overrides,
|
|
455
|
+
)
|
|
456
|
+
)
|
|
457
|
+
logger.info(
|
|
458
|
+
"Search keyword enrichment applied (%d boosts%s)",
|
|
459
|
+
len(self._SEARCH_KEYWORDS),
|
|
460
|
+
f", {len(overrides)} overrides" if overrides else "",
|
|
461
|
+
)
|
|
462
|
+
except Exception:
|
|
463
|
+
logger.exception("Failed to apply SearchKeywordsTransform")
|
|
464
|
+
|
|
405
465
|
def _apply_tool_search(self) -> None:
|
|
406
466
|
"""Apply the CategorizedSearchTransform if enabled.
|
|
407
467
|
|
|
@@ -410,6 +470,10 @@ class HomeAssistantSmartMCPServer(EnhancedToolsMixin):
|
|
|
410
470
|
remain directly visible in list_tools() for individual permission
|
|
411
471
|
gating. ResourcesAsTools (list_resources/read_resource) are also
|
|
412
472
|
pinned when enabled.
|
|
473
|
+
|
|
474
|
+
Note: ``_apply_search_keyword_enrichment`` already ran before this
|
|
475
|
+
method and installed ``SearchKeywordsTransform`` — the enriched
|
|
476
|
+
catalog is what the categorized transform indexes.
|
|
413
477
|
"""
|
|
414
478
|
if not self.settings.enable_tool_search:
|
|
415
479
|
return
|
|
@@ -447,18 +511,6 @@ class HomeAssistantSmartMCPServer(EnhancedToolsMixin):
|
|
|
447
511
|
)
|
|
448
512
|
|
|
449
513
|
try:
|
|
450
|
-
# Enrich tool descriptions for BM25 ranking (innermost transform).
|
|
451
|
-
# Added first so the search transform indexes enriched descriptions.
|
|
452
|
-
# Original tool docstrings are unchanged.
|
|
453
|
-
from .transforms import SearchKeywordsTransform
|
|
454
|
-
|
|
455
|
-
self.mcp.add_transform(
|
|
456
|
-
SearchKeywordsTransform(
|
|
457
|
-
keywords=self._SEARCH_KEYWORDS,
|
|
458
|
-
overrides=self._SEARCH_DESCRIPTION_OVERRIDES,
|
|
459
|
-
)
|
|
460
|
-
)
|
|
461
|
-
|
|
462
514
|
self.mcp.add_transform(
|
|
463
515
|
CategorizedSearchTransform(
|
|
464
516
|
max_results=5,
|
|
@@ -623,7 +675,9 @@ class HomeAssistantSmartMCPServer(EnhancedToolsMixin):
|
|
|
623
675
|
if not f.resolve().is_relative_to(resolved_root):
|
|
624
676
|
continue
|
|
625
677
|
rel = f.relative_to(skill_dir)
|
|
626
|
-
ref_files.append(
|
|
678
|
+
ref_files.append(
|
|
679
|
+
{"name": str(rel), "uri": f"skill://{skill_name}/{rel}"}
|
|
680
|
+
)
|
|
627
681
|
except OSError:
|
|
628
682
|
logger.warning("Error reading skill files in %s", skill_dir)
|
|
629
683
|
return ref_files
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
"""Reference validator for automation and script configs.
|
|
2
|
+
|
|
3
|
+
Walks a config dict and extracts every literal service and entity
|
|
4
|
+
reference, then cross-checks them against the live service and entity
|
|
5
|
+
registries. Produces soft warnings that flow into the existing response
|
|
6
|
+
alongside ``best_practice_warnings``.
|
|
7
|
+
|
|
8
|
+
Intentional limits (documented, not bugs):
|
|
9
|
+
|
|
10
|
+
- **Templates** (strings containing ``{{``) are counted and skipped.
|
|
11
|
+
Jinja is not rendered here; template-safe validation would need
|
|
12
|
+
``POST /api/template`` round-trips and is a later follow-up.
|
|
13
|
+
- **Blueprint automations** (``use_blueprint`` at the root) are skipped
|
|
14
|
+
wholesale. Post-substitution config is not exposed by any HA API, so
|
|
15
|
+
the effective refs cannot be ground-truthed.
|
|
16
|
+
- **``device_id`` / ``area_id`` / ``label_id``** are NOT checked yet.
|
|
17
|
+
They require separate registry fetches and are planned for a later
|
|
18
|
+
pass; see #940.
|
|
19
|
+
|
|
20
|
+
Background: #940 (hallucinated ``notify.mobile_app_andrew_phone`` that
|
|
21
|
+
``ha_config_set_automation`` accepted silently).
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
import asyncio
|
|
27
|
+
import logging
|
|
28
|
+
from typing import Any, TypedDict
|
|
29
|
+
|
|
30
|
+
logger = logging.getLogger(__name__)
|
|
31
|
+
|
|
32
|
+
# Keys whose value (literal string) names a Home Assistant service in
|
|
33
|
+
# the form ``<domain>.<service_name>``. Both legacy (``service:``) and
|
|
34
|
+
# the new-style (``action:``) keys are recognized — HA accepts either
|
|
35
|
+
# inside automation/script action blocks.
|
|
36
|
+
_SERVICE_KEYS: frozenset[str] = frozenset({"service", "action"})
|
|
37
|
+
|
|
38
|
+
# Keys whose value (string or list of strings) names an entity. These
|
|
39
|
+
# appear in triggers, conditions, ``target:`` blocks, and service
|
|
40
|
+
# ``data:`` blocks — the walker is depth-agnostic so the location
|
|
41
|
+
# doesn't matter.
|
|
42
|
+
_ENTITY_KEYS: frozenset[str] = frozenset({"entity_id"})
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class ExtractedRef(TypedDict):
|
|
46
|
+
"""One reference pulled out of the config tree."""
|
|
47
|
+
|
|
48
|
+
path: str
|
|
49
|
+
value: str
|
|
50
|
+
kind: str # "service" | "entity"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class WalkerResult(TypedDict):
|
|
54
|
+
"""Return value of :func:`extract_refs`."""
|
|
55
|
+
|
|
56
|
+
refs: list[ExtractedRef]
|
|
57
|
+
unvalidated_templates: int
|
|
58
|
+
blueprint_skipped: bool
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class ValidationWarning(TypedDict):
|
|
62
|
+
"""One warning in the tool response."""
|
|
63
|
+
|
|
64
|
+
path: str
|
|
65
|
+
value: str
|
|
66
|
+
kind: str
|
|
67
|
+
reason: str
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def extract_refs(config: Any) -> WalkerResult:
|
|
71
|
+
"""Pull every literal service/entity reference out of *config*.
|
|
72
|
+
|
|
73
|
+
Pure function: no network, no mutation of the input. The caller
|
|
74
|
+
decides what to do with the extracted refs.
|
|
75
|
+
|
|
76
|
+
Blueprint configs short-circuit with an empty ref list and
|
|
77
|
+
``blueprint_skipped=True`` — the effective post-substitution config
|
|
78
|
+
is not reachable from ha-mcp, so it cannot be validated.
|
|
79
|
+
"""
|
|
80
|
+
if isinstance(config, dict) and "use_blueprint" in config:
|
|
81
|
+
return {
|
|
82
|
+
"refs": [],
|
|
83
|
+
"unvalidated_templates": 0,
|
|
84
|
+
"blueprint_skipped": True,
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
refs: list[ExtractedRef] = []
|
|
88
|
+
# Wrapped in a single-element list so the inner closure can mutate
|
|
89
|
+
# it without a ``nonlocal`` dance.
|
|
90
|
+
unvalidated_templates = [0]
|
|
91
|
+
|
|
92
|
+
def _walk(node: Any, path: str) -> None:
|
|
93
|
+
if isinstance(node, dict):
|
|
94
|
+
for key, value in node.items():
|
|
95
|
+
sub_path = f"{path}.{key}" if path else key
|
|
96
|
+
|
|
97
|
+
if key in _SERVICE_KEYS and isinstance(value, str):
|
|
98
|
+
if _is_template(value):
|
|
99
|
+
unvalidated_templates[0] += 1
|
|
100
|
+
else:
|
|
101
|
+
refs.append(
|
|
102
|
+
{"path": sub_path, "value": value, "kind": "service"}
|
|
103
|
+
)
|
|
104
|
+
continue
|
|
105
|
+
|
|
106
|
+
if key in _ENTITY_KEYS:
|
|
107
|
+
if isinstance(value, str):
|
|
108
|
+
if _is_template(value):
|
|
109
|
+
unvalidated_templates[0] += 1
|
|
110
|
+
else:
|
|
111
|
+
refs.append(
|
|
112
|
+
{"path": sub_path, "value": value, "kind": "entity"}
|
|
113
|
+
)
|
|
114
|
+
continue
|
|
115
|
+
if isinstance(value, list):
|
|
116
|
+
for i, item in enumerate(value):
|
|
117
|
+
if not isinstance(item, str):
|
|
118
|
+
continue
|
|
119
|
+
item_path = f"{sub_path}[{i}]"
|
|
120
|
+
if _is_template(item):
|
|
121
|
+
unvalidated_templates[0] += 1
|
|
122
|
+
else:
|
|
123
|
+
refs.append(
|
|
124
|
+
{
|
|
125
|
+
"path": item_path,
|
|
126
|
+
"value": item,
|
|
127
|
+
"kind": "entity",
|
|
128
|
+
}
|
|
129
|
+
)
|
|
130
|
+
continue
|
|
131
|
+
|
|
132
|
+
# Neither a service nor an entity key: recurse so deeply
|
|
133
|
+
# nested action blocks (choose/if/parallel/repeat) still
|
|
134
|
+
# get walked.
|
|
135
|
+
_walk(value, sub_path)
|
|
136
|
+
|
|
137
|
+
elif isinstance(node, list):
|
|
138
|
+
for i, item in enumerate(node):
|
|
139
|
+
_walk(item, f"{path}[{i}]")
|
|
140
|
+
# Primitives: nothing to extract.
|
|
141
|
+
|
|
142
|
+
_walk(config, "")
|
|
143
|
+
return {
|
|
144
|
+
"refs": refs,
|
|
145
|
+
"unvalidated_templates": unvalidated_templates[0],
|
|
146
|
+
"blueprint_skipped": False,
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _is_template(value: str) -> bool:
|
|
151
|
+
"""Return True if *value* looks like a Jinja template."""
|
|
152
|
+
return "{{" in value
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def build_service_index(services_payload: Any) -> dict[str, set[str]]:
|
|
156
|
+
"""Turn ``/api/services`` output into a ``{domain: {services}}`` map.
|
|
157
|
+
|
|
158
|
+
HA returns a list of ``{"domain": str, "services": {name: {...}}}``
|
|
159
|
+
objects — one per domain. Any malformed entry is skipped silently.
|
|
160
|
+
"""
|
|
161
|
+
index: dict[str, set[str]] = {}
|
|
162
|
+
if not isinstance(services_payload, list):
|
|
163
|
+
return index
|
|
164
|
+
for entry in services_payload:
|
|
165
|
+
if not isinstance(entry, dict):
|
|
166
|
+
continue
|
|
167
|
+
domain = entry.get("domain")
|
|
168
|
+
services = entry.get("services")
|
|
169
|
+
if not isinstance(domain, str) or not isinstance(services, dict):
|
|
170
|
+
continue
|
|
171
|
+
index[domain] = set(services.keys())
|
|
172
|
+
return index
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def build_entity_set(states_payload: Any) -> set[str]:
|
|
176
|
+
"""Turn ``/api/states`` output into a set of entity_ids."""
|
|
177
|
+
entities: set[str] = set()
|
|
178
|
+
if not isinstance(states_payload, list):
|
|
179
|
+
return entities
|
|
180
|
+
for entry in states_payload:
|
|
181
|
+
if isinstance(entry, dict):
|
|
182
|
+
entity_id = entry.get("entity_id")
|
|
183
|
+
if isinstance(entity_id, str):
|
|
184
|
+
entities.add(entity_id)
|
|
185
|
+
return entities
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def check_refs(
|
|
189
|
+
refs: list[ExtractedRef],
|
|
190
|
+
service_index: dict[str, set[str]],
|
|
191
|
+
entity_set: set[str],
|
|
192
|
+
) -> list[ValidationWarning]:
|
|
193
|
+
"""Return one warning per ref that isn't in the registry."""
|
|
194
|
+
warnings: list[ValidationWarning] = []
|
|
195
|
+
for ref in refs:
|
|
196
|
+
value = ref["value"]
|
|
197
|
+
if ref["kind"] == "service":
|
|
198
|
+
domain, _, service_name = value.partition(".")
|
|
199
|
+
if (
|
|
200
|
+
not service_name
|
|
201
|
+
or domain not in service_index
|
|
202
|
+
or service_name not in service_index[domain]
|
|
203
|
+
):
|
|
204
|
+
warnings.append(
|
|
205
|
+
{
|
|
206
|
+
"path": ref["path"],
|
|
207
|
+
"value": value,
|
|
208
|
+
"kind": "service",
|
|
209
|
+
"reason": "not found in service registry",
|
|
210
|
+
}
|
|
211
|
+
)
|
|
212
|
+
elif ref["kind"] == "entity":
|
|
213
|
+
if value not in entity_set:
|
|
214
|
+
warnings.append(
|
|
215
|
+
{
|
|
216
|
+
"path": ref["path"],
|
|
217
|
+
"value": value,
|
|
218
|
+
"kind": "entity",
|
|
219
|
+
"reason": "not found in entity registry",
|
|
220
|
+
}
|
|
221
|
+
)
|
|
222
|
+
return warnings
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
async def validate_config_references(
|
|
226
|
+
client: Any, config: dict[str, Any]
|
|
227
|
+
) -> dict[str, Any]:
|
|
228
|
+
"""Walk *config*, fetch registries, return validation metadata.
|
|
229
|
+
|
|
230
|
+
Errors from the two registry fetches are logged and swallowed so
|
|
231
|
+
validation can never break the happy path of
|
|
232
|
+
``ha_config_set_automation`` / ``ha_config_set_script``.
|
|
233
|
+
|
|
234
|
+
Returns a dict with three keys:
|
|
235
|
+
|
|
236
|
+
- ``warnings`` - list of :class:`ValidationWarning`, empty on success
|
|
237
|
+
- ``unvalidated_templates`` - int, templated strings skipped by the
|
|
238
|
+
walker
|
|
239
|
+
- ``blueprint_skipped`` - bool, True iff the root config uses
|
|
240
|
+
``use_blueprint``
|
|
241
|
+
"""
|
|
242
|
+
walker_result = extract_refs(config)
|
|
243
|
+
|
|
244
|
+
if walker_result["blueprint_skipped"] or not walker_result["refs"]:
|
|
245
|
+
return {
|
|
246
|
+
"warnings": [],
|
|
247
|
+
"unvalidated_templates": walker_result["unvalidated_templates"],
|
|
248
|
+
"blueprint_skipped": walker_result["blueprint_skipped"],
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
try:
|
|
252
|
+
services_payload, states_payload = await asyncio.gather(
|
|
253
|
+
client.get_services(),
|
|
254
|
+
client.get_states(),
|
|
255
|
+
)
|
|
256
|
+
except Exception:
|
|
257
|
+
logger.exception(
|
|
258
|
+
"Reference validator: failed to fetch service/entity registries; "
|
|
259
|
+
"skipping validation for this call"
|
|
260
|
+
)
|
|
261
|
+
return {
|
|
262
|
+
"warnings": [],
|
|
263
|
+
"unvalidated_templates": walker_result["unvalidated_templates"],
|
|
264
|
+
"blueprint_skipped": False,
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
service_index = build_service_index(services_payload)
|
|
268
|
+
entity_set = build_entity_set(states_payload)
|
|
269
|
+
warnings = check_refs(walker_result["refs"], service_index, entity_set)
|
|
270
|
+
|
|
271
|
+
return {
|
|
272
|
+
"warnings": warnings,
|
|
273
|
+
"unvalidated_templates": walker_result["unvalidated_templates"],
|
|
274
|
+
"blueprint_skipped": False,
|
|
275
|
+
}
|
{ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/tools/tools_config_automations.py
RENAMED
|
@@ -37,10 +37,12 @@ from .helpers import (
|
|
|
37
37
|
raise_tool_error,
|
|
38
38
|
register_tool_methods,
|
|
39
39
|
)
|
|
40
|
+
from .reference_validator import validate_config_references
|
|
40
41
|
from .util_helpers import (
|
|
41
42
|
apply_entity_category,
|
|
42
43
|
coerce_bool_param,
|
|
43
44
|
fetch_entity_category,
|
|
45
|
+
merge_validation_meta,
|
|
44
46
|
parse_json_param,
|
|
45
47
|
wait_for_entity_registered,
|
|
46
48
|
wait_for_entity_removed,
|
|
@@ -641,6 +643,13 @@ class AutomationConfigTools:
|
|
|
641
643
|
config_dict, skill_prefix=_get_skill_prefix()
|
|
642
644
|
)
|
|
643
645
|
|
|
646
|
+
# Cross-check literal service and entity references against
|
|
647
|
+
# the live registries. Soft warnings only — the write still
|
|
648
|
+
# happens, even when references don't resolve (#940).
|
|
649
|
+
validation_meta = await validate_config_references(
|
|
650
|
+
self._client, config_dict
|
|
651
|
+
)
|
|
652
|
+
|
|
644
653
|
result = await self._client.upsert_automation_config(config_dict, identifier)
|
|
645
654
|
|
|
646
655
|
# If the client could not verify the entity was registered, warn but don't hard-fail.
|
|
@@ -677,6 +686,8 @@ class AutomationConfigTools:
|
|
|
677
686
|
if bp_warnings:
|
|
678
687
|
result["best_practice_warnings"] = bp_warnings
|
|
679
688
|
|
|
689
|
+
merge_validation_meta(result, validation_meta)
|
|
690
|
+
|
|
680
691
|
return {
|
|
681
692
|
"success": True,
|
|
682
693
|
**result,
|
{ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/tools/tools_config_scripts.py
RENAMED
|
@@ -31,10 +31,12 @@ from .helpers import (
|
|
|
31
31
|
raise_tool_error,
|
|
32
32
|
register_tool_methods,
|
|
33
33
|
)
|
|
34
|
+
from .reference_validator import validate_config_references
|
|
34
35
|
from .util_helpers import (
|
|
35
36
|
apply_entity_category,
|
|
36
37
|
coerce_bool_param,
|
|
37
38
|
fetch_entity_category,
|
|
39
|
+
merge_validation_meta,
|
|
38
40
|
parse_json_param,
|
|
39
41
|
wait_for_entity_registered,
|
|
40
42
|
wait_for_entity_removed,
|
|
@@ -523,6 +525,13 @@ class ConfigScriptTools:
|
|
|
523
525
|
config_dict, skill_prefix=_get_skill_prefix()
|
|
524
526
|
)
|
|
525
527
|
|
|
528
|
+
# Cross-check literal service and entity references against
|
|
529
|
+
# the live registries. Soft warnings only — the write still
|
|
530
|
+
# happens, even when references don't resolve (#940).
|
|
531
|
+
validation_meta = await validate_config_references(
|
|
532
|
+
self._client, config_dict
|
|
533
|
+
)
|
|
534
|
+
|
|
526
535
|
result = await self._client.upsert_script_config(config_dict, script_id)
|
|
527
536
|
|
|
528
537
|
# Wait for script to be queryable
|
|
@@ -545,6 +554,8 @@ class ConfigScriptTools:
|
|
|
545
554
|
if bp_warnings:
|
|
546
555
|
result["best_practice_warnings"] = bp_warnings
|
|
547
556
|
|
|
557
|
+
merge_validation_meta(result, validation_meta)
|
|
558
|
+
|
|
548
559
|
return {
|
|
549
560
|
"success": True,
|
|
550
561
|
**result,
|
|
@@ -530,3 +530,34 @@ async def apply_entity_category(
|
|
|
530
530
|
result_dict["category_warning"] = (
|
|
531
531
|
f"{entity_type.capitalize()} saved but failed to set category: {e}"
|
|
532
532
|
)
|
|
533
|
+
|
|
534
|
+
|
|
535
|
+
def merge_validation_meta(
|
|
536
|
+
result: dict[str, Any], validation_meta: dict[str, Any]
|
|
537
|
+
) -> None:
|
|
538
|
+
"""Attach reference-validator output to a set-tool success ``result``.
|
|
539
|
+
|
|
540
|
+
Produces a single nested ``validation`` field when there's anything
|
|
541
|
+
worth reporting - warnings, skipped templates, or a blueprint
|
|
542
|
+
short-circuit. Keeps the happy-path response unchanged.
|
|
543
|
+
|
|
544
|
+
Shared between ``ha_config_set_automation`` and
|
|
545
|
+
``ha_config_set_script``; see
|
|
546
|
+
:mod:`ha_mcp.tools.reference_validator` for the validator itself
|
|
547
|
+
and #940 for background.
|
|
548
|
+
"""
|
|
549
|
+
warnings = validation_meta.get("warnings") or []
|
|
550
|
+
unvalidated_templates = validation_meta.get("unvalidated_templates") or 0
|
|
551
|
+
blueprint_skipped = bool(validation_meta.get("blueprint_skipped"))
|
|
552
|
+
|
|
553
|
+
if not warnings and not unvalidated_templates and not blueprint_skipped:
|
|
554
|
+
return
|
|
555
|
+
|
|
556
|
+
entry: dict[str, Any] = {}
|
|
557
|
+
if warnings:
|
|
558
|
+
entry["warnings"] = warnings
|
|
559
|
+
if unvalidated_templates:
|
|
560
|
+
entry["unvalidated_templates"] = unvalidated_templates
|
|
561
|
+
if blueprint_skipped:
|
|
562
|
+
entry["blueprint_skipped"] = True
|
|
563
|
+
result["validation"] = entry
|
|
@@ -43,6 +43,7 @@ src/ha_mcp/tools/best_practice_checker.py
|
|
|
43
43
|
src/ha_mcp/tools/device_control.py
|
|
44
44
|
src/ha_mcp/tools/enhanced.py
|
|
45
45
|
src/ha_mcp/tools/helpers.py
|
|
46
|
+
src/ha_mcp/tools/reference_validator.py
|
|
46
47
|
src/ha_mcp/tools/registry.py
|
|
47
48
|
src/ha_mcp/tools/smart_search.py
|
|
48
49
|
src/ha_mcp/tools/tools_addons.py
|
|
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.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/resources/skills-vendor/AGENTS.md
RENAMED
|
File without changes
|
{ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/resources/skills-vendor/CLAUDE.md
RENAMED
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/resources/skills-vendor/LICENSE
RENAMED
|
File without changes
|
{ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/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
|
{ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/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.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/tools/tools_config_dashboards.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/tools/tools_config_entry_flow.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/tools/tools_config_helpers.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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp/tools/tools_voice_assistant.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/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
|
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/src/ha_mcp_dev.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{ha_mcp_dev-7.3.0.dev395 → ha_mcp_dev-7.3.0.dev397}/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
|