Skip to content

Commit f3ea361

Browse files
committed
Merge branch 'pr-960' into addon-repo
2 parents dc7a93a + 0b5e8db commit f3ea361

7 files changed

Lines changed: 774 additions & 1 deletion

File tree

homeassistant-addon-dev/config.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ arch:
1010
init: false
1111
startup: application
1212
boot: manual
13+
ingress: true
14+
ingress_port: 9583
15+
ingress_stream: true
1316
hassio_api: true
1417
hassio_role: default
1518
homeassistant_api: true
@@ -21,12 +24,18 @@ options:
2124
enable_skills_as_tools: false
2225
enable_tool_search: false
2326
enable_yaml_config_editing: false
27+
tool_search_max_results: 5
28+
disabled_tools: ""
29+
pinned_tools: ""
2430
schema:
2531
backup_hint: list(strong|normal|weak|auto)
2632
secret_path: str?
2733
enable_skills: bool?
2834
enable_skills_as_tools: bool?
2935
enable_tool_search: bool?
3036
enable_yaml_config_editing: bool?
37+
tool_search_max_results: int?
38+
disabled_tools: str?
39+
pinned_tools: str?
3140
ports:
3241
9583/tcp: 9583

homeassistant-addon-dev/translations/en.yaml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,22 @@ configuration:
3535
like homeassistant, http, and recorder are blocked. A backup is
3636
created before every edit. Use for YAML-only features that have no
3737
UI or API alternative. Requires restart to take effect.
38+
tool_search_max_results:
39+
name: Tool search max results
40+
description: >-
41+
Maximum number of tools returned by ha_search_tools when tool
42+
search is enabled. Lower values (2-3) save context tokens but
43+
may miss relevant tools. Range: 2-10. Requires restart.
44+
disabled_tools:
45+
name: Disabled tools (text fallback)
46+
description: >-
47+
Comma-separated tool names to disable. For a visual interface,
48+
click "Open Web UI" on the addon info page. This field seeds the initial config
49+
when the web UI hasn't been used yet. Requires restart.
50+
pinned_tools:
51+
name: Pinned tools (text fallback)
52+
description: >-
53+
Comma-separated tool names to pin (always visible in tool search).
54+
For a visual interface, click "Open Web UI" on the addon info page. This field
55+
seeds the initial config when the web UI hasn't been used yet.
56+
Requires restart.

homeassistant-addon/start.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,9 @@ def main() -> int:
110110
enable_skills_as_tools = False # default
111111
enable_tool_search = False # default
112112
enable_yaml_config_editing = False # default
113+
tool_search_max_results = 5 # default
114+
disabled_tools_raw = "" # default
115+
pinned_tools_raw = "" # default
113116

114117
if config_file.exists():
115118
try:
@@ -125,6 +128,12 @@ def main() -> int:
125128
enable_tool_search = raw_tool_search if isinstance(raw_tool_search, bool) else False
126129
raw_yaml_config = config.get("enable_yaml_config_editing", False)
127130
enable_yaml_config_editing = raw_yaml_config if isinstance(raw_yaml_config, bool) else False
131+
raw_max_results = config.get("tool_search_max_results", 5)
132+
tool_search_max_results = raw_max_results if isinstance(raw_max_results, int) else 5
133+
raw_disabled = config.get("disabled_tools", "")
134+
disabled_tools_raw = raw_disabled if isinstance(raw_disabled, str) else ""
135+
raw_pinned = config.get("pinned_tools", "")
136+
pinned_tools_raw = raw_pinned if isinstance(raw_pinned, str) else ""
128137
except Exception as e:
129138
log_error(f"Failed to read config: {e}, using defaults")
130139

@@ -140,6 +149,9 @@ def main() -> int:
140149
os.environ["ENABLE_SKILLS_AS_TOOLS"] = str(enable_skills_as_tools).lower()
141150
os.environ["ENABLE_TOOL_SEARCH"] = str(enable_tool_search).lower()
142151
os.environ["ENABLE_YAML_CONFIG_EDITING"] = str(enable_yaml_config_editing).lower()
152+
os.environ["TOOL_SEARCH_MAX_RESULTS"] = str(tool_search_max_results)
153+
os.environ["DISABLED_TOOLS"] = disabled_tools_raw
154+
os.environ["PINNED_TOOLS"] = pinned_tools_raw
143155

144156
# Validate Supervisor token
145157
supervisor_token = os.environ.get("SUPERVISOR_TOKEN")

src/ha_mcp/config.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,15 @@ class Settings(BaseSettings):
112112
# files. Disabled by default; only for YAML-only features with no UI/API path.
113113
enable_yaml_config_editing: bool = Field(False, alias="ENABLE_YAML_CONFIG_EDITING")
114114

115+
# Seed values for tool visibility (comma-separated tool names).
116+
# Used as initial config when no tool_config.json exists.
117+
# The web settings UI (/settings) is the primary interface for managing these.
118+
disabled_tools: str = Field("", alias="DISABLED_TOOLS")
119+
pinned_tools: str = Field("", alias="PINNED_TOOLS")
120+
121+
# Max results returned by ha_search_tools (2-10).
122+
tool_search_max_results: int = Field(5, alias="TOOL_SEARCH_MAX_RESULTS")
123+
115124
@model_validator(mode="after")
116125
def _skills_dependency(self) -> "Settings":
117126
"""Auto-enable skills (resources) when skills-as-tools is on.

src/ha_mcp/server.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,10 @@ def _initialize_server(self) -> None:
143143
# Register bundled skills as MCP resources
144144
self._register_skills()
145145

146+
# Apply user-configured tool visibility (after all tools registered,
147+
# before tool search transform wraps them)
148+
self._apply_settings_ui()
149+
146150
# Apply tool search transform (must come after all tools and
147151
# ResourcesAsTools are registered so it can wrap everything)
148152
self._apply_tool_search()
@@ -306,6 +310,23 @@ def _build_skill_block(self, skill_name: str, main_file: Path) -> str | None:
306310

307311
return f"\n### Skill: {skill_name} ({uri})\n{description.strip()}"
308312

313+
def _apply_settings_ui(self) -> None:
314+
"""Register settings web UI and apply persisted tool visibility."""
315+
from .settings_ui import (
316+
apply_tool_visibility,
317+
load_tool_config,
318+
register_settings_routes,
319+
)
320+
321+
register_settings_routes(self.mcp, self)
322+
323+
config = load_tool_config(self.settings)
324+
if config:
325+
pinned = apply_tool_visibility(self.mcp, config, self.settings)
326+
if pinned:
327+
self._user_pinned_tools = list(pinned)
328+
logger.info("Applied persisted tool config (%d entries)", len(config.get("tools", {})))
329+
309330
# Tools pinned outside the search transform for individual permission gating.
310331
# These are always visible in list_tools() regardless of search transform.
311332
_PINNED_TOOLS: ClassVar[list[str]] = list(DEFAULT_PINNED_TOOLS)
@@ -419,8 +440,9 @@ def _apply_tool_search(self) -> None:
419440
)
420441
return
421442

422-
# Build the always_visible list
443+
# Build the always_visible list: defaults + user-configured pins
423444
pinned = list(self._PINNED_TOOLS)
445+
pinned.extend(getattr(self, "_user_pinned_tools", []))
424446

425447
# Pin ResourcesAsTools and skill guidance tools if skills-as-tools is enabled
426448
if self.settings.enable_skills_as_tools:

0 commit comments

Comments
 (0)