ha-mcp-dev 7.1.0.dev302__tar.gz → 7.1.0.dev303__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 (99) hide show
  1. {ha_mcp_dev-7.1.0.dev302/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.1.0.dev303}/PKG-INFO +5 -2
  2. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/README.md +4 -1
  3. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/pyproject.toml +1 -1
  4. ha_mcp_dev-7.1.0.dev303/src/ha_mcp/tools/tools_categories.py +320 -0
  5. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/tools/tools_entities.py +223 -120
  6. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303/src/ha_mcp_dev.egg-info}/PKG-INFO +5 -2
  7. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp_dev.egg-info/SOURCES.txt +1 -0
  8. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/LICENSE +0 -0
  9. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/MANIFEST.in +0 -0
  10. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/setup.cfg +0 -0
  11. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/__init__.py +0 -0
  12. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/__main__.py +0 -0
  13. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/_pypi_marker +0 -0
  14. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/auth/__init__.py +0 -0
  15. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/auth/consent_form.py +0 -0
  16. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/auth/provider.py +0 -0
  17. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/client/__init__.py +0 -0
  18. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/client/rest_client.py +0 -0
  19. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/client/websocket_client.py +0 -0
  20. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/client/websocket_listener.py +0 -0
  21. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/config.py +0 -0
  22. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/errors.py +0 -0
  23. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/py.typed +0 -0
  24. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
  25. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
  26. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
  27. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
  28. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
  29. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
  30. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
  31. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
  32. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
  33. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
  34. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
  35. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
  36. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
  37. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
  38. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
  39. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
  40. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
  41. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
  42. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
  43. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
  44. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/server.py +0 -0
  45. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/smoke_test.py +0 -0
  46. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/tools/__init__.py +0 -0
  47. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/tools/backup.py +0 -0
  48. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/tools/best_practice_checker.py +0 -0
  49. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/tools/device_control.py +0 -0
  50. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/tools/enhanced.py +0 -0
  51. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/tools/helpers.py +0 -0
  52. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/tools/registry.py +0 -0
  53. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/tools/smart_search.py +0 -0
  54. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/tools/tools_addons.py +0 -0
  55. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/tools/tools_areas.py +0 -0
  56. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/tools/tools_blueprints.py +0 -0
  57. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/tools/tools_bug_report.py +0 -0
  58. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/tools/tools_calendar.py +0 -0
  59. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/tools/tools_camera.py +0 -0
  60. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/tools/tools_config_automations.py +0 -0
  61. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
  62. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/tools/tools_config_entry_flow.py +0 -0
  63. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
  64. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
  65. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/tools/tools_filesystem.py +0 -0
  66. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/tools/tools_groups.py +0 -0
  67. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/tools/tools_hacs.py +0 -0
  68. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/tools/tools_history.py +0 -0
  69. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/tools/tools_integrations.py +0 -0
  70. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/tools/tools_labels.py +0 -0
  71. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/tools/tools_mcp_component.py +0 -0
  72. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/tools/tools_registry.py +0 -0
  73. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/tools/tools_resources.py +0 -0
  74. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/tools/tools_search.py +0 -0
  75. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/tools/tools_service.py +0 -0
  76. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/tools/tools_services.py +0 -0
  77. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/tools/tools_system.py +0 -0
  78. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/tools/tools_todo.py +0 -0
  79. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/tools/tools_traces.py +0 -0
  80. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/tools/tools_updates.py +0 -0
  81. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/tools/tools_utility.py +0 -0
  82. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
  83. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/tools/tools_zones.py +0 -0
  84. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/tools/util_helpers.py +0 -0
  85. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/transforms/__init__.py +0 -0
  86. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/transforms/categorized_search.py +0 -0
  87. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/utils/__init__.py +0 -0
  88. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/utils/domain_handlers.py +0 -0
  89. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/utils/fuzzy_search.py +0 -0
  90. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/utils/operation_manager.py +0 -0
  91. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/utils/python_sandbox.py +0 -0
  92. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp/utils/usage_logger.py +0 -0
  93. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
  94. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
  95. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
  96. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
  97. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/tests/__init__.py +0 -0
  98. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/tests/test_constants.py +0 -0
  99. {ha_mcp_dev-7.1.0.dev302 → ha_mcp_dev-7.1.0.dev303}/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.1.0.dev302
3
+ Version: 7.1.0.dev303
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
@@ -264,7 +264,7 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
264
264
  - **[@ryphez](https://github.com/ryphez)** — Codex Desktop UI MCP quick setup guide.
265
265
  - **[@Danm72](https://github.com/Danm72)** — Entity registry tools (`ha_set_entity`, `ha_get_entity`) for managing entity properties.
266
266
  - **[@Raygooo](https://github.com/Raygooo)** — SOCKS proxy support.
267
- - **[@cj-elevate](https://github.com/cj-elevate)** — Integration & entity management tools (enable/disable/delete).
267
+ - **[@cj-elevate](https://github.com/cj-elevate)** — Integration & entity management tools (enable/disable/delete); person/zone/tag config store routing.
268
268
  - **[@maxperron](https://github.com/maxperron)** — Beta testing.
269
269
  - **[@kingbear2](https://github.com/kingbear2)** — Windows UV setup guide.
270
270
  - **[@konradwalsh](https://github.com/konradwalsh)** — Financial support via [GitHub Sponsors](https://github.com/sponsors/julienld). Thank you! ☕
@@ -274,6 +274,9 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
274
274
  - **[@saphid](https://github.com/saphid)** — Config entry options flow tools (initial design, #590).
275
275
  - **[@adraguidev](https://github.com/adraguidev)** — Fix menu-based config entry flows for group helpers (#647).
276
276
  - **[@transportrefer](https://github.com/transportrefer)** — Integration options inspection (`ha_get_integration` schema support, #689).
277
+ - **[@teh-hippo](https://github.com/teh-hippo)** — Fix blueprint import missing save step.
278
+ - **[@smenzer](https://github.com/smenzer)** — Documentation fix.
279
+ - **[@The-Greg-O](https://github.com/The-Greg-O)** — REST API for config entry deletion.
277
280
  - **[@restriction](https://github.com/restriction)** — Responsible disclosure: python_transform sandbox missing call target validation.
278
281
 
279
282
  ---
@@ -235,7 +235,7 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
235
235
  - **[@ryphez](https://github.com/ryphez)** — Codex Desktop UI MCP quick setup guide.
236
236
  - **[@Danm72](https://github.com/Danm72)** — Entity registry tools (`ha_set_entity`, `ha_get_entity`) for managing entity properties.
237
237
  - **[@Raygooo](https://github.com/Raygooo)** — SOCKS proxy support.
238
- - **[@cj-elevate](https://github.com/cj-elevate)** — Integration & entity management tools (enable/disable/delete).
238
+ - **[@cj-elevate](https://github.com/cj-elevate)** — Integration & entity management tools (enable/disable/delete); person/zone/tag config store routing.
239
239
  - **[@maxperron](https://github.com/maxperron)** — Beta testing.
240
240
  - **[@kingbear2](https://github.com/kingbear2)** — Windows UV setup guide.
241
241
  - **[@konradwalsh](https://github.com/konradwalsh)** — Financial support via [GitHub Sponsors](https://github.com/sponsors/julienld). Thank you! ☕
@@ -245,6 +245,9 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
245
245
  - **[@saphid](https://github.com/saphid)** — Config entry options flow tools (initial design, #590).
246
246
  - **[@adraguidev](https://github.com/adraguidev)** — Fix menu-based config entry flows for group helpers (#647).
247
247
  - **[@transportrefer](https://github.com/transportrefer)** — Integration options inspection (`ha_get_integration` schema support, #689).
248
+ - **[@teh-hippo](https://github.com/teh-hippo)** — Fix blueprint import missing save step.
249
+ - **[@smenzer](https://github.com/smenzer)** — Documentation fix.
250
+ - **[@The-Greg-O](https://github.com/The-Greg-O)** — REST API for config entry deletion.
248
251
  - **[@restriction](https://github.com/restriction)** — Responsible disclosure: python_transform sandbox missing call target validation.
249
252
 
250
253
  ---
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ha-mcp-dev"
7
- version = "7.1.0.dev302"
7
+ version = "7.1.0.dev303"
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"
@@ -0,0 +1,320 @@
1
+ """
2
+ Category management tools for Home Assistant.
3
+
4
+ This module provides tools for listing, creating, updating, and deleting
5
+ Home Assistant categories. Categories are domain-scoped organizational groups
6
+ (e.g., for automations, scripts, scenes, helpers) introduced in Home Assistant 2024.4.
7
+
8
+ To assign categories to entities, use ha_set_entity(categories=...).
9
+ """
10
+
11
+ import logging
12
+ from typing import Annotated, Any
13
+
14
+ from fastmcp.exceptions import ToolError
15
+ from pydantic import Field
16
+
17
+ from ..errors import ErrorCode, create_error_response
18
+ from .helpers import exception_to_structured_error, log_tool_usage, raise_tool_error
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+ def register_category_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
24
+ """Register Home Assistant category management tools."""
25
+
26
+ @mcp.tool(
27
+ annotations={
28
+ "idempotentHint": True,
29
+ "readOnlyHint": True,
30
+ "tags": ["category"],
31
+ "title": "Get Category",
32
+ }
33
+ )
34
+ @log_tool_usage
35
+ async def ha_config_get_category(
36
+ scope: Annotated[
37
+ str,
38
+ Field(
39
+ description="Domain scope for categories (e.g., 'automation', 'script', 'scene', 'helpers').",
40
+ ),
41
+ ],
42
+ category_id: Annotated[
43
+ str | None,
44
+ Field(
45
+ description="ID of the category to retrieve. If omitted, lists all categories for the scope.",
46
+ default=None,
47
+ ),
48
+ ] = None,
49
+ ) -> dict[str, Any]:
50
+ """
51
+ Get category info - list all categories for a scope or get a specific one by ID.
52
+
53
+ Without a category_id: Lists all Home Assistant categories for the given scope.
54
+ With a category_id: Returns configuration for that specific category.
55
+
56
+ Categories are domain-scoped organizational groups for automations, scripts, scenes, and helpers.
57
+
58
+ CATEGORY PROPERTIES:
59
+ - ID (category_id), Name
60
+ - Icon (optional)
61
+
62
+ EXAMPLES:
63
+ - List automation categories: ha_config_get_category("automation")
64
+ - List script categories: ha_config_get_category("script")
65
+ - List helper categories: ha_config_get_category("helpers")
66
+ - Get specific category: ha_config_get_category("automation", category_id="my_category_id")
67
+
68
+ Use ha_config_set_category() to create or update categories.
69
+ Use ha_set_entity(categories={"automation": "category_id"}) to assign categories to entities.
70
+ """
71
+ try:
72
+ message: dict[str, Any] = {
73
+ "type": "config/category_registry/list",
74
+ "scope": scope,
75
+ }
76
+
77
+ result = await client.send_websocket_message(message)
78
+
79
+ if not result.get("success"):
80
+ raise_tool_error(
81
+ create_error_response(
82
+ ErrorCode.SERVICE_CALL_FAILED,
83
+ result.get("error", "Failed to get categories"),
84
+ context={"scope": scope, "category_id": category_id},
85
+ )
86
+ )
87
+
88
+ categories = result.get("result", [])
89
+
90
+ # List mode - return all categories
91
+ if category_id is None:
92
+ return {
93
+ "success": True,
94
+ "count": len(categories),
95
+ "categories": categories,
96
+ "scope": scope,
97
+ "message": f"Found {len(categories)} category(ies) for scope '{scope}'",
98
+ }
99
+
100
+ # Get mode - find specific category
101
+ category = next(
102
+ (cat for cat in categories if cat.get("category_id") == category_id),
103
+ None,
104
+ )
105
+
106
+ if category:
107
+ return {
108
+ "success": True,
109
+ "category_id": category_id,
110
+ "category": category,
111
+ "scope": scope,
112
+ "message": f"Found category: {category.get('name', category_id)}",
113
+ }
114
+ else:
115
+ available_ids = [cat.get("category_id") for cat in categories[:10]]
116
+ raise_tool_error(
117
+ create_error_response(
118
+ ErrorCode.ENTITY_NOT_FOUND,
119
+ f"Category not found: {category_id}",
120
+ context={
121
+ "category_id": category_id,
122
+ "scope": scope,
123
+ "available_category_ids": available_ids,
124
+ },
125
+ suggestions=[
126
+ f"Use ha_config_get_category('{scope}') without category_id to see all categories"
127
+ ],
128
+ )
129
+ )
130
+
131
+ except ToolError:
132
+ raise
133
+ except Exception as e:
134
+ logger.error(f"Error getting categories: {e}")
135
+ exception_to_structured_error(
136
+ e,
137
+ context={"scope": scope, "category_id": category_id},
138
+ suggestions=[
139
+ "Check Home Assistant connection",
140
+ "Verify WebSocket connection is active",
141
+ "Ensure scope is valid (e.g., 'automation', 'script', 'scene', 'helpers')",
142
+ ],
143
+ )
144
+
145
+ @mcp.tool(
146
+ annotations={
147
+ "destructiveHint": True,
148
+ "tags": ["category"],
149
+ "title": "Create or Update Category",
150
+ }
151
+ )
152
+ @log_tool_usage
153
+ async def ha_config_set_category(
154
+ name: Annotated[str, Field(description="Display name for the category")],
155
+ scope: Annotated[
156
+ str,
157
+ Field(
158
+ description="Domain scope for the category (e.g., 'automation', 'script', 'scene', 'helpers').",
159
+ ),
160
+ ],
161
+ category_id: Annotated[
162
+ str | None,
163
+ Field(
164
+ description="Category ID for updates. If not provided, creates a new category.",
165
+ default=None,
166
+ ),
167
+ ] = None,
168
+ icon: Annotated[
169
+ str | None,
170
+ Field(
171
+ description="Material Design Icon (e.g., 'mdi:tag', 'mdi:label')",
172
+ default=None,
173
+ ),
174
+ ] = None,
175
+ ) -> dict[str, Any]:
176
+ """
177
+ Create or update a Home Assistant category.
178
+
179
+ Creates a new category if category_id is not provided, or updates an existing category if category_id is provided.
180
+
181
+ Categories are domain-scoped organizational groups for automations, scripts,
182
+ scenes, and helpers. Unlike labels (which are cross-domain), categories are
183
+ specific to a single domain scope.
184
+
185
+ EXAMPLES:
186
+ - Create automation category: ha_config_set_category("Lighting", scope="automation")
187
+ - Create with icon: ha_config_set_category("Security", scope="automation", icon="mdi:shield")
188
+ - Update category: ha_config_set_category("Updated Name", scope="automation", category_id="my_category_id")
189
+
190
+ After creating a category, use ha_set_entity(categories={"automation": "category_id"}) to assign it.
191
+ """
192
+ try:
193
+ # Determine if this is a create or update
194
+ action = "update" if category_id else "create"
195
+
196
+ message: dict[str, Any] = {
197
+ "type": f"config/category_registry/{action}",
198
+ "scope": scope,
199
+ "name": name,
200
+ }
201
+
202
+ if action == "update":
203
+ message["category_id"] = category_id
204
+
205
+ # Add optional fields only if they are explicitly provided (not None)
206
+ if icon is not None:
207
+ message["icon"] = icon
208
+
209
+ result = await client.send_websocket_message(message)
210
+
211
+ if result.get("success"):
212
+ category_data = result.get("result", {})
213
+ action_past = "created" if action == "create" else "updated"
214
+ return {
215
+ "success": True,
216
+ "category_id": category_data.get("category_id"),
217
+ "category_data": category_data,
218
+ "scope": scope,
219
+ "message": f"Successfully {action_past} category: {name}",
220
+ }
221
+ else:
222
+ raise_tool_error(
223
+ create_error_response(
224
+ ErrorCode.SERVICE_CALL_FAILED,
225
+ f"Failed to {action} category: {result.get('error', 'Unknown error')}",
226
+ context={
227
+ "name": name,
228
+ "scope": scope,
229
+ "category_id": category_id,
230
+ },
231
+ )
232
+ )
233
+
234
+ except ToolError:
235
+ raise
236
+ except Exception as e:
237
+ logger.error(f"Error setting category {name!r}: {e}")
238
+ exception_to_structured_error(
239
+ e,
240
+ context={"name": name, "scope": scope, "category_id": category_id},
241
+ suggestions=[
242
+ "Check Home Assistant connection",
243
+ "Verify the category name is valid",
244
+ "For updates, verify the category_id exists using ha_config_get_category()",
245
+ ],
246
+ )
247
+
248
+ @mcp.tool(
249
+ annotations={
250
+ "destructiveHint": True,
251
+ "idempotentHint": True,
252
+ "tags": ["category"],
253
+ "title": "Remove Category",
254
+ }
255
+ )
256
+ @log_tool_usage
257
+ async def ha_config_remove_category(
258
+ scope: Annotated[
259
+ str,
260
+ Field(
261
+ description="Domain scope for the category (e.g., 'automation', 'script', 'scene', 'helpers').",
262
+ ),
263
+ ],
264
+ category_id: Annotated[
265
+ str,
266
+ Field(description="ID of the category to delete"),
267
+ ],
268
+ ) -> dict[str, Any]:
269
+ """
270
+ Delete a Home Assistant category.
271
+
272
+ Removes the category from the category registry for the given scope
273
+ (e.g., 'automation', 'script', 'scene', 'helpers').
274
+ This will also remove the category assignment from all entities in that scope.
275
+
276
+ EXAMPLES:
277
+ - Delete category: ha_config_remove_category("automation", "my_category_id")
278
+
279
+ Use ha_config_get_category() to find category IDs.
280
+
281
+ **WARNING:** Deleting a category will remove it from all assigned entities.
282
+ This action cannot be undone.
283
+ """
284
+ try:
285
+ message: dict[str, Any] = {
286
+ "type": "config/category_registry/delete",
287
+ "scope": scope,
288
+ "category_id": category_id,
289
+ }
290
+
291
+ result = await client.send_websocket_message(message)
292
+
293
+ if result.get("success"):
294
+ return {
295
+ "success": True,
296
+ "category_id": category_id,
297
+ "scope": scope,
298
+ "message": f"Successfully deleted category: {category_id}",
299
+ }
300
+ else:
301
+ raise_tool_error(
302
+ create_error_response(
303
+ ErrorCode.SERVICE_CALL_FAILED,
304
+ f"Failed to delete category: {result.get('error', 'Unknown error')}",
305
+ context={"category_id": category_id, "scope": scope},
306
+ )
307
+ )
308
+
309
+ except ToolError:
310
+ raise
311
+ except Exception as e:
312
+ logger.error(f"Error removing category {category_id!r}: {e}")
313
+ exception_to_structured_error(
314
+ e,
315
+ context={"category_id": category_id, "scope": scope},
316
+ suggestions=[
317
+ "Check Home Assistant connection",
318
+ "Verify the category_id exists using ha_config_get_category()",
319
+ ],
320
+ )