ha-mcp-dev 7.4.1.dev447__tar.gz → 7.4.1.dev449__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. {ha_mcp_dev-7.4.1.dev447/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.4.1.dev449}/PKG-INFO +1 -1
  2. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/pyproject.toml +1 -1
  3. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/tools/tools_search.py +73 -51
  4. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
  5. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/LICENSE +0 -0
  6. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/MANIFEST.in +0 -0
  7. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/README.md +0 -0
  8. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/setup.cfg +0 -0
  9. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/__init__.py +0 -0
  10. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/__main__.py +0 -0
  11. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/_pypi_marker +0 -0
  12. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/_version.py +0 -0
  13. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/auth/__init__.py +0 -0
  14. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/auth/consent_form.py +0 -0
  15. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/auth/provider.py +0 -0
  16. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/client/__init__.py +0 -0
  17. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/client/rest_client.py +0 -0
  18. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/client/websocket_client.py +0 -0
  19. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/client/websocket_listener.py +0 -0
  20. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/config.py +0 -0
  21. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/errors.py +0 -0
  22. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/py.typed +0 -0
  23. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
  24. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
  25. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
  26. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
  27. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
  28. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
  29. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
  30. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
  31. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
  32. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
  33. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
  34. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
  35. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
  36. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
  37. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
  38. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
  39. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
  40. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
  41. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
  42. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
  43. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/server.py +0 -0
  44. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/settings_ui.py +0 -0
  45. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/smoke_test.py +0 -0
  46. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/tools/__init__.py +0 -0
  47. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/tools/backup.py +0 -0
  48. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/tools/best_practice_checker.py +0 -0
  49. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/tools/device_control.py +0 -0
  50. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/tools/enhanced.py +0 -0
  51. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/tools/helpers.py +0 -0
  52. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/tools/reference_validator.py +0 -0
  53. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/tools/registry.py +0 -0
  54. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/tools/smart_search.py +0 -0
  55. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/tools/tools_addons.py +0 -0
  56. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/tools/tools_areas.py +0 -0
  57. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/tools/tools_blueprints.py +0 -0
  58. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/tools/tools_bug_report.py +0 -0
  59. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/tools/tools_calendar.py +0 -0
  60. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/tools/tools_camera.py +0 -0
  61. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/tools/tools_categories.py +0 -0
  62. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/tools/tools_code.py +0 -0
  63. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/tools/tools_config_automations.py +0 -0
  64. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
  65. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/tools/tools_config_entry_flow.py +0 -0
  66. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
  67. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
  68. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/tools/tools_energy.py +0 -0
  69. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/tools/tools_entities.py +0 -0
  70. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/tools/tools_filesystem.py +0 -0
  71. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/tools/tools_groups.py +0 -0
  72. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/tools/tools_hacs.py +0 -0
  73. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/tools/tools_history.py +0 -0
  74. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/tools/tools_integrations.py +0 -0
  75. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/tools/tools_labels.py +0 -0
  76. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/tools/tools_mcp_component.py +0 -0
  77. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/tools/tools_registry.py +0 -0
  78. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/tools/tools_resources.py +0 -0
  79. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/tools/tools_service.py +0 -0
  80. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/tools/tools_services.py +0 -0
  81. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/tools/tools_system.py +0 -0
  82. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/tools/tools_todo.py +0 -0
  83. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/tools/tools_traces.py +0 -0
  84. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/tools/tools_updates.py +0 -0
  85. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/tools/tools_utility.py +0 -0
  86. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
  87. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
  88. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/tools/tools_zones.py +0 -0
  89. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/tools/util_helpers.py +0 -0
  90. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/transforms/__init__.py +0 -0
  91. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/transforms/categorized_search.py +0 -0
  92. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/utils/__init__.py +0 -0
  93. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/utils/config_hash.py +0 -0
  94. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/utils/data_paths.py +0 -0
  95. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/utils/domain_handlers.py +0 -0
  96. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/utils/fuzzy_search.py +0 -0
  97. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
  98. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/utils/operation_manager.py +0 -0
  99. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/utils/python_sandbox.py +0 -0
  100. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp/utils/usage_logger.py +0 -0
  101. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
  102. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
  103. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
  104. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
  105. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
  106. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/tests/__init__.py +0 -0
  107. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/tests/test_constants.py +0 -0
  108. {ha_mcp_dev-7.4.1.dev447 → ha_mcp_dev-7.4.1.dev449}/tests/test_env_manager.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ha-mcp-dev
3
- Version: 7.4.1.dev447
3
+ Version: 7.4.1.dev449
4
4
  Summary: Home Assistant MCP Server - Complete control of Home Assistant through MCP
5
5
  Author-email: Julien <github@qc-h.net>
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ha-mcp-dev"
7
- version = "7.4.1.dev447"
7
+ version = "7.4.1.dev449"
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"
@@ -32,20 +32,13 @@ def _build_pagination_metadata(
32
32
  """Build standardized pagination metadata for search responses.
33
33
 
34
34
  Thin wrapper around the shared ``build_pagination_metadata`` helper that
35
- keeps the existing call-site signature (accepts a *results* list and uses
36
- ``total_matches`` as the key name expected by search tools).
35
+ keeps the existing call-site signature (accepts a *results* list) and
36
+ renames ``total_count`` ``total_matches`` to match the search tools'
37
+ response shape.
37
38
  """
38
39
  meta = build_pagination_metadata(total_matches, offset, limit, len(results))
39
- # Search tools use "total_matches" instead of "total_count"
40
- # construct explicitly to avoid fragile dependency on shared helper's key names
41
- return {
42
- "total_matches": meta["total_count"],
43
- "offset": meta["offset"],
44
- "limit": meta["limit"],
45
- "count": meta["count"],
46
- "has_more": meta["has_more"],
47
- "next_offset": meta["next_offset"],
48
- }
40
+ meta["total_matches"] = meta.pop("total_count")
41
+ return meta
49
42
 
50
43
 
51
44
  async def _exact_match_search(
@@ -241,20 +234,18 @@ def register_search_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
241
234
 
242
235
  # If we also have a query, filter the area results
243
236
  if query and query.strip():
244
- # Get all entities from all areas in the result
237
+ # Collect entities from all matched areas, applying
238
+ # domain_filter if present. get_entities_by_area is called
239
+ # with group_by_domain=True above, so entities is always a
240
+ # dict keyed by domain.
245
241
  all_area_entities = []
246
- if "areas" in area_result:
247
- for area_data in area_result["areas"].values():
248
- if "entities" in area_data:
249
- if isinstance(
250
- area_data["entities"], dict
251
- ): # grouped by domain
252
- for domain_entities in area_data[
253
- "entities"
254
- ].values():
255
- all_area_entities.extend(domain_entities)
256
- else: # flat list
257
- all_area_entities.extend(area_data["entities"])
242
+ for area_data in area_result.get("areas", {}).values():
243
+ entities = area_data.get("entities") or {}
244
+ if domain_filter:
245
+ all_area_entities.extend(entities.get(domain_filter, []))
246
+ else:
247
+ for domain_entities in entities.values():
248
+ all_area_entities.extend(domain_entities)
258
249
 
259
250
  # Apply fuzzy search to area entities
260
251
  from ..utils.fuzzy_search import create_fuzzy_searcher
@@ -303,6 +294,8 @@ def register_search_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
303
294
  "results": results,
304
295
  "search_type": "area_filtered_query",
305
296
  }
297
+ if domain_filter:
298
+ search_data["domain_filter"] = domain_filter
306
299
 
307
300
  if group_by_domain_bool:
308
301
  by_domain: dict[str, list[dict[str, Any]]] = {}
@@ -318,35 +311,58 @@ def register_search_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
318
311
  # Just area filter, return area results with enhanced format
319
312
  if area_result.get("areas"):
320
313
  first_area = next(iter(area_result["areas"].values()))
321
- by_domain = first_area.get("entities", {})
314
+ entities_data = first_area.get("entities")
315
+
316
+ # Build a flat results list, applying domain_filter and
317
+ # tagging each entity with its `domain` so the optional
318
+ # by_domain rebuild below can group without re-parsing
319
+ # entity_id. `{**entity, "domain": domain}` avoids
320
+ # mutating dicts owned by the helper.
321
+ all_results: list[dict[str, Any]] = []
322
+ for domain, entities in (entities_data or {}).items():
323
+ if domain_filter and domain != domain_filter:
324
+ continue
325
+ all_results.extend(
326
+ {**entity, "domain": domain} for entity in entities
327
+ )
322
328
 
323
- # Flatten for results while keeping by_domain structure
324
- all_results = []
325
- for domain, entities in by_domain.items():
326
- for entity in entities:
327
- entity["domain"] = domain
328
- all_results.append(entity)
329
+ paginated = all_results[offset : offset + limit]
329
330
 
330
- area_search_data = {
331
+ area_search_data: dict[str, Any] = {
331
332
  "success": True,
332
333
  "area_filter": area_filter,
333
- "total_matches": len(all_results),
334
- "results": all_results,
335
- "by_domain": by_domain,
334
+ **_build_pagination_metadata(
335
+ len(all_results), offset, limit, paginated
336
+ ),
337
+ "results": paginated,
336
338
  "search_type": "area_only",
337
339
  "area_name": first_area.get("area_name", area_filter),
338
340
  }
341
+ if domain_filter:
342
+ area_search_data["domain_filter"] = domain_filter
343
+ if group_by_domain_bool:
344
+ # Group the paginated slice (not all_results) so
345
+ # by_domain and results stay in sync.
346
+ paginated_by_domain: dict[str, list[dict[str, Any]]] = {}
347
+ for entity in paginated:
348
+ paginated_by_domain.setdefault(
349
+ entity["domain"], []
350
+ ).append(entity)
351
+ area_search_data["by_domain"] = paginated_by_domain
339
352
  return await add_timezone_metadata(client, area_search_data)
340
353
  else:
341
- empty_area_data = {
354
+ empty_area_data: dict[str, Any] = {
342
355
  "success": True,
343
356
  "area_filter": area_filter,
344
- "total_matches": 0,
357
+ **_build_pagination_metadata(0, offset, limit, []),
345
358
  "results": [],
346
- "by_domain": {},
347
359
  "search_type": "area_only",
348
360
  "message": f"No entities found in area: {area_filter}",
349
361
  }
362
+ if domain_filter:
363
+ empty_area_data["domain_filter"] = domain_filter
364
+ if group_by_domain_bool:
365
+ empty_area_data["by_domain"] = {}
350
366
  return await add_timezone_metadata(client, empty_area_data)
351
367
 
352
368
  # Regular entity search (no area filter)
@@ -929,22 +945,28 @@ def register_search_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
929
945
  MAX_ENTITIES = 100
930
946
 
931
947
  if not isinstance(entity_ids, list) or not entity_ids:
932
- raise_tool_error(create_validation_error(
933
- "entity_id must be a non-empty string or list of entity ID strings",
934
- parameter="entity_id",
935
- ))
948
+ raise_tool_error(
949
+ create_validation_error(
950
+ "entity_id must be a non-empty string or list of entity ID strings",
951
+ parameter="entity_id",
952
+ )
953
+ )
936
954
 
937
955
  if not all(isinstance(eid, str) for eid in entity_ids):
938
- raise_tool_error(create_validation_error(
939
- "All entity_id values must be strings",
940
- parameter="entity_id",
941
- ))
956
+ raise_tool_error(
957
+ create_validation_error(
958
+ "All entity_id values must be strings",
959
+ parameter="entity_id",
960
+ )
961
+ )
942
962
 
943
963
  if len(entity_ids) > MAX_ENTITIES:
944
- raise_tool_error(create_validation_error(
945
- f"Too many entity IDs: {len(entity_ids)} exceeds maximum of {MAX_ENTITIES}",
946
- parameter="entity_id",
947
- ))
964
+ raise_tool_error(
965
+ create_validation_error(
966
+ f"Too many entity IDs: {len(entity_ids)} exceeds maximum of {MAX_ENTITIES}",
967
+ parameter="entity_id",
968
+ )
969
+ )
948
970
 
949
971
  # Deduplicate while preserving order
950
972
  unique_ids = list(dict.fromkeys(entity_ids))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ha-mcp-dev
3
- Version: 7.4.1.dev447
3
+ Version: 7.4.1.dev449
4
4
  Summary: Home Assistant MCP Server - Complete control of Home Assistant through MCP
5
5
  Author-email: Julien <github@qc-h.net>
6
6
  License: MIT