Skip to content

Commit fd39c73

Browse files
committed
feat: drop ENABLE_SKILLS / ENABLE_SKILLS_AS_TOOLS toggles, rename to ha_list_resources / ha_read_resource
The two skills toggles were redundant once homeassistant-ai#960 (web settings UI) shipped: the original justifications — "avoid local-skill conflicts" (skills served via skill:// URIs and locally-installed skills under ~/.claude/skills/ live in separate namespaces) and "don't burn extra context" (resources are not auto-injected; list_resources only returns metadata) — don't survive scrutiny. Per-tool visibility is now the single mechanism; users who want ha_list_resources or ha_read_resource off can disable them from the web settings UI like any other tool. Changes: - Drop enable_skills, enable_skills_as_tools, _skills_dependency validator from src/ha_mcp/config.py. - Always register SkillsDirectoryProvider and the ResourcesAsTools transform in server._register_skills(); always include the skills hint in _build_skills_instructions and the search-tool description. - Rename the transform-generated tools list_resources / read_resource to ha_list_resources / ha_read_resource via a HaResourcesAsTools subclass so they follow the project's ha_<verb>_<noun> convention. - Surface the renamed pair in the settings UI: extend _get_tool_metadata with TRANSFORM_GENERATED_TOOLS stub injection, so users can disable them per-tool (FastMCP's local_provider doesn't see transform-appended tools). - Drop add-on toggles: enable_skills / enable_skills_as_tools removed from homeassistant-addon{,-dev}/config.yaml schema + options, the env-var setup and the .skills_as_tools_default_migration_v1 migration helper removed from homeassistant-addon/start.py, and the supervisor labels removed from homeassistant-addon-dev/translations/en.yaml (prod translations sync at the next biweekly release). - Simplify best_practice_checker: drop get_skill_prefix() and its callers; always reference skill:// URIs. - Tests: replace the obsolete enable_skills* assertions with field-removal assertions, drop TestSkillsAsToolsMigration, update unit tests for unconditional registration, add e2e coverage that ha_list_resources / ha_read_resource appear (and unprefixed names don't), add a unit test that _get_tool_metadata injects stubs when local_provider omits them. - README and homeassistant-addon-dev/DOCS.md: remove the toggle rows; document the per-tool visibility path. Migration note: existing users with ENABLE_SKILLS_AS_TOOLS=false lose the env-var path. Equivalent opt-out is available per-tool in the web settings UI, documented in DOCS.md and README. Same outcome is achievable via the alternate mechanism, so this is not a breaking change per AGENTS.md's definition. Closes homeassistant-ai#1133
1 parent a7355c8 commit fd39c73

17 files changed

Lines changed: 221 additions & 519 deletions

README.md

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -225,12 +225,9 @@ An MCP server can create automations, helpers, and dashboards, but it has no opi
225225

226226
### Bundled Skills (built-in)
227227

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.
229229

230-
| Setting | Default | Description |
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.
234231

235232
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.
236233

homeassistant-addon-dev/DOCS.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@ The dev add-on uses the same configuration as the stable version. See the main a
1414
|--------|-------------|---------|
1515
| `backup_hint` | Backup strength preference | `normal` |
1616
| `secret_path` | Custom secret path (optional) | auto-generated |
17-
| `enable_skills` | Serve bundled HA best-practice skills as MCP resources | `true` |
18-
| `enable_skills_as_tools` | Expose skills via list_resources/read_resource tools | `true` |
1917
| `enable_tool_search` | Replace full tool catalog with search-based discovery (~46K → ~5K tokens). ⚠️ Do NOT enable for Claude Sonnet/Opus — their built-in tool search conflicts with ha-mcp's. Disable one or the other. | `false` |
2018
| `enable_yaml_config_editing` *(beta)* | Enables `ha_config_set_yaml` for editing `configuration.yaml` directly. Requires `ha_mcp_tools` custom component. | `false` |
2119
| `enable_filesystem_tools` *(beta)* | Enables file read/write tools (`ha_list_files`, `ha_read_file`, `ha_write_file`, `ha_delete_file`). Requires `ha_mcp_tools` custom component. | `false` |
@@ -25,6 +23,8 @@ The dev add-on uses the same configuration as the stable version. See the main a
2523
| `pinned_tools` | Comma-separated list of tool names to pin when tool search is enabled (seed value; web UI is primary) | empty |
2624
| `verify_ssl` | Verify the HA server's TLS certificate. Disable for self-signed certs or hostname mismatches. Weakens security — leave on unless needed. | `true` |
2725

26+
*Removed in 7.4.x:* `enable_skills` *and* `enable_skills_as_tools`*. Bundled skills are now always served; to hide* `ha_list_resources` *or* `ha_read_resource` *from the catalog, disable them from the Tool Settings Web UI (see below).*
27+
2828
Beta options are hidden under "Show unused optional configuration options" in the add-on Configuration tab. See [beta.md](https://github.com/homeassistant-ai/ha-mcp/blob/master/docs/beta.md) for details.
2929

3030
## Tool Settings Web UI

homeassistant-addon-dev/config.yaml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@ host_network: true
2020
image: "ghcr.io/homeassistant-ai/ha-mcp-addon-dev-{arch}"
2121
options:
2222
backup_hint: "normal"
23-
enable_skills: true
24-
enable_skills_as_tools: true
2523
enable_tool_search: false
2624
enable_yaml_config_editing: false
2725
tool_search_max_results: 5
@@ -31,8 +29,6 @@ options:
3129
schema:
3230
backup_hint: list(strong|normal|weak|auto)
3331
secret_path: str?
34-
enable_skills: bool?
35-
enable_skills_as_tools: bool?
3632
enable_tool_search: bool?
3733
enable_yaml_config_editing: bool?
3834
enable_filesystem_tools: bool?

homeassistant-addon-dev/translations/en.yaml

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,6 @@ configuration:
77
name: Secret path override
88
description: |
99
Optional custom HTTP path for the MCP server. Leave empty to use the auto-generated secure path.
10-
enable_skills:
11-
name: Enable skills
12-
description: >-
13-
Serve bundled Home Assistant best-practice skills as MCP resources.
14-
Skills provide automation patterns, helper selection guides, and device
15-
control best practices. Clients must explicitly request them.
16-
enable_skills_as_tools:
17-
name: Enable skills as tools
18-
description: >-
19-
Expose skills via list_resources/read_resource tools for MCP clients
20-
that don't support resources natively. Adds 3 extra tools.
2110
enable_tool_search:
2211
name: Enable tool search
2312
description: >-

homeassistant-addon/config.yaml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,11 @@ image: "ghcr.io/homeassistant-ai/ha-mcp-addon-{arch}"
2727
# from homeassistant-addon-dev/config.yaml without reading that doc first.
2828
options:
2929
backup_hint: "normal"
30-
enable_skills: true
31-
enable_skills_as_tools: true
3230
enable_tool_search: false
3331
verify_ssl: true
3432
schema:
3533
backup_hint: list(strong|normal|weak|auto)
3634
secret_path: str?
37-
enable_skills: bool?
38-
enable_skills_as_tools: bool?
3935
enable_tool_search: bool?
4036
verify_ssl: bool?
4137
advanced_debug_logging: bool?

homeassistant-addon/start.py

Lines changed: 0 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -179,86 +179,6 @@ def resolve_bool_option(config: dict[str, Any], key: str, default: bool) -> bool
179179
return raw if isinstance(raw, bool) else default
180180

181181

182-
SKILLS_AS_TOOLS_MIGRATION_MARKER = ".skills_as_tools_default_migration_v1"
183-
184-
185-
def migrate_skills_as_tools_default(
186-
data_dir: Path,
187-
config_file: Path,
188-
stored_value: bool,
189-
config_read_ok: bool,
190-
) -> bool:
191-
"""One-time migration to force enable_skills_as_tools=true for existing users.
192-
193-
The Pydantic default in src/ha_mcp/config.py was flipped to True in
194-
#806, but the add-on's config.yaml was never updated at the same time.
195-
For add-on installs the env var is written from options.json before
196-
ha-mcp reads its Pydantic settings, so the new Python default never
197-
took effect for existing users. This runs exactly once per install
198-
(guarded by a marker file in /data) and forces the flag on for users
199-
who still have False stored, then persists the new value to
200-
options.json so the supervisor UI reflects it. On subsequent boots the
201-
marker is present and the stored value is respected, so users who
202-
deliberately toggle it off will not be re-forced.
203-
204-
config_read_ok must be False when the caller could not load
205-
options.json (file unreadable or malformed JSON). In that case the
206-
marker is not created, so the migration can run again on a later
207-
boot once options.json is readable and expose the user's real
208-
stored value.
209-
"""
210-
marker = data_dir / SKILLS_AS_TOOLS_MIGRATION_MARKER
211-
if marker.exists():
212-
return stored_value
213-
214-
# First run after this update. Force-on + persist only if the user is
215-
# currently on False, then create the marker so the migration does
216-
# not loop — but skip marker creation when the caller could not
217-
# verify the stored value (see config_read_ok in the docstring).
218-
if not stored_value:
219-
log_info(
220-
"One-time migration: forcing enable_skills_as_tools=true. "
221-
"The Pydantic default was set to True in #806 but the add-on's "
222-
"config.yaml was not updated alongside it, so this value stayed "
223-
"False for existing add-on installs. Future user-initiated "
224-
"changes to this setting will be respected."
225-
)
226-
if config_file.exists():
227-
try:
228-
with open(config_file, encoding="utf-8") as f:
229-
opts = json.load(f)
230-
if isinstance(opts, dict):
231-
opts["enable_skills_as_tools"] = True
232-
with open(config_file, "w", encoding="utf-8") as f:
233-
json.dump(opts, f, indent=2)
234-
f.write("\n")
235-
log_info("Persisted enable_skills_as_tools=true to options.json")
236-
else:
237-
log_error(
238-
"Cannot persist migration to options.json: top-level "
239-
f"is {type(opts).__name__}, expected dict. Runtime "
240-
"override still applied for this session."
241-
)
242-
except (OSError, json.JSONDecodeError) as e:
243-
log_error(
244-
f"Failed to persist migration to options.json "
245-
f"(operation: persist_skills_as_tools_migration): {e}. "
246-
"Runtime override still applied for this session."
247-
)
248-
stored_value = True
249-
250-
if config_read_ok:
251-
try:
252-
marker.touch()
253-
except OSError as e:
254-
log_error(
255-
f"Failed to create migration marker "
256-
f"(operation: create_skills_as_tools_marker): {e}"
257-
)
258-
259-
return stored_value
260-
261-
262182
def main() -> int:
263183
"""Start the Home Assistant MCP Server."""
264184
log_info("Starting Home Assistant MCP Server...")
@@ -269,8 +189,6 @@ def main() -> int:
269189
config: dict[str, Any] = {}
270190
backup_hint = "normal" # default
271191
custom_secret_path = "" # default
272-
enable_skills = True # default
273-
enable_skills_as_tools = True # default
274192
enable_tool_search = False # default
275193
enable_yaml_config_editing = False # default
276194
enable_filesystem_tools = False # default
@@ -280,18 +198,13 @@ def main() -> int:
280198
pinned_tools_raw = "" # default
281199
verify_ssl = True # default
282200
advanced_debug_logging = False # default
283-
config_read_ok = True
284201

285202
if config_file.exists():
286203
try:
287204
with open(config_file) as f:
288205
config = json.load(f)
289206
backup_hint = config.get("backup_hint", "normal")
290207
custom_secret_path = config.get("secret_path", "")
291-
raw_skills = config.get("enable_skills", True)
292-
enable_skills = raw_skills if isinstance(raw_skills, bool) else True
293-
raw_skills_as_tools = config.get("enable_skills_as_tools", True)
294-
enable_skills_as_tools = raw_skills_as_tools if isinstance(raw_skills_as_tools, bool) else True
295208
raw_tool_search = config.get("enable_tool_search", False)
296209
enable_tool_search = raw_tool_search if isinstance(raw_tool_search, bool) else False
297210
raw_yaml_config = config.get("enable_yaml_config_editing", False)
@@ -310,16 +223,6 @@ def main() -> int:
310223
advanced_debug_logging = resolve_bool_option(config, "advanced_debug_logging", False)
311224
except Exception as e:
312225
log_error(f"Failed to read config: {e}, using defaults")
313-
config_read_ok = False
314-
315-
# One-time migration: add-on users whose stored value is False predate
316-
# this release's config.yaml default flip. See migrate_skills_as_tools_default.
317-
enable_skills_as_tools = migrate_skills_as_tools_default(
318-
data_dir=data_dir,
319-
config_file=config_file,
320-
stored_value=enable_skills_as_tools,
321-
config_read_ok=config_read_ok,
322-
)
323226

324227
# Validate Supervisor token (needed for both ha-mcp auth below and the
325228
# options-persist call right after secret path resolution)
@@ -344,8 +247,6 @@ def main() -> int:
344247
# Set up environment for ha-mcp
345248
os.environ["HOMEASSISTANT_URL"] = "http://supervisor/core"
346249
os.environ["BACKUP_HINT"] = backup_hint
347-
os.environ["ENABLE_SKILLS"] = str(enable_skills).lower()
348-
os.environ["ENABLE_SKILLS_AS_TOOLS"] = str(enable_skills_as_tools).lower()
349250
os.environ["ENABLE_TOOL_SEARCH"] = str(enable_tool_search).lower()
350251
os.environ["ENABLE_YAML_CONFIG_EDITING"] = str(enable_yaml_config_editing).lower()
351252
os.environ["HAMCP_ENABLE_FILESYSTEM_TOOLS"] = str(enable_filesystem_tools).lower()

src/ha_mcp/config.py

Lines changed: 1 addition & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from pathlib import Path
1010

1111
from dotenv import load_dotenv
12-
from pydantic import Field, field_validator, model_validator
12+
from pydantic import Field, field_validator
1313
from pydantic_settings import BaseSettings, SettingsConfigDict
1414

1515
from ha_mcp._version import get_version
@@ -92,17 +92,6 @@ class Settings(BaseSettings):
9292
True, alias="ENABLE_DASHBOARD_PARTIAL_TOOLS"
9393
)
9494

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-
10695
# Tool search transform — replaces the full tool catalog with a unified
10796
# BM25 search tool and categorized call proxies (read/write/delete).
10897
# Dramatically reduces idle context token usage for LLMs.
@@ -124,18 +113,6 @@ class Settings(BaseSettings):
124113
# supervisor UI rejects out-of-range values before they reach env vars.
125114
tool_search_max_results: int = Field(5, ge=2, le=10, alias="TOOL_SEARCH_MAX_RESULTS")
126115

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-
139116
@property
140117
def env_file_name(self) -> str:
141118
"""Get the current environment file name."""

0 commit comments

Comments
 (0)