Commit 9783f34
feat: web-based settings UI for per-tool enable/disable/pin (#960)
* feat: web-based settings UI for per-tool enable/disable/pin
Add a self-contained HTML settings page served via FastMCP's
custom_route at /settings. Provides searchable, grouped tool
management with three states per tool: enabled, pinned, disabled.
- GET /settings — serves the settings page (inline HTML/CSS/JS)
- GET /api/settings/tools — returns tool metadata + current states
- POST /api/settings/tools — saves states, applies immediately via
mcp.disable()/mcp.enable() (no restart needed for tool changes)
- Persists to tool_config.json in addon data dir or ~/.ha-mcp/
- Seeds from DISABLED_TOOLS/PINNED_TOOLS env vars on first run
- Mandatory tools (ha_search_entities, ha_get_overview, ha_get_state,
ha_report_issue) shown grayed out, cannot be disabled
- enable_yaml_config_editing toggle respected as override
- Works across all install methods (addon, Docker, standalone)
- Dark theme matching HA aesthetic
Closes #798
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address CI failures and Gemini review comments
- Import DEFAULT_PINNED_TOOLS from transforms (avoid duplication)
- Catch specific exceptions (OSError, json.JSONDecodeError) instead of
broad Exception
- Fix mypy no-any-return: annotate json.loads return types
- Fix ruff C401: use set comprehension instead of set(generator)
- Fix ruff C420: use dict.fromkeys instead of dict comprehension
- Use structured error format in POST endpoint responses
- Make tools.json path discovery check multiple locations
- Fix ValueError/TypeError catch for JSON parsing in POST handler
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add ingress config and text field fallbacks for addon
- Enable ingress (shows "Open Web UI" button on addon info page)
- Add disabled_tools/pinned_tools text fields as seed/fallback
- Add translations for the new fields
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add ingress_stream, serve settings at root for ingress
- Add ingress_stream: true for WebSocket passthrough
- Remove panel_icon (not needed for "Open Web UI" button)
- Serve settings page at both / and /settings so ingress root works
- Ingress proxies to http://localhost:9583/ which needs a handler
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add ingress + text fields to addon-dev config
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: revert homeassistant-addon/ to master (release pipeline handles it)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use live FastMCP list_tools() and relative fetch URLs
- Replace broken _tool_manager._tools internal API with the public
await mcp.list_tools() call
- Remove tools.json fallback — was a dev-only path, wouldn't exist in
production containers anyway
- Use relative './api/settings/tools' fetch URLs so requests work both
directly and through ingress proxy (ESPHome/Node-RED pattern)
- Fix translation description wording ("on the addon info page")
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: dual toggles, feature-gated stubs, tool_search_max_results, grouping fix
Settings UI rework:
- Replace dropdown with two toggles (enabled + pinned) per tool
- Pinned toggle disabled/grayed out when enabled toggle is off
- Add banner note explaining pinning only applies with tool search
- Show feature-gated tools (ha_config_set_yaml, filesystem tools) as
stub entries with a "Requires X in add-on config" note — their
toggles are locked since they can't be enabled at runtime
Tool grouping fix:
- Use local_provider._list_tools() to see ALL registered tools
regardless of runtime enable state (so users can re-enable them)
- Sort tags alphabetically and prefer non-secondary tags for primary
group (Device Registry instead of Z-Wave for ha_get_device)
Config additions:
- tool_search_max_results field in addon-dev config.yaml + translations
- disabled_tools/pinned_tools text fields as seed values
- start.py wires all new env vars through
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: remove unused type: ignore on _list_tools
* feat: preserve group open state, add per-group master toggle
- Persist open groups in a Set that survives re-renders (fixes
collapse-on-toggle-click bug where clicking any tool toggle would
call render() and wipe the expanded state)
- Add master enable/disable toggle per group in the header
- Master toggle affects all non-mandatory, non-feature-gated tools
- Stop propagation on master toggle so clicking it doesn't also
expand/collapse the group
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: require restart to apply tool visibility changes
Runtime mcp.enable/disable calls accumulate visibility transforms in
the provider's _transforms list every save, causing stale transforms
to pile up. More importantly, they don't reliably remove tools from
the LLM's tool list in practice — tools still appear in list_tools()
output with full schema, just fail at call time with "Unknown tool".
New approach: save changes to tool_config.json and require an add-on
restart. Startup-time apply_tool_visibility() reads the config and
applies visibility once, cleanly. Disabled tools are then fully absent
from list_tools() on next startup.
- Remove runtime mcp.enable/disable from POST handler
- Show "Saved — restart required" status after save
- Show prominent red restart-required banner in UI
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add in-UI restart button using Supervisor API
- New POST /api/settings/restart endpoint calls
http://supervisor/addons/self/restart with SUPERVISOR_TOKEN
- New GET /api/settings/info exposes whether running as add-on
- Frontend shows "Restart Add-on" button in the restart notice banner
(only visible when running as add-on)
- Click opens a confirmation dialog and triggers the restart
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: treat dropped connection as success in restart handler
The Supervisor kills our process while the restart request is in
flight, causing httpx to throw ReadError/RemoteProtocolError. That's
actually the SUCCESS path — the restart is happening. Catch those
specifically and return success.
Also surface the real Supervisor error message in the browser when a
real failure occurs (was showing generic "Restart failed" before).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs: document settings UI in addon DOCS and .env.example
Add-on DOCS.md:
- Document new options: enable_skills, enable_skills_as_tools,
enable_tool_search, enable_yaml_config_editing,
tool_search_max_results, disabled_tools, pinned_tools
- Add "Tool Settings Web UI" section explaining the web UI features,
restart requirement, and text-field fallback
.env.example:
- Add DISABLED_TOOLS, PINNED_TOOLS, TOOL_SEARCH_MAX_RESULTS
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address Gemini review feedback
- Use create_error_response/ErrorCode for all REST endpoint errors
(settings save, restart, validation)
- Validate states dict from client (string keys, allowed state values)
before persisting to disk
- Type hint settings parameter as Settings (TYPE_CHECKING import)
- Initialize _user_pinned_tools in __init__ instead of getattr fallback
- Add clearer docstring explaining why _list_tools() (private API)
is used: public list_tools() filters disabled tools, and the
settings UI specifically needs the unfiltered list
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: remove unnecessary type annotation quotes (UP037)
* fix: pass real FastMCP to register_settings_routes (mypy)
mypy rejects the _DeferredMCP wrapper as FastMCP[Any]. _get_server()
forces the lazy init, then we pass server.mcp (the real FastMCP) into
register_settings_routes. register_browser_landing is left alone since
its signature already accepts the union FastMCP | _DeferredMCP.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: kingpanther13 <kingpanther13@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>1 parent 8ba80ae commit 9783f34
10 files changed
Lines changed: 1289 additions & 4 deletions
File tree
- homeassistant-addon-dev
- translations
- homeassistant-addon
- src/ha_mcp
- tests/src/unit
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
32 | 32 | | |
33 | 33 | | |
34 | 34 | | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
35 | 44 | | |
36 | 45 | | |
37 | | - | |
| 46 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
14 | 14 | | |
15 | 15 | | |
16 | 16 | | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
17 | 20 | | |
18 | 21 | | |
19 | 22 | | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
20 | 26 | | |
21 | 27 | | |
22 | 28 | | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
23 | 52 | | |
24 | 53 | | |
25 | 54 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
10 | 10 | | |
11 | 11 | | |
12 | 12 | | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
13 | 16 | | |
14 | 17 | | |
15 | 18 | | |
| |||
21 | 24 | | |
22 | 25 | | |
23 | 26 | | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
24 | 30 | | |
25 | 31 | | |
26 | 32 | | |
| |||
30 | 36 | | |
31 | 37 | | |
32 | 38 | | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
33 | 42 | | |
34 | 43 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
56 | 56 | | |
57 | 57 | | |
58 | 58 | | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
263 | 263 | | |
264 | 264 | | |
265 | 265 | | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
266 | 269 | | |
267 | 270 | | |
268 | 271 | | |
| |||
283 | 286 | | |
284 | 287 | | |
285 | 288 | | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
286 | 295 | | |
287 | 296 | | |
288 | 297 | | |
| |||
323 | 332 | | |
324 | 333 | | |
325 | 334 | | |
| 335 | + | |
| 336 | + | |
| 337 | + | |
326 | 338 | | |
327 | 339 | | |
328 | 340 | | |
| |||
351 | 363 | | |
352 | 364 | | |
353 | 365 | | |
| 366 | + | |
354 | 367 | | |
355 | 368 | | |
356 | 369 | | |
357 | 370 | | |
| 371 | + | |
358 | 372 | | |
359 | 373 | | |
| 374 | + | |
| 375 | + | |
| 376 | + | |
| 377 | + | |
| 378 | + | |
| 379 | + | |
| 380 | + | |
360 | 381 | | |
361 | 382 | | |
362 | 383 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
733 | 733 | | |
734 | 734 | | |
735 | 735 | | |
| 736 | + | |
| 737 | + | |
736 | 738 | | |
737 | 739 | | |
| 740 | + | |
738 | 741 | | |
739 | 742 | | |
740 | 743 | | |
| |||
871 | 874 | | |
872 | 875 | | |
873 | 876 | | |
| 877 | + | |
| 878 | + | |
| 879 | + | |
874 | 880 | | |
875 | 881 | | |
876 | 882 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
110 | 110 | | |
111 | 111 | | |
112 | 112 | | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
113 | 124 | | |
114 | 125 | | |
115 | 126 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
70 | 70 | | |
71 | 71 | | |
72 | 72 | | |
| 73 | + | |
| 74 | + | |
73 | 75 | | |
74 | 76 | | |
75 | 77 | | |
| |||
143 | 145 | | |
144 | 146 | | |
145 | 147 | | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
146 | 153 | | |
147 | 154 | | |
148 | 155 | | |
| |||
313 | 320 | | |
314 | 321 | | |
315 | 322 | | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
| 331 | + | |
| 332 | + | |
| 333 | + | |
| 334 | + | |
| 335 | + | |
| 336 | + | |
| 337 | + | |
| 338 | + | |
| 339 | + | |
| 340 | + | |
| 341 | + | |
316 | 342 | | |
317 | 343 | | |
318 | 344 | | |
| |||
487 | 513 | | |
488 | 514 | | |
489 | 515 | | |
490 | | - | |
| 516 | + | |
491 | 517 | | |
| 518 | + | |
492 | 519 | | |
493 | 520 | | |
494 | 521 | | |
| |||
513 | 540 | | |
514 | 541 | | |
515 | 542 | | |
516 | | - | |
| 543 | + | |
517 | 544 | | |
518 | 545 | | |
519 | 546 | | |
520 | 547 | | |
521 | | - | |
| 548 | + | |
| 549 | + | |
| 550 | + | |
| 551 | + | |
| 552 | + | |
522 | 553 | | |
523 | 554 | | |
524 | 555 | | |
| |||
0 commit comments