ha-mcp-dev 7.4.1.dev441__tar.gz → 7.4.1.dev443__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.dev441/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.4.1.dev443}/PKG-INFO +5 -6
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/README.md +4 -5
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/pyproject.toml +1 -1
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/config.py +1 -24
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/server.py +179 -66
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/settings_ui.py +102 -24
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/best_practice_checker.py +4 -21
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/tools_config_automations.py +2 -9
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/tools_config_scripts.py +2 -9
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443/src/ha_mcp_dev.egg-info}/PKG-INFO +5 -6
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/LICENSE +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/MANIFEST.in +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/setup.cfg +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/__init__.py +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/__main__.py +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/_pypi_marker +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/_version.py +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/auth/__init__.py +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/auth/consent_form.py +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/auth/provider.py +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/client/__init__.py +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/client/rest_client.py +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/client/websocket_client.py +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/client/websocket_listener.py +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/errors.py +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/py.typed +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/smoke_test.py +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/__init__.py +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/backup.py +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/device_control.py +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/enhanced.py +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/helpers.py +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/reference_validator.py +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/registry.py +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/smart_search.py +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/tools_addons.py +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/tools_areas.py +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/tools_blueprints.py +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/tools_bug_report.py +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/tools_calendar.py +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/tools_camera.py +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/tools_categories.py +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/tools_config_entry_flow.py +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/tools_energy.py +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/tools_entities.py +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/tools_filesystem.py +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/tools_groups.py +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/tools_hacs.py +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/tools_history.py +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/tools_integrations.py +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/tools_labels.py +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/tools_mcp_component.py +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/tools_registry.py +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/tools_resources.py +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/tools_search.py +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/tools_service.py +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/tools_services.py +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/tools_system.py +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/tools_todo.py +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/tools_traces.py +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/tools_updates.py +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/tools_utility.py +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/tools_zones.py +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/util_helpers.py +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/transforms/__init__.py +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/transforms/categorized_search.py +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/utils/__init__.py +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/utils/config_hash.py +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/utils/data_paths.py +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/utils/domain_handlers.py +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/utils/fuzzy_search.py +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/utils/operation_manager.py +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/utils/python_sandbox.py +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/utils/usage_logger.py +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/tests/__init__.py +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/tests/test_constants.py +0 -0
- {ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/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.
|
|
3
|
+
Version: 7.4.1.dev443
|
|
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
|
|
@@ -254,12 +254,9 @@ An MCP server can create automations, helpers, and dashboards, but it has no opi
|
|
|
254
254
|
|
|
255
255
|
### Bundled Skills (built-in)
|
|
256
256
|
|
|
257
|
-
Skills from `homeassistant-ai/skills` are bundled and served as [MCP resources](https://modelcontextprotocol.io/docs/concepts/resources) via `skill://` URIs. Any MCP client that supports resources can discover them automatically — no manual installation needed.
|
|
257
|
+
Skills from `homeassistant-ai/skills` are bundled and served as [MCP resources](https://modelcontextprotocol.io/docs/concepts/resources) via `skill://` URIs. Any MCP client that supports resources can discover them automatically — no manual installation needed. For tool-only clients, the same skills are also exposed as `ha_list_resources` / `ha_read_resource` tools. Resources are not auto-injected into context — clients must explicitly request them, so idle context cost is just the metadata listing.
|
|
258
258
|
|
|
259
|
-
|
|
260
|
-
|---------|---------|-------------|
|
|
261
|
-
| `ENABLE_SKILLS` | `true` | Serve skills as MCP resources. Resources are not auto-injected into context — clients must explicitly request them. |
|
|
262
|
-
| `ENABLE_SKILLS_AS_TOOLS` | `true` | Expose skills and doc resources via `list_resources`/`read_resource` tools. Resource-capable clients can set to `false` to reduce tool count. |
|
|
259
|
+
If you want to hide either tool from the catalog, disable it from the web settings UI like any other tool.
|
|
263
260
|
|
|
264
261
|
Skills can still be installed manually for clients that prefer local skill files — see the [skills repo](https://github.com/homeassistant-ai/skills) for instructions.
|
|
265
262
|
|
|
@@ -344,6 +341,8 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
|
|
|
344
341
|
- **[@gcormier](https://github.com/gcormier)** — Windows installer improvements: removed unused variable and fixed terminal closing after install.
|
|
345
342
|
- **[@ekobres](https://github.com/ekobres)** — Feature flags for `HAMCP_ENABLE_FILESYSTEM_TOOLS` and `HAMCP_ENABLE_CUSTOM_COMPONENT_INTEGRATION` in the add-on config, with beta tagging in source and docs.
|
|
346
343
|
- **[@w3z315](https://github.com/w3z315)** — Financial support via [GitHub Sponsors](https://github.com/sponsors/julienld). Thank you! ☕
|
|
344
|
+
- **[@griffinmartin](https://github.com/griffinmartin)** — Added OpenCode (by Anomaly) as a selectable AI client in the setup wizard, with both stdio and streamable HTTP support.
|
|
345
|
+
- **[@hhopke](https://github.com/hhopke)** — Fixed addon API calls to route through HA Core ingress proxy instead of direct container connections, fixing `ha_manage_addon` proxy mode on addon installs.
|
|
347
346
|
|
|
348
347
|
---
|
|
349
348
|
|
|
@@ -225,12 +225,9 @@ An MCP server can create automations, helpers, and dashboards, but it has no opi
|
|
|
225
225
|
|
|
226
226
|
### Bundled Skills (built-in)
|
|
227
227
|
|
|
228
|
-
Skills from `homeassistant-ai/skills` are bundled and served as [MCP resources](https://modelcontextprotocol.io/docs/concepts/resources) via `skill://` URIs. Any MCP client that supports resources can discover them automatically — no manual installation needed.
|
|
228
|
+
Skills from `homeassistant-ai/skills` are bundled and served as [MCP resources](https://modelcontextprotocol.io/docs/concepts/resources) via `skill://` URIs. Any MCP client that supports resources can discover them automatically — no manual installation needed. For tool-only clients, the same skills are also exposed as `ha_list_resources` / `ha_read_resource` tools. Resources are not auto-injected into context — clients must explicitly request them, so idle context cost is just the metadata listing.
|
|
229
229
|
|
|
230
|
-
|
|
231
|
-
|---------|---------|-------------|
|
|
232
|
-
| `ENABLE_SKILLS` | `true` | Serve skills as MCP resources. Resources are not auto-injected into context — clients must explicitly request them. |
|
|
233
|
-
| `ENABLE_SKILLS_AS_TOOLS` | `true` | Expose skills and doc resources via `list_resources`/`read_resource` tools. Resource-capable clients can set to `false` to reduce tool count. |
|
|
230
|
+
If you want to hide either tool from the catalog, disable it from the web settings UI like any other tool.
|
|
234
231
|
|
|
235
232
|
Skills can still be installed manually for clients that prefer local skill files — see the [skills repo](https://github.com/homeassistant-ai/skills) for instructions.
|
|
236
233
|
|
|
@@ -315,6 +312,8 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
|
|
|
315
312
|
- **[@gcormier](https://github.com/gcormier)** — Windows installer improvements: removed unused variable and fixed terminal closing after install.
|
|
316
313
|
- **[@ekobres](https://github.com/ekobres)** — Feature flags for `HAMCP_ENABLE_FILESYSTEM_TOOLS` and `HAMCP_ENABLE_CUSTOM_COMPONENT_INTEGRATION` in the add-on config, with beta tagging in source and docs.
|
|
317
314
|
- **[@w3z315](https://github.com/w3z315)** — Financial support via [GitHub Sponsors](https://github.com/sponsors/julienld). Thank you! ☕
|
|
315
|
+
- **[@griffinmartin](https://github.com/griffinmartin)** — Added OpenCode (by Anomaly) as a selectable AI client in the setup wizard, with both stdio and streamable HTTP support.
|
|
316
|
+
- **[@hhopke](https://github.com/hhopke)** — Fixed addon API calls to route through HA Core ingress proxy instead of direct container connections, fixing `ha_manage_addon` proxy mode on addon installs.
|
|
318
317
|
|
|
319
318
|
---
|
|
320
319
|
|
|
@@ -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.dev443"
|
|
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"
|
|
@@ -9,7 +9,7 @@ import os
|
|
|
9
9
|
from pathlib import Path
|
|
10
10
|
|
|
11
11
|
from dotenv import load_dotenv
|
|
12
|
-
from pydantic import Field, field_validator
|
|
12
|
+
from pydantic import Field, field_validator
|
|
13
13
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
14
14
|
|
|
15
15
|
from ha_mcp._version import get_version
|
|
@@ -92,17 +92,6 @@ class Settings(BaseSettings):
|
|
|
92
92
|
True, alias="ENABLE_DASHBOARD_PARTIAL_TOOLS"
|
|
93
93
|
)
|
|
94
94
|
|
|
95
|
-
# Skills configuration
|
|
96
|
-
# Serve bundled HA best-practice skills as MCP resources (skill:// URIs).
|
|
97
|
-
# Resources are not auto-injected — clients must explicitly request them.
|
|
98
|
-
enable_skills: bool = Field(True, alias="ENABLE_SKILLS")
|
|
99
|
-
|
|
100
|
-
# Expose skills and doc resources as tools (list_resources/read_resource)
|
|
101
|
-
# for clients that don't support MCP resources natively.
|
|
102
|
-
# Defaults to True so all clients can access documentation and skills.
|
|
103
|
-
# Resource-capable clients can set to False to reduce tool count.
|
|
104
|
-
enable_skills_as_tools: bool = Field(True, alias="ENABLE_SKILLS_AS_TOOLS")
|
|
105
|
-
|
|
106
95
|
# Tool search transform — replaces the full tool catalog with a unified
|
|
107
96
|
# BM25 search tool and categorized call proxies (read/write/delete).
|
|
108
97
|
# Dramatically reduces idle context token usage for LLMs.
|
|
@@ -124,18 +113,6 @@ class Settings(BaseSettings):
|
|
|
124
113
|
# supervisor UI rejects out-of-range values before they reach env vars.
|
|
125
114
|
tool_search_max_results: int = Field(5, ge=2, le=10, alias="TOOL_SEARCH_MAX_RESULTS")
|
|
126
115
|
|
|
127
|
-
@model_validator(mode="after")
|
|
128
|
-
def _skills_dependency(self) -> "Settings":
|
|
129
|
-
"""Auto-enable skills (resources) when skills-as-tools is on.
|
|
130
|
-
|
|
131
|
-
skills_as_tools wraps ResourcesAsTools which requires skills to be
|
|
132
|
-
registered as MCP resources first. Without this, enabling
|
|
133
|
-
skills_as_tools alone would produce empty list_resources results.
|
|
134
|
-
"""
|
|
135
|
-
if self.enable_skills_as_tools and not self.enable_skills:
|
|
136
|
-
self.enable_skills = True
|
|
137
|
-
return self
|
|
138
|
-
|
|
139
116
|
@property
|
|
140
117
|
def env_file_name(self) -> str:
|
|
141
118
|
"""Get the current environment file name."""
|
|
@@ -16,6 +16,7 @@ from typing import TYPE_CHECKING, Any, ClassVar, cast
|
|
|
16
16
|
|
|
17
17
|
import yaml # type: ignore[import-untyped]
|
|
18
18
|
from fastmcp import FastMCP
|
|
19
|
+
from fastmcp.server.transforms import ResourcesAsTools
|
|
19
20
|
from mcp.types import Icon
|
|
20
21
|
|
|
21
22
|
from .config import _PACKAGE_VERSION, get_global_settings
|
|
@@ -23,11 +24,84 @@ from .tools.enhanced import EnhancedToolsMixin
|
|
|
23
24
|
from .transforms import DEFAULT_PINNED_TOOLS
|
|
24
25
|
|
|
25
26
|
if TYPE_CHECKING:
|
|
27
|
+
from collections.abc import Sequence
|
|
28
|
+
|
|
29
|
+
from fastmcp.server.transforms import GetToolNext
|
|
30
|
+
from fastmcp.tools.base import Tool
|
|
31
|
+
from fastmcp.utilities.versions import VersionSpec
|
|
32
|
+
|
|
26
33
|
from .client.rest_client import HomeAssistantClient
|
|
27
34
|
from .tools.registry import ToolsRegistry
|
|
28
35
|
|
|
29
36
|
logger = logging.getLogger(__name__)
|
|
30
37
|
|
|
38
|
+
|
|
39
|
+
class HaResourcesAsTools(ResourcesAsTools):
|
|
40
|
+
"""ResourcesAsTools renamed to follow ha-mcp's ha_<verb>_<noun> convention.
|
|
41
|
+
|
|
42
|
+
FastMCP's ResourcesAsTools transform hardcodes ``list_resources`` and
|
|
43
|
+
``read_resource``. This subclass renames them to ``ha_list_resources``
|
|
44
|
+
and ``ha_read_resource`` so they behave like every other tool in the
|
|
45
|
+
catalog (consistent prefix, discoverable in the web settings UI).
|
|
46
|
+
|
|
47
|
+
Upgrade fragility: depends on FastMCP's ``_make_list_resources_tool`` /
|
|
48
|
+
``_make_read_resource_tool`` private factories and on the names
|
|
49
|
+
``list_resources`` / ``read_resource`` produced by them. A FastMCP
|
|
50
|
+
upgrade that renames either factory or either tool name will require
|
|
51
|
+
a matching update here. ``list_tools`` logs a warning if the rename
|
|
52
|
+
fails to match exactly two tools so the regression is loud at boot.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
LIST_TOOL_NAME = "ha_list_resources"
|
|
56
|
+
READ_TOOL_NAME = "ha_read_resource"
|
|
57
|
+
_RENAMES: ClassVar[dict[str, str]] = {
|
|
58
|
+
"list_resources": LIST_TOOL_NAME,
|
|
59
|
+
"read_resource": READ_TOOL_NAME,
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async def list_tools(self, tools: Sequence[Tool]) -> Sequence[Tool]:
|
|
63
|
+
# Scan the entire result rather than slicing the tail so a future
|
|
64
|
+
# FastMCP change that reorders or expands the appended tool set
|
|
65
|
+
# surfaces as a logged warning instead of silently leaking the
|
|
66
|
+
# unprefixed names into the catalog.
|
|
67
|
+
result = list(await super().list_tools(tools))
|
|
68
|
+
renamed: list[Tool] = []
|
|
69
|
+
matches = 0
|
|
70
|
+
for tool in result:
|
|
71
|
+
new_name = self._RENAMES.get(tool.name)
|
|
72
|
+
if new_name is None:
|
|
73
|
+
renamed.append(tool)
|
|
74
|
+
continue
|
|
75
|
+
renamed.append(tool.model_copy(update={"name": new_name}))
|
|
76
|
+
matches += 1
|
|
77
|
+
if matches != len(self._RENAMES):
|
|
78
|
+
logger.warning(
|
|
79
|
+
"HaResourcesAsTools: expected to rename %d tools (%s) but "
|
|
80
|
+
"matched %d in the upstream tool list — fastmcp's "
|
|
81
|
+
"ResourcesAsTools contract may have changed",
|
|
82
|
+
len(self._RENAMES),
|
|
83
|
+
", ".join(self._RENAMES),
|
|
84
|
+
matches,
|
|
85
|
+
)
|
|
86
|
+
return renamed
|
|
87
|
+
|
|
88
|
+
async def get_tool(
|
|
89
|
+
self,
|
|
90
|
+
name: str,
|
|
91
|
+
call_next: GetToolNext,
|
|
92
|
+
*,
|
|
93
|
+
version: VersionSpec | None = None,
|
|
94
|
+
) -> Tool | None:
|
|
95
|
+
if name == self.LIST_TOOL_NAME:
|
|
96
|
+
return self._make_list_resources_tool().model_copy(
|
|
97
|
+
update={"name": self.LIST_TOOL_NAME}
|
|
98
|
+
)
|
|
99
|
+
if name == self.READ_TOOL_NAME:
|
|
100
|
+
return self._make_read_resource_tool().model_copy(
|
|
101
|
+
update={"name": self.READ_TOOL_NAME}
|
|
102
|
+
)
|
|
103
|
+
return await call_next(name, version=version)
|
|
104
|
+
|
|
31
105
|
# Server icon configuration using GitHub-hosted images
|
|
32
106
|
# These icons are bundled in packaging/mcpb/ and also available via GitHub raw URLs
|
|
33
107
|
SERVER_ICONS = [
|
|
@@ -179,12 +253,9 @@ class HomeAssistantSmartMCPServer(EnhancedToolsMixin):
|
|
|
179
253
|
is authored for LLM consumption and should not be parsed or
|
|
180
254
|
restructured by code.
|
|
181
255
|
|
|
182
|
-
Returns None when skills
|
|
183
|
-
from the default (None).
|
|
256
|
+
Returns None when no skills directory or no parseable skills are
|
|
257
|
+
present, leaving instructions unchanged from the default (None).
|
|
184
258
|
"""
|
|
185
|
-
if not self.settings.enable_skills:
|
|
186
|
-
return None
|
|
187
|
-
|
|
188
259
|
skills_dir = self._get_skills_dir()
|
|
189
260
|
if not skills_dir:
|
|
190
261
|
return None
|
|
@@ -208,22 +279,15 @@ class HomeAssistantSmartMCPServer(EnhancedToolsMixin):
|
|
|
208
279
|
if not skill_blocks:
|
|
209
280
|
return None
|
|
210
281
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
"time or tokens on those tools."
|
|
221
|
-
)
|
|
222
|
-
else:
|
|
223
|
-
access_method = (
|
|
224
|
-
"Read the skill via MCP resources (resources/read with the "
|
|
225
|
-
"skill:// URI)."
|
|
226
|
-
)
|
|
282
|
+
access_method = (
|
|
283
|
+
"Read the skill via MCP resources (resources/read with the "
|
|
284
|
+
"skill:// URI) — if you can read these instructions, you "
|
|
285
|
+
"should be able to access resources as well. If for any "
|
|
286
|
+
"reason you cannot access MCP resources, use the "
|
|
287
|
+
"ha_list_resources and ha_read_resource tools as a fallback. "
|
|
288
|
+
"If you can access resources normally, do not waste "
|
|
289
|
+
"time or tokens on those tools."
|
|
290
|
+
)
|
|
227
291
|
|
|
228
292
|
header = (
|
|
229
293
|
"IMPORTANT: This server provides best-practice skills that MUST "
|
|
@@ -517,25 +581,27 @@ class HomeAssistantSmartMCPServer(EnhancedToolsMixin):
|
|
|
517
581
|
pinned = list(self._PINNED_TOOLS)
|
|
518
582
|
pinned.extend(self._user_pinned_tools)
|
|
519
583
|
|
|
520
|
-
# Pin
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
)
|
|
584
|
+
# Pin the skills-as-tools transform pair and the per-skill guidance
|
|
585
|
+
# tools so they remain visible when search-based discovery is on.
|
|
586
|
+
# The settings UI's mcp.disable() flow runs after these transforms
|
|
587
|
+
# are appended, so a per-tool disable still wins over this pin.
|
|
588
|
+
pinned.extend(
|
|
589
|
+
[HaResourcesAsTools.LIST_TOOL_NAME, HaResourcesAsTools.READ_TOOL_NAME]
|
|
590
|
+
)
|
|
591
|
+
pinned.extend(getattr(self, "_skill_tool_names", []))
|
|
592
|
+
|
|
593
|
+
# The client may not support resources or server instructions — add
|
|
594
|
+
# skills hint to the search tool description (the one place the LLM
|
|
595
|
+
# is guaranteed to see).
|
|
596
|
+
description = self._SEARCH_TOOL_DESCRIPTION + (
|
|
597
|
+
"\n\nThis server also provides best-practice skills via "
|
|
598
|
+
"skill:// resources. If your client supports MCP resources, "
|
|
599
|
+
f"prefer reading them directly. Otherwise, call "
|
|
600
|
+
f"{HaResourcesAsTools.LIST_TOOL_NAME} and "
|
|
601
|
+
f"{HaResourcesAsTools.READ_TOOL_NAME} (directly, no proxy "
|
|
602
|
+
"needed) to access the relevant SKILL.md before creating "
|
|
603
|
+
"automations or configuring devices."
|
|
604
|
+
)
|
|
539
605
|
|
|
540
606
|
try:
|
|
541
607
|
self.mcp.add_transform(
|
|
@@ -556,14 +622,29 @@ class HomeAssistantSmartMCPServer(EnhancedToolsMixin):
|
|
|
556
622
|
def _register_skills(self) -> None:
|
|
557
623
|
"""Register bundled HA best-practice skills as MCP resources.
|
|
558
624
|
|
|
559
|
-
Uses FastMCP's SkillsDirectoryProvider to serve skill files via
|
|
560
|
-
|
|
561
|
-
that don't support MCP resources
|
|
562
|
-
|
|
563
|
-
|
|
625
|
+
Uses FastMCP's SkillsDirectoryProvider to serve skill files via
|
|
626
|
+
skill:// URIs and exposes them as tools (ha_list_resources /
|
|
627
|
+
ha_read_resource) for clients that don't support MCP resources
|
|
628
|
+
natively. Per-tool visibility is managed via the web settings UI;
|
|
629
|
+
users who want either tool off can disable it there.
|
|
630
|
+
|
|
631
|
+
Each phase tracks success in ``status`` so the final summary log
|
|
632
|
+
line tells operators at a glance whether the skill system is
|
|
633
|
+
healthy, partially degraded, or fully unavailable. Without that
|
|
634
|
+
summary, three independent ``logger.exception`` calls leave
|
|
635
|
+
operators reconstructing state from scattered log lines.
|
|
636
|
+
|
|
637
|
+
Failure modes degrade unevenly across clients: if Phase 3
|
|
638
|
+
(transform) fails, resource-capable clients still see skills,
|
|
639
|
+
but tool-only clients (claude.ai etc.) lose ha_list_resources
|
|
640
|
+
and ha_read_resource from their catalog with no protocol-level
|
|
641
|
+
error — only the warning summary signals it.
|
|
564
642
|
"""
|
|
565
|
-
|
|
566
|
-
|
|
643
|
+
status: dict[str, str | int] = {
|
|
644
|
+
"provider": "skipped",
|
|
645
|
+
"transform": "skipped",
|
|
646
|
+
"guidance_tools": 0,
|
|
647
|
+
}
|
|
567
648
|
|
|
568
649
|
# Phase 1: Import SkillsDirectoryProvider
|
|
569
650
|
try:
|
|
@@ -572,6 +653,7 @@ class HomeAssistantSmartMCPServer(EnhancedToolsMixin):
|
|
|
572
653
|
logger.warning(
|
|
573
654
|
"SkillsDirectoryProvider not available in fastmcp, skipping skills"
|
|
574
655
|
)
|
|
656
|
+
self._log_skill_registration_summary(status)
|
|
575
657
|
return
|
|
576
658
|
|
|
577
659
|
# Phase 2: Register skills as MCP resources
|
|
@@ -582,6 +664,7 @@ class HomeAssistantSmartMCPServer(EnhancedToolsMixin):
|
|
|
582
664
|
"Skills directory not found at %s, skipping skill registration",
|
|
583
665
|
Path(__file__).parent / "resources" / "skills-vendor" / "skills",
|
|
584
666
|
)
|
|
667
|
+
self._log_skill_registration_summary(status)
|
|
585
668
|
return
|
|
586
669
|
|
|
587
670
|
self.mcp.add_provider(
|
|
@@ -590,36 +673,61 @@ class HomeAssistantSmartMCPServer(EnhancedToolsMixin):
|
|
|
590
673
|
)
|
|
591
674
|
)
|
|
592
675
|
logger.info("Registered bundled skills as MCP resources")
|
|
676
|
+
status["provider"] = "ok"
|
|
593
677
|
except Exception:
|
|
594
678
|
logger.exception("Failed to register skills as resources")
|
|
679
|
+
status["provider"] = "failed"
|
|
680
|
+
self._log_skill_registration_summary(status)
|
|
595
681
|
return
|
|
596
682
|
|
|
597
|
-
# Phase 3:
|
|
598
|
-
|
|
599
|
-
return
|
|
600
|
-
|
|
683
|
+
# Phase 3: Expose skills as tools so clients without resource
|
|
684
|
+
# support can still reach the documentation.
|
|
601
685
|
try:
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
"ResourcesAsTools not available in fastmcp, "
|
|
606
|
-
"skills registered as resources but not exposed as tools"
|
|
686
|
+
self.mcp.add_transform(HaResourcesAsTools(self.mcp))
|
|
687
|
+
logger.info(
|
|
688
|
+
"Skills also exposed as tools (ha_list_resources / ha_read_resource)"
|
|
607
689
|
)
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
try:
|
|
611
|
-
self.mcp.add_transform(ResourcesAsTools(self.mcp))
|
|
612
|
-
logger.info("Skills also exposed as tools (ResourcesAsTools)")
|
|
690
|
+
status["transform"] = "ok"
|
|
613
691
|
except Exception:
|
|
614
692
|
logger.exception(
|
|
615
693
|
"Failed to expose skills as tools (resources still available)"
|
|
616
694
|
)
|
|
695
|
+
status["transform"] = "failed"
|
|
617
696
|
|
|
618
697
|
# Phase 4: Register skill guidance tools for clients that don't read
|
|
619
698
|
# server instructions (e.g., claude.ai). The tool description contains
|
|
620
699
|
# the trigger conditions so the AI sees them in the tool listing.
|
|
621
700
|
# Names stored for pinning in search transforms (always-visible).
|
|
622
701
|
self._register_skill_guidance_tools(skills_dir)
|
|
702
|
+
status["guidance_tools"] = len(self._skill_tool_names)
|
|
703
|
+
|
|
704
|
+
self._log_skill_registration_summary(status)
|
|
705
|
+
|
|
706
|
+
@staticmethod
|
|
707
|
+
def _log_skill_registration_summary(status: dict[str, str | int]) -> None:
|
|
708
|
+
"""Emit one-line summary of skill registration outcome.
|
|
709
|
+
|
|
710
|
+
``info`` when both provider and transform succeeded *and* at least
|
|
711
|
+
one guidance tool registered; ``warning`` otherwise. The
|
|
712
|
+
guidance>0 gate catches the "shipped but exposes nothing" case
|
|
713
|
+
(skills directory exists but is empty, or every SKILL.md fails to
|
|
714
|
+
parse) — both prior phases succeed yet no skill is actually
|
|
715
|
+
reachable. This is the line operators should grep for when a
|
|
716
|
+
user reports missing skill features.
|
|
717
|
+
"""
|
|
718
|
+
provider = status.get("provider")
|
|
719
|
+
transform = status.get("transform")
|
|
720
|
+
raw_guidance = status.get("guidance_tools", 0)
|
|
721
|
+
guidance = raw_guidance if isinstance(raw_guidance, int) else 0
|
|
722
|
+
|
|
723
|
+
message = (
|
|
724
|
+
"Skill system summary: provider=%s, transform=%s, guidance_tools=%d"
|
|
725
|
+
)
|
|
726
|
+
args = (provider, transform, guidance)
|
|
727
|
+
if provider == "ok" and transform == "ok" and guidance > 0:
|
|
728
|
+
logger.info(message, *args)
|
|
729
|
+
else:
|
|
730
|
+
logger.warning(message, *args)
|
|
623
731
|
|
|
624
732
|
def _register_skill_guidance_tools(self, skills_dir: Path) -> None:
|
|
625
733
|
"""Register a lightweight guidance tool per skill.
|
|
@@ -628,7 +736,9 @@ class HomeAssistantSmartMCPServer(EnhancedToolsMixin):
|
|
|
628
736
|
so the bootstrap prompt (trigger conditions, symptoms) is invisible.
|
|
629
737
|
This registers a tool per skill whose description contains the trigger
|
|
630
738
|
conditions. The tool itself just lists available reference files —
|
|
631
|
-
actual content is loaded on demand via
|
|
739
|
+
actual content is loaded on demand via the resources/read MCP method
|
|
740
|
+
(or the ha_read_resource fallback tool when the client lacks resource
|
|
741
|
+
support).
|
|
632
742
|
"""
|
|
633
743
|
try:
|
|
634
744
|
entries = sorted(skills_dir.iterdir())
|
|
@@ -653,7 +763,8 @@ class HomeAssistantSmartMCPServer(EnhancedToolsMixin):
|
|
|
653
763
|
tool_description = (
|
|
654
764
|
f"CALL THIS FIRST before performing matching actions. "
|
|
655
765
|
f"{description}\n\n"
|
|
656
|
-
f"Returns available reference files.
|
|
766
|
+
f"Returns available reference files. Read each file via "
|
|
767
|
+
f"resources/read (or ha_read_resource as a fallback) using "
|
|
657
768
|
f"the file URI to load specific guides as needed."
|
|
658
769
|
)
|
|
659
770
|
|
|
@@ -670,9 +781,11 @@ class HomeAssistantSmartMCPServer(EnhancedToolsMixin):
|
|
|
670
781
|
"skill": s_name,
|
|
671
782
|
"skill_uri": s_uri,
|
|
672
783
|
"how_to_use": (
|
|
673
|
-
"
|
|
674
|
-
"
|
|
675
|
-
"
|
|
784
|
+
"Read each file via resources/read (or "
|
|
785
|
+
"ha_read_resource as a fallback) with a file "
|
|
786
|
+
"URI below to load the specific reference you "
|
|
787
|
+
"need. Start with SKILL.md for the decision "
|
|
788
|
+
"workflow."
|
|
676
789
|
),
|
|
677
790
|
"available_files": files,
|
|
678
791
|
}
|
|
@@ -13,7 +13,7 @@ import json
|
|
|
13
13
|
import logging
|
|
14
14
|
import os
|
|
15
15
|
from pathlib import Path
|
|
16
|
-
from typing import TYPE_CHECKING, Any
|
|
16
|
+
from typing import TYPE_CHECKING, Any, NotRequired, TypedDict
|
|
17
17
|
|
|
18
18
|
import httpx
|
|
19
19
|
from starlette.requests import Request
|
|
@@ -30,6 +30,26 @@ if TYPE_CHECKING:
|
|
|
30
30
|
from .config import Settings
|
|
31
31
|
from .server import HomeAssistantSmartMCPServer
|
|
32
32
|
|
|
33
|
+
|
|
34
|
+
class ToolStub(TypedDict):
|
|
35
|
+
"""Metadata advertised in the settings UI for a tool that isn't visible
|
|
36
|
+
in ``local_provider._list_tools()``.
|
|
37
|
+
|
|
38
|
+
Two reasons a tool needs a stub: it's added by a FastMCP transform at
|
|
39
|
+
runtime (``TRANSFORM_GENERATED_TOOLS``), or it's feature-gated and
|
|
40
|
+
only registers when a setting is on (``FEATURE_GATED_TOOLS``). The
|
|
41
|
+
consumer (`_get_tool_metadata`) renders the same shape for both;
|
|
42
|
+
``disabled_by`` is the only field that differs and signals UI
|
|
43
|
+
placement of the "Beta — set X" hint.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
title: str
|
|
47
|
+
primary_tag: str
|
|
48
|
+
description: str
|
|
49
|
+
readOnlyHint: NotRequired[bool]
|
|
50
|
+
destructiveHint: NotRequired[bool]
|
|
51
|
+
disabled_by: NotRequired[str]
|
|
52
|
+
|
|
33
53
|
_VALID_STATES = frozenset({"enabled", "disabled", "pinned"})
|
|
34
54
|
|
|
35
55
|
logger = logging.getLogger(__name__)
|
|
@@ -48,6 +68,39 @@ MANDATORY_TOOLS: set[str] = {
|
|
|
48
68
|
"ha_report_issue",
|
|
49
69
|
}
|
|
50
70
|
|
|
71
|
+
# Tools created by FastMCP transforms (not registered through
|
|
72
|
+
# local_provider). The ``ResourcesAsTools`` transform — subclassed in
|
|
73
|
+
# server.py as ``HaResourcesAsTools`` — appends ``ha_list_resources`` and
|
|
74
|
+
# ``ha_read_resource`` at runtime, so they never show up in
|
|
75
|
+
# ``local_provider._list_tools()``. Inject stub metadata so the UI can
|
|
76
|
+
# render them and ``mcp.disable()`` can hide them from the catalog.
|
|
77
|
+
#
|
|
78
|
+
# Keys MUST match ``HaResourcesAsTools.LIST_TOOL_NAME`` / ``READ_TOOL_NAME``;
|
|
79
|
+
# server.py is not imported here to avoid a top-level cycle, but the
|
|
80
|
+
# ``test_transform_generated_tool_names_match_class_constants`` unit test
|
|
81
|
+
# fails fast if either side drifts.
|
|
82
|
+
TRANSFORM_GENERATED_TOOLS: dict[str, ToolStub] = {
|
|
83
|
+
"ha_list_resources": {
|
|
84
|
+
"title": "List Resources",
|
|
85
|
+
"primary_tag": "System",
|
|
86
|
+
"description": (
|
|
87
|
+
"List bundled skill files and other MCP resources exposed via "
|
|
88
|
+
"skill:// URIs. Fallback for clients that do not support MCP "
|
|
89
|
+
"resources natively."
|
|
90
|
+
),
|
|
91
|
+
"readOnlyHint": True,
|
|
92
|
+
},
|
|
93
|
+
"ha_read_resource": {
|
|
94
|
+
"title": "Read Resource",
|
|
95
|
+
"primary_tag": "System",
|
|
96
|
+
"description": (
|
|
97
|
+
"Read a skill or resource by URI. Fallback for clients that do "
|
|
98
|
+
"not support MCP resources natively."
|
|
99
|
+
),
|
|
100
|
+
"readOnlyHint": True,
|
|
101
|
+
},
|
|
102
|
+
}
|
|
103
|
+
|
|
51
104
|
# Tools that exist in the codebase but are only registered when a
|
|
52
105
|
# corresponding feature flag/env var is set. When the flag is off, these
|
|
53
106
|
# won't appear in local_provider._list_tools(), so we inject stub entries
|
|
@@ -55,48 +108,48 @@ MANDATORY_TOOLS: set[str] = {
|
|
|
55
108
|
# it. Keep this dict in sync with the ``"beta"`` tag added to each tool's
|
|
56
109
|
# source file (tools_yaml_config.py, tools_filesystem.py, tools_mcp_component.py)
|
|
57
110
|
# — a future rename or removal needs to land in both places.
|
|
58
|
-
FEATURE_GATED_TOOLS: dict[str,
|
|
111
|
+
FEATURE_GATED_TOOLS: dict[str, ToolStub] = {
|
|
59
112
|
"ha_config_set_yaml": {
|
|
60
113
|
"title": "Set YAML Config",
|
|
61
114
|
"primary_tag": "System",
|
|
62
115
|
"description": "Add, replace, or remove top-level keys in configuration.yaml or package files.",
|
|
63
116
|
"disabled_by": "enable_yaml_config_editing",
|
|
64
|
-
"destructiveHint":
|
|
117
|
+
"destructiveHint": True,
|
|
65
118
|
},
|
|
66
119
|
"ha_list_files": {
|
|
67
120
|
"title": "List Files",
|
|
68
121
|
"primary_tag": "Files",
|
|
69
122
|
"description": "List files in a directory within the Home Assistant config.",
|
|
70
123
|
"disabled_by": "enable_filesystem_tools",
|
|
71
|
-
"readOnlyHint":
|
|
124
|
+
"readOnlyHint": True,
|
|
72
125
|
},
|
|
73
126
|
"ha_read_file": {
|
|
74
127
|
"title": "Read File",
|
|
75
128
|
"primary_tag": "Files",
|
|
76
129
|
"description": "Read a file from the Home Assistant config directory.",
|
|
77
130
|
"disabled_by": "enable_filesystem_tools",
|
|
78
|
-
"readOnlyHint":
|
|
131
|
+
"readOnlyHint": True,
|
|
79
132
|
},
|
|
80
133
|
"ha_write_file": {
|
|
81
134
|
"title": "Write File",
|
|
82
135
|
"primary_tag": "Files",
|
|
83
136
|
"description": "Write a file to allowed directories in the Home Assistant config.",
|
|
84
137
|
"disabled_by": "enable_filesystem_tools",
|
|
85
|
-
"destructiveHint":
|
|
138
|
+
"destructiveHint": True,
|
|
86
139
|
},
|
|
87
140
|
"ha_delete_file": {
|
|
88
141
|
"title": "Delete File",
|
|
89
142
|
"primary_tag": "Files",
|
|
90
143
|
"description": "Delete a file from allowed directories.",
|
|
91
144
|
"disabled_by": "enable_filesystem_tools",
|
|
92
|
-
"destructiveHint":
|
|
145
|
+
"destructiveHint": True,
|
|
93
146
|
},
|
|
94
147
|
"ha_install_mcp_tools": {
|
|
95
148
|
"title": "Install MCP Tools Component",
|
|
96
149
|
"primary_tag": "Utilities",
|
|
97
150
|
"description": "Install the ha_mcp_tools custom component via HACS.",
|
|
98
151
|
"disabled_by": "enable_custom_component_integration",
|
|
99
|
-
"destructiveHint":
|
|
152
|
+
"destructiveHint": True,
|
|
100
153
|
},
|
|
101
154
|
}
|
|
102
155
|
|
|
@@ -179,6 +232,35 @@ def save_tool_config(config: dict[str, Any]) -> bool:
|
|
|
179
232
|
return True
|
|
180
233
|
|
|
181
234
|
|
|
235
|
+
def _render_stub(name: str, meta: ToolStub) -> dict[str, Any]:
|
|
236
|
+
"""Render a ToolStub as the dict shape ``_get_tool_metadata`` returns.
|
|
237
|
+
|
|
238
|
+
Both transform-generated and feature-gated stubs share the same UI
|
|
239
|
+
representation; the only meaningful difference is whether
|
|
240
|
+
``disabled_by`` carries the safety-toggle name (which the JS
|
|
241
|
+
template renders as a "Beta — set X" hint). Annotations come
|
|
242
|
+
through as bools and are dropped from the final dict when False
|
|
243
|
+
so the JSON payload stays small.
|
|
244
|
+
"""
|
|
245
|
+
annotations: dict[str, bool] = {}
|
|
246
|
+
if meta.get("readOnlyHint"):
|
|
247
|
+
annotations["readOnlyHint"] = True
|
|
248
|
+
if meta.get("destructiveHint"):
|
|
249
|
+
annotations["destructiveHint"] = True
|
|
250
|
+
|
|
251
|
+
rendered: dict[str, Any] = {
|
|
252
|
+
"name": name,
|
|
253
|
+
"title": meta["title"],
|
|
254
|
+
"description": meta["description"],
|
|
255
|
+
"tags": [meta["primary_tag"]],
|
|
256
|
+
"primary_tag": meta["primary_tag"],
|
|
257
|
+
"annotations": annotations,
|
|
258
|
+
}
|
|
259
|
+
if "disabled_by" in meta:
|
|
260
|
+
rendered["disabled_by"] = meta["disabled_by"]
|
|
261
|
+
return rendered
|
|
262
|
+
|
|
263
|
+
|
|
182
264
|
async def _get_tool_metadata(server: HomeAssistantSmartMCPServer) -> list[dict[str, Any]]:
|
|
183
265
|
"""Extract metadata for all registered tools from the server.
|
|
184
266
|
|
|
@@ -218,25 +300,21 @@ async def _get_tool_metadata(server: HomeAssistantSmartMCPServer) -> list[dict[s
|
|
|
218
300
|
"annotations": annotations,
|
|
219
301
|
})
|
|
220
302
|
|
|
221
|
-
# Inject stub entries for feature-gated tools that aren't registered
|
|
222
303
|
registered_names = {t["name"] for t in tools}
|
|
304
|
+
|
|
305
|
+
# Inject stub entries for tools generated by FastMCP transforms — these
|
|
306
|
+
# never reach local_provider so they have to be advertised explicitly.
|
|
307
|
+
for name, transform_meta in TRANSFORM_GENERATED_TOOLS.items():
|
|
308
|
+
if name in registered_names:
|
|
309
|
+
continue
|
|
310
|
+
tools.append(_render_stub(name, transform_meta))
|
|
311
|
+
registered_names.add(name)
|
|
312
|
+
|
|
313
|
+
# Inject stub entries for feature-gated tools that aren't registered
|
|
223
314
|
for name, meta in FEATURE_GATED_TOOLS.items():
|
|
224
315
|
if name in registered_names:
|
|
225
316
|
continue
|
|
226
|
-
|
|
227
|
-
if meta.get("readOnlyHint") == "true":
|
|
228
|
-
stub_annotations["readOnlyHint"] = True
|
|
229
|
-
if meta.get("destructiveHint") == "true":
|
|
230
|
-
stub_annotations["destructiveHint"] = True
|
|
231
|
-
tools.append({
|
|
232
|
-
"name": name,
|
|
233
|
-
"title": meta["title"],
|
|
234
|
-
"description": meta["description"],
|
|
235
|
-
"tags": [meta["primary_tag"]],
|
|
236
|
-
"primary_tag": meta["primary_tag"],
|
|
237
|
-
"annotations": stub_annotations,
|
|
238
|
-
"disabled_by": meta["disabled_by"],
|
|
239
|
-
})
|
|
317
|
+
tools.append(_render_stub(name, meta))
|
|
240
318
|
|
|
241
319
|
tools.sort(key=lambda t: (t["primary_tag"], t["name"]))
|
|
242
320
|
return tools
|
|
@@ -380,7 +458,7 @@ _SETTINGS_HTML = """\
|
|
|
380
458
|
<span id="status" class="status">Loading...</span>
|
|
381
459
|
</div>
|
|
382
460
|
<div class="readonly-notice">
|
|
383
|
-
Safety toggles (
|
|
461
|
+
Safety toggles (Tool Search, YAML Config Editing) are managed in the
|
|
384
462
|
add-on configuration page and require a restart to change.
|
|
385
463
|
</div>
|
|
386
464
|
<div class="pin-notice show" id="pinNotice">
|
{ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/best_practice_checker.py
RENAMED
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
Stateless payload inspection — returns warnings pointing to skill reference
|
|
4
4
|
files. Zero overhead on clean calls (returns empty list).
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
callers
|
|
9
|
-
references entirely.
|
|
6
|
+
Warnings include skill:// URIs so the LLM can read the relevant reference
|
|
7
|
+
file via the bundled SkillsDirectoryProvider. The ``skill_prefix`` kwarg
|
|
8
|
+
lets callers pass any URL prefix (e.g., a GitHub mirror) when skill://
|
|
9
|
+
isn't reachable, or ``None`` to omit references entirely.
|
|
10
10
|
|
|
11
11
|
Anti-patterns sourced from:
|
|
12
12
|
https://github.com/homeassistant-ai/skills
|
|
@@ -19,25 +19,8 @@ import re
|
|
|
19
19
|
from typing import Any
|
|
20
20
|
|
|
21
21
|
_SKILL_URI_PREFIX = "skill://home-assistant-best-practices/references"
|
|
22
|
-
_GITHUB_URL_PREFIX = (
|
|
23
|
-
"https://github.com/homeassistant-ai/skills/blob/main"
|
|
24
|
-
"/skills/home-assistant-best-practices/references"
|
|
25
|
-
)
|
|
26
22
|
_DEFAULT_SKILL_PREFIX = _SKILL_URI_PREFIX
|
|
27
23
|
|
|
28
|
-
|
|
29
|
-
def get_skill_prefix() -> str:
|
|
30
|
-
"""Return the appropriate skill prefix based on global settings.
|
|
31
|
-
|
|
32
|
-
When skills are enabled (ENABLE_SKILLS=true), returns skill:// URIs.
|
|
33
|
-
Otherwise falls back to GitHub URLs for the reference files.
|
|
34
|
-
"""
|
|
35
|
-
from ..config import get_global_settings
|
|
36
|
-
|
|
37
|
-
if get_global_settings().enable_skills:
|
|
38
|
-
return _SKILL_URI_PREFIX
|
|
39
|
-
return _GITHUB_URL_PREFIX
|
|
40
|
-
|
|
41
24
|
# ---------------------------------------------------------------------------
|
|
42
25
|
# Regex patterns for template anti-patterns
|
|
43
26
|
# ---------------------------------------------------------------------------
|
{ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/tools_config_automations.py
RENAMED
|
@@ -28,9 +28,6 @@ from ..utils.python_sandbox import (
|
|
|
28
28
|
from .best_practice_checker import (
|
|
29
29
|
check_automation_config as _check_best_practices,
|
|
30
30
|
)
|
|
31
|
-
from .best_practice_checker import (
|
|
32
|
-
get_skill_prefix as _get_skill_prefix,
|
|
33
|
-
)
|
|
34
31
|
from .helpers import (
|
|
35
32
|
exception_to_structured_error,
|
|
36
33
|
log_tool_usage,
|
|
@@ -575,9 +572,7 @@ class AutomationConfigTools:
|
|
|
575
572
|
# Normalize and validate the transformed config
|
|
576
573
|
transformed_config = _normalize_automation_config(transformed_config)
|
|
577
574
|
self._validate_required_fields(transformed_config, identifier)
|
|
578
|
-
bp_warnings = _check_best_practices(
|
|
579
|
-
transformed_config, skill_prefix=_get_skill_prefix()
|
|
580
|
-
)
|
|
575
|
+
bp_warnings = _check_best_practices(transformed_config)
|
|
581
576
|
|
|
582
577
|
# Save transformed config
|
|
583
578
|
result = await self._client.upsert_automation_config(
|
|
@@ -642,9 +637,7 @@ class AutomationConfigTools:
|
|
|
642
637
|
self._validate_required_fields(config_dict, identifier)
|
|
643
638
|
|
|
644
639
|
# Pre-check for best-practice issues.
|
|
645
|
-
bp_warnings = _check_best_practices(
|
|
646
|
-
config_dict, skill_prefix=_get_skill_prefix()
|
|
647
|
-
)
|
|
640
|
+
bp_warnings = _check_best_practices(config_dict)
|
|
648
641
|
|
|
649
642
|
# Cross-check literal service and entity references against
|
|
650
643
|
# the live registries. Soft warnings only — the write still
|
{ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/tools_config_scripts.py
RENAMED
|
@@ -22,9 +22,6 @@ from ..utils.python_sandbox import (
|
|
|
22
22
|
from .best_practice_checker import (
|
|
23
23
|
check_script_config as _check_best_practices,
|
|
24
24
|
)
|
|
25
|
-
from .best_practice_checker import (
|
|
26
|
-
get_skill_prefix as _get_skill_prefix,
|
|
27
|
-
)
|
|
28
25
|
from .helpers import (
|
|
29
26
|
exception_to_structured_error,
|
|
30
27
|
log_tool_usage,
|
|
@@ -476,9 +473,7 @@ class ConfigScriptTools:
|
|
|
476
473
|
context={"action": "python_transform", "script_id": script_id},
|
|
477
474
|
)
|
|
478
475
|
)
|
|
479
|
-
bp_warnings = _check_best_practices(
|
|
480
|
-
transformed_config, skill_prefix=_get_skill_prefix()
|
|
481
|
-
)
|
|
476
|
+
bp_warnings = _check_best_practices(transformed_config)
|
|
482
477
|
|
|
483
478
|
# Save transformed config
|
|
484
479
|
result = await self._client.upsert_script_config(
|
|
@@ -524,9 +519,7 @@ class ConfigScriptTools:
|
|
|
524
519
|
await self._fetch_and_verify_hash(script_id, config_hash, "set")
|
|
525
520
|
|
|
526
521
|
# Pre-check for best-practice issues.
|
|
527
|
-
bp_warnings = _check_best_practices(
|
|
528
|
-
config_dict, skill_prefix=_get_skill_prefix()
|
|
529
|
-
)
|
|
522
|
+
bp_warnings = _check_best_practices(config_dict)
|
|
530
523
|
|
|
531
524
|
# Cross-check literal service and entity references against
|
|
532
525
|
# the live registries. Soft warnings only — the write still
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ha-mcp-dev
|
|
3
|
-
Version: 7.4.1.
|
|
3
|
+
Version: 7.4.1.dev443
|
|
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
|
|
@@ -254,12 +254,9 @@ An MCP server can create automations, helpers, and dashboards, but it has no opi
|
|
|
254
254
|
|
|
255
255
|
### Bundled Skills (built-in)
|
|
256
256
|
|
|
257
|
-
Skills from `homeassistant-ai/skills` are bundled and served as [MCP resources](https://modelcontextprotocol.io/docs/concepts/resources) via `skill://` URIs. Any MCP client that supports resources can discover them automatically — no manual installation needed.
|
|
257
|
+
Skills from `homeassistant-ai/skills` are bundled and served as [MCP resources](https://modelcontextprotocol.io/docs/concepts/resources) via `skill://` URIs. Any MCP client that supports resources can discover them automatically — no manual installation needed. For tool-only clients, the same skills are also exposed as `ha_list_resources` / `ha_read_resource` tools. Resources are not auto-injected into context — clients must explicitly request them, so idle context cost is just the metadata listing.
|
|
258
258
|
|
|
259
|
-
|
|
260
|
-
|---------|---------|-------------|
|
|
261
|
-
| `ENABLE_SKILLS` | `true` | Serve skills as MCP resources. Resources are not auto-injected into context — clients must explicitly request them. |
|
|
262
|
-
| `ENABLE_SKILLS_AS_TOOLS` | `true` | Expose skills and doc resources via `list_resources`/`read_resource` tools. Resource-capable clients can set to `false` to reduce tool count. |
|
|
259
|
+
If you want to hide either tool from the catalog, disable it from the web settings UI like any other tool.
|
|
263
260
|
|
|
264
261
|
Skills can still be installed manually for clients that prefer local skill files — see the [skills repo](https://github.com/homeassistant-ai/skills) for instructions.
|
|
265
262
|
|
|
@@ -344,6 +341,8 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
|
|
|
344
341
|
- **[@gcormier](https://github.com/gcormier)** — Windows installer improvements: removed unused variable and fixed terminal closing after install.
|
|
345
342
|
- **[@ekobres](https://github.com/ekobres)** — Feature flags for `HAMCP_ENABLE_FILESYSTEM_TOOLS` and `HAMCP_ENABLE_CUSTOM_COMPONENT_INTEGRATION` in the add-on config, with beta tagging in source and docs.
|
|
346
343
|
- **[@w3z315](https://github.com/w3z315)** — Financial support via [GitHub Sponsors](https://github.com/sponsors/julienld). Thank you! ☕
|
|
344
|
+
- **[@griffinmartin](https://github.com/griffinmartin)** — Added OpenCode (by Anomaly) as a selectable AI client in the setup wizard, with both stdio and streamable HTTP support.
|
|
345
|
+
- **[@hhopke](https://github.com/hhopke)** — Fixed addon API calls to route through HA Core ingress proxy instead of direct container connections, fixing `ha_manage_addon` proxy mode on addon installs.
|
|
347
346
|
|
|
348
347
|
---
|
|
349
348
|
|
|
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.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/resources/skills-vendor/AGENTS.md
RENAMED
|
File without changes
|
{ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/resources/skills-vendor/CLAUDE.md
RENAMED
|
File without changes
|
|
File without changes
|
{ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/resources/skills-vendor/LICENSE
RENAMED
|
File without changes
|
{ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/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
|
|
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.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/tools_config_dashboards.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp/tools/tools_config_entry_flow.py
RENAMED
|
File without changes
|
{ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/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.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/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.dev441 → ha_mcp_dev-7.4.1.dev443}/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.dev441 → ha_mcp_dev-7.4.1.dev443}/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.dev441 → ha_mcp_dev-7.4.1.dev443}/src/ha_mcp_dev.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{ha_mcp_dev-7.4.1.dev441 → ha_mcp_dev-7.4.1.dev443}/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
|