Skip to content

feat: web-based settings UI for per-tool enable/disable/pin - #960

Merged
Patch76 merged 18 commits into
homeassistant-ai:masterfrom
kingpanther13:feat/settings-ui
May 2, 2026
Merged

feat: web-based settings UI for per-tool enable/disable/pin#960
Patch76 merged 18 commits into
homeassistant-ai:masterfrom
kingpanther13:feat/settings-ui

Conversation

@kingpanther13

@kingpanther13 kingpanther13 commented Apr 12, 2026

Copy link
Copy Markdown
Member

What does this PR do?

Adds a self-contained web settings page for configuring per-tool enable/disable/pin states. Served directly by the FastMCP HTTP server, works across all installation methods (add-on, Docker, standalone).

Context: Discussed with @julienld in #857 — he suggested using the built-in FastMCP HTTP server to expose a configuration UI that works across all installation methods rather than relying on HAOS add-on config.yaml schema. This PR implements that approach.

UI features

  • Grouped by category — tools organized by their primary tag (Device Registry, HACS, System, etc.) with collapsible groups
  • Two toggles per tool — separate enabled and pinned switches. Pinned is grayed out when a tool is disabled.
  • Per-group master toggle — enable/disable all non-mandatory tools in a group with one click
  • Search/filter — live search across tool names and titles
  • Badges — mandatory, read-only, destructive labels per tool
  • Feature-gated tool stubs — beta tools (ha_config_set_yaml, the four filesystem tools, ha_install_mcp_tools) appear in the list with a stub note pointing at docs/beta.md whether or not they are currently registered. Their toggles are locked because they require the underlying feature flag.
  • Mandatory tool protectionha_search_entities, ha_get_overview, ha_get_state, ha_report_issue shown with mandatory badge and locked toggles
  • Pinning note — banner explaining that pinning only applies when tool search is enabled
  • Dark theme matching HA aesthetic
  • Open state persistence — expanded groups stay open across re-renders after toggle changes
  • Built-in restart button — when running as an add-on, a "Restart Add-on" button appears in the save notice that calls the Supervisor API (POST /addons/self/restart) to apply changes with one click

Endpoints and where they mount

The settings UI uses FastMCP custom routes, which bypass FastMCP's auth middleware. To match the auth-by-obscurity model the rest of the server uses for HTTP modes, routes are mounted under the MCP secret path (/<secret_path>/...) so HTTP clients need the same secret to reach the UI as they do to reach the MCP endpoint itself. In add-on mode the routes are also mounted at root so HA ingress (which proxies localhost:9583/) can serve the "Open Web UI" button.

Path Add-on Docker / standalone stdio
/ and /settings ✅ (HA ingress) not registered
/<secret_path>/settings ✅ (direct port access) not registered
/api/settings/tools (GET / POST) not registered
/<secret_path>/api/settings/tools (GET / POST) not registered
/api/settings/restart (POST) ✅ (only succeeds with SUPERVISOR_TOKEN) n/a not registered
/api/settings/info (GET) n/a not registered

If neither SUPERVISOR_TOKEN nor a secret path is available, register_settings_routes logs a warning and registers nothing rather than expose the routes publicly.

Persistence and apply model

  • Changes save to /data/tool_config.json (add-on) or ~/.ha-mcp/tool_config.json (standalone) on every toggle. The path is selected by SUPERVISOR_TOKEN presence, not by /data directory existence (matches the rest of the file).
  • Changes require restart to apply — runtime mcp.disable() calls accumulate transforms unreliably; startup-time apply via _apply_settings_visibility() is the clean path. Disabled tools are fully removed from the MCP tool list on next startup.
  • Seeds from DISABLED_TOOLS/PINNED_TOOLS env vars on first run (backwards-compatible with existing env var config).
  • Safety toggles (enable_yaml_config_editing, enable_tool_search, enable_skills, etc.) remain in the dev add-on config page as source of truth. AND-semantics: a beta-gated tool is enabled only when the safety toggle is on and the UI hasn't disabled it.

Beta tool integration (post-#942)

After #942 and #1030 formalized the beta-channel system, beta-tagged tools (ha_config_set_yaml, ha_list_files, ha_read_file, ha_write_file, ha_delete_file, ha_install_mcp_tools) only register when their feature flag is set. The settings UI injects stub entries for the unregistered ones so users can see the tool exists and how to enable it; the stub points at docs/beta.md which covers both the dev-channel toggle and the env-var path.

Add-on integration

  • ingress: true + ingress_port: 9583 + ingress_stream: true on homeassistant-addon-dev/config.yaml — shows the "Open Web UI" button on the add-on info page
  • Dev add-on config page gains disabled_tools and pinned_tools text fields (seed/fallback for users who don't want the web UI) plus tool_search_max_results (range-validated 2-10 in both Pydantic and the addon schema)
  • homeassistant-addon/ (stable channel) config.yaml is intentionally untouched — these are dev-only options for now, matching the existing convention for beta features. homeassistant-addon/start.py is modified because the dev add-on shares it via COPY in homeassistant-addon-dev/Dockerfile; it reads the new options with safe defaults so the stable add-on keeps working unchanged.

Tool search max results wired through

tool_search_max_results (2-10, default 5) now actually flows into CategorizedSearchTransform(max_results=...) instead of the previous hardcoded 5. Pydantic enforces the range; the dev addon schema uses int(2,10)? so the supervisor UI rejects out-of-range values before they reach env vars.

Closes #798

Type of change

  • 🐛 Bug fix
  • ✨ New feature
  • 📚 Documentation
  • 🔧 Maintenance/refactor
  • 💥 Breaking change

Testing

  • I have tested these changes with a LLM agent
  • All automated tests pass (uv run pytest)
  • Code follows style guidelines (uv run ruff check)

Checklist

  • I have updated documentation if needed

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 homeassistant-ai#798

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request enhances the Home Assistant MCP server by adding a self-contained web-based settings interface. This allows users to dynamically manage tool visibility and pinning directly through their browser, improving usability across various deployment environments like Docker and Home Assistant Add-ons. The changes ensure that tool configurations are persisted and applied immediately, while maintaining safety constraints for critical system tools.

Highlights

  • Web-based Settings UI: Introduced a new /settings endpoint that provides a graphical interface for managing MCP tool states (enabled, disabled, or pinned) without requiring a server restart.
  • Persistence and Configuration: Added support for persisting tool visibility settings to a tool_config.json file, with backward compatibility for existing DISABLED_TOOLS and PINNED_TOOLS environment variables.
  • Safety and Mandatory Tools: Implemented safety mechanisms to prevent the disabling of mandatory tools and integrated existing configuration toggles into the new UI flow.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a web-based settings UI at /settings to manage MCP tool visibility (enabling, disabling, and pinning tools) without requiring a server restart. It includes new configuration fields in Settings, logic for persisting these choices to a tool_config.json file, and unit tests for the persistence and visibility logic. Several improvements are needed regarding adherence to repository standards: error responses must use the structured format from errors.py, imports should be consolidated to avoid duplication of constants like DEFAULT_PINNED_TOOLS, and exception handling should be narrowed from broad Exception blocks to specific types like OSError or json.JSONDecodeError. Additionally, a brittle file path used for metadata lookup should be addressed to ensure reliability across different installation environments.

Comment thread src/ha_mcp/settings_ui.py Outdated
Comment thread src/ha_mcp/settings_ui.py Outdated
Comment thread src/ha_mcp/settings_ui.py
Comment thread src/ha_mcp/settings_ui.py Outdated
Comment thread src/ha_mcp/settings_ui.py Outdated
Comment thread src/ha_mcp/settings_ui.py Outdated
Comment thread src/ha_mcp/settings_ui.py Outdated
- 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>

@kingpanther13 kingpanther13 left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ All Gemini comments addressed in 0089c27:

  • Structured error responses: POST endpoint now returns {"success": false, "error": {"code": "...", "message": "..."}} format
  • Import DEFAULT_PINNED_TOOLS: Now imported from ha_mcp.transforms instead of duplicating
  • Specific exceptions: Changed except Exception to except (OSError, json.JSONDecodeError) and except OSError throughout
  • Brittle tools.json path: Added _find_tools_json() that checks multiple candidate paths, with runtime fallback to FastMCP tool manager if file not found

kingpanther13 added a commit to kingpanther13/ha-mcp-fork that referenced this pull request Apr 12, 2026
…only, dev79

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
kingpanther13 and others added 2 commits April 12, 2026 11:18
- 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>
- 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>
kingpanther13 added a commit to kingpanther13/ha-mcp-fork that referenced this pull request Apr 12, 2026
…only, dev80

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
kingpanther13 and others added 2 commits April 12, 2026 12:42
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
kingpanther13 added a commit to kingpanther13/ha-mcp-fork that referenced this pull request Apr 12, 2026
…only, dev81

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- 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>
kingpanther13 added a commit to kingpanther13/ha-mcp-fork that referenced this pull request Apr 13, 2026
…only, dev82

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…uping 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>
kingpanther13 added a commit to kingpanther13/ha-mcp-fork that referenced this pull request Apr 13, 2026
…only, dev83

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
kingpanther13 and others added 4 commits April 12, 2026 21:25
- 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>
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>
- 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>
kingpanther13 added a commit to kingpanther13/ha-mcp-fork that referenced this pull request Apr 13, 2026
…only, dev84

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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>
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>
@kingpanther13

Copy link
Copy Markdown
Member Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a web-based settings UI at /settings to manage MCP tool visibility (enabling, disabling, and pinning tools) across different installation methods. It includes updates to the Home Assistant add-on configuration, documentation, and startup scripts to support these new visibility settings and a tool search result limit. Feedback focuses on adhering to the project's structured error response format, improving type hinting for settings, avoiding brittle private method access in FastMCP, and validating user-provided configuration before persistence.

Comment thread src/ha_mcp/settings_ui.py
Comment thread src/ha_mcp/server.py Outdated
Comment thread src/ha_mcp/settings_ui.py
Comment thread src/ha_mcp/settings_ui.py Outdated
Comment thread src/ha_mcp/settings_ui.py Outdated
- 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>

@kingpanther13 kingpanther13 left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ All Gemini review feedback addressed in 9c6e8de:

  • Structured errors: REST endpoints (/api/settings/tools, /api/settings/restart) now use create_error_response / ErrorCode from errors.py
  • States validation: client-supplied tool states are validated (string keys, allowed state values) before persisting
  • Settings type hint: settings parameter typed as Settings via TYPE_CHECKING import
  • _user_pinned_tools: now initialized in __init__, accessed directly instead of getattr
  • _list_tools() private API: added clearer docstring explaining why the unfiltered list is needed (public list_tools() filters disabled tools, settings UI needs to show them so users can re-enable)

@Patch76

Patch76 commented May 2, 2026

Copy link
Copy Markdown
Member

Hey @kingpanther13 — ran my Claude over this PR with my current review filters as a sanity-check pass; these are my 2 cents on top of what's already in the thread. Solid feature overall, the apply-on-restart model is the right call and the Gemini round was thoroughly addressed. I have two items I'd want resolved before merge plus a handful of smaller ones; happy to be talked out of any of them.

Verdict

Two HIGH items (one auth-surface question, one orphaned setting), one MEDIUM bug (POST handler crashes on non-dict body), plus a small batch of MEDIUM/LOW items and a couple of Boy-Scout opportunities. Nothing architectural.


Blockers

G1 — /api/settings/* routes have no auth in non-ingress deployments (HIGH)

File: src/ha_mcp/settings_ui.py:628-752, mounting via mcp.custom_route(...)

In add-on installs (the primary path) this is fine — HA's ingress proxy authenticates before forwarding. The concern is the other deployment paths:

  • mcp.custom_route(...) registers Starlette routes on _additional_http_routes, stitched into the Starlette app outside the auth middleware. RequireAuthMiddleware (in FastMCP's server/http.py) only wraps the streamable-http path, not custom routes.
  • In ha-mcp-web Docker / standalone HTTP / OAuth-tunnel mode, anyone who can reach the listening port can POST /api/settings/tools and disable any non-mandatory tool, or POST /api/settings/restart and bounce the add-on (this one only succeeds when SUPERVISOR_TOKEN is set, but the surface is still there).

Two reasonable resolutions:

  • Refuse to register the routes when SUPERVISOR_TOKEN is unset (settings UI = add-on-only feature, matches the existing assumption that the restart endpoint already only works in add-on mode).
  • Or gate the routes behind the same auth the MCP endpoint uses (mount under the secret path, or check Authorization).

If you want to keep the routes unconditional, please add a clear note in DOCS.md / .env.example so a Docker user understands what they're standing up.

G2 — tool_search_max_results setting is wired through everything except the actual transform (HIGH)

Files: src/ha_mcp/config.py:122, src/ha_mcp/server.py:484, homeassistant-addon-dev/{config.yaml,translations/en.yaml,DOCS.md}, .env.example:39-40, plus homeassistant-addon/start.py:113-152

Three problems here:

  1. The setting is added everywhere except where it would have effect — _apply_tool_search() in server.py:484 still calls CategorizedSearchTransform(max_results=5, ...) with the literal 5. The new knob is dead code as shipped.
  2. No range validation in Pydantic (config.py:122) despite the docstring claiming Range: 2-10 — the Field has no ge/le. Add-on schema (homeassistant-addon-dev/config.yaml:27) is also bare int?.
  3. The setting is missing entirely from homeassistant-addon/config.yaml (stable channel) — even after this PR is fixed, stable users won't be able to configure it from their add-on UI.

Cleanest options: either pass max_results=self.settings.tool_search_max_results (with a Field(5, ge=2, le=10) and matching int(2,10)? schema in both add-on configs), or pull this setting out of the PR — it's somewhat tangential to the settings UI itself and feels like it sneaked in.


Findings

G3 — POST /api/settings/tools 500s on a non-dict JSON body (MEDIUM)

File: src/ha_mcp/settings_ui.py:666

body = await request.json()
...
raw_states = body.get("states", {})

A valid-JSON-but-non-object payload (null, [], "string", 42) raises AttributeError on body.get → 500 from Starlette. The validation block a few lines below catches a non-dict raw_states, but not a non-dict body. Add if not isinstance(body, dict) immediately after the parse, returning the same VALIDATION_INVALID_PARAMETER shape.

G4 — _get_config_path heuristic conflates "/data exists" with "running as add-on" (MEDIUM)

File: src/ha_mcp/settings_ui.py:85-92

data_dir = Path("/data")
if data_dir.exists():
    return data_dir / "tool_config.json"

The rest of the same module uses os.environ.get("SUPERVISOR_TOKEN") for add-on detection (lines 704, 751). This heuristic disagrees: any host with a /data directory (some Linux Docker setups, some macOS dev configs) writes the config there instead of ~/.ha-mcp/. Worse, if /data exists but is unwritable, save_tool_config silently logs and the user sees Save failed! with no obvious cause. Suggest using SUPERVISOR_TOKEN here too for consistency.

G5 — Tool metadata interpolated into HTML without escaping (MEDIUM)

File: src/ha_mcp/settings_ui.py:518-541

title, t.name, desc, disabledBy, and tag flow straight into innerHTML via JS template literals. Source is server-controlled today (tool docstrings + FEATURE_GATED_TOOLS), so this is not a live exploit — but the moment someone lands a docstring with < or &, the page breaks or executes attacker-supplied markup (e.g., a contributor copy-pasting an example with <entity_id> would render as a broken/ignored tag). Two cheap fixes: a small escapeHtml(s) helper applied to every interpolated value, or render via textContent/createElement instead of innerHTML.

G6 — Mandatory-tools list overlap is non-obvious (LOW)

File: src/ha_mcp/settings_ui.py:35-40 and src/ha_mcp/transforms/categorized_search.py:39-50

MANDATORY_TOOLS overlaps DEFAULT_PINNED_TOOLS by 3 of 4 entries (ha_search_entities, ha_get_overview, ha_report_issue). The fourth, ha_get_state, is mandatory but not pinned by default — intentional (it's reachable via ha_call_read_tool proxy when search is enabled), but worth a one-line comment so the next person editing either constant doesn't merge them or drift them.

G7 — FEATURE_GATED_TOOLS is incomplete (LOW)

File: src/ha_mcp/settings_ui.py:46-82

tools_mcp_component.py registers ha_install_mcp_tools behind HAMCP_ENABLE_CUSTOM_COMPONENT_INTEGRATION. With that flag off, the tool isn't in _list_tools() and isn't a stub here — users won't know it exists. Either add it to FEATURE_GATED_TOOLS, or document the truth-source as "we only stub the headline gated tools".

Also worth a # Keep in sync with tools_filesystem.py / tools_yaml_config.py / tools_mcp_component.py comment — gate strings and tool names live in two files now and a future rename will silently desync.

G8 — Per-restart enable transform layering (LOW / informational)

File: src/ha_mcp/settings_ui.py:237, src/ha_mcp/server.py:150

apply_tool_visibility calls mcp.enable(names=MANDATORY_TOOLS) unconditionally on every server boot. Each call appends a Visibility transform (FastMCP providers/base.py:518-565). In-memory only, so restart resets — harmless in practice, but if the apply ever moves to a hot-reload path (which the PR explicitly avoids and explains), it becomes a slow leak. A one-line "restart-only by design" comment guards the assumption.

G9 — if not enable_yaml_config_editing add is redundant (LOW)

File: src/ha_mcp/settings_ui.py:226-229

if not settings.enable_yaml_config_editing:
    disabled_names.add("ha_config_set_yaml")
else:
    disabled_names.discard("ha_config_set_yaml")

When enable_yaml_config_editing=False, tools_yaml_config.py:38-42 early-returns and the tool is never registered, so mcp.disable(names={"ha_config_set_yaml"}) is a no-op (FastMCP's disable doesn't validate names). The if not branch (line 227-228) is therefore redundant — drop it, or keep it as defense-in-depth and add a comment.

To be clear: the else: discard on line 229 is load-bearing — when the safety toggle is on and the user has saved "disabled" in tool_config.json, the discard strips it back out, force-enabling the tool. Don't drop the whole if/else.

(One question while we're here: is the safety-toggle-overrides-UI-toggle semantics intentional? A user might enable YAML editing in add-on config because they want it available, then disable the tool in the UI to keep it out of tool-search results — and the next restart re-enables it. If that's the intended hierarchy, fine; if not, worth a follow-up issue.)

G10 — _apply_settings_ui registers HTTP routes even in stdio mode (LOW)

File: src/ha_mcp/server.py:315-330, called unconditionally from _initialize_server at :150

In stdio transport, the custom routes are appended to _additional_http_routes but never reachable. Cost is trivial (a few Starlette Route objects), and apply_tool_visibility needs to run regardless of transport. I lean toward leaving it; a one-line comment that "routes are inert in stdio mode" closes the clarity nit if you want it.


Boy-Scout opportunities

G11 — PR body says homeassistant-addon/ is untouched, but the change is required

PR body line 56: "homeassistant-addon/ (stable channel) is intentionally untouched — it's updated automatically by the release pipeline". But homeassistant-addon/start.py is modified (lines 110-152) — and it has to be, because homeassistant-addon-dev/Dockerfile:31 does COPY homeassistant-addon/start.py /, so the dev add-on shares stable's start.py. The modification is functionally required for the new config keys to wire through in the dev channel.

Code is correct as-is; suggest just updating the PR description so future archaeology doesn't get the wrong story. (And per G2 above, the matching homeassistant-addon/config.yaml actually does need touching to expose tool_search_max_results to stable users.)

G12 — .env.example lost its trailing newline

Diff shows \ No newline at end of file. Re-add the final \n.

G13 — GET /api/settings/tools mutates server state in place (NIT)

File: src/ha_mcp/settings_ui.py:646-649

The default-pin loop (for name in DEFAULT_PINNED_TOOLS: if name not in states: states[name] = "pinned") mutates the dict returned by load_tool_config() by reference. Not persisted, not a bug — just slightly off-pattern for a GET handler. Cheap fix: copy the dict before mutating, or build a fresh states dict from scratch.

G14 — POST flow has no lock (NIT)

File: src/ha_mcp/settings_ui.py:684-705

Load → mutate → save without an asyncio.Lock. Practical impact on a single-admin settings page is essentially zero, but two near-simultaneous saves can lose updates. If you're already in the file, an asyncio.Lock around the read-modify-write is two lines.


Positives

  • Apply-on-restart model is the right call — the docstring at settings_ui.py:1-8 plus the in-UI banner make the deferred-effect contract visible.
  • Restart-as-success on dropped connection (settings_ui.py:722-725) — correctly handles the supervisor killing the process mid-request, with a clear comment.
  • States validation added in the Gemini round (settings_ui.py:677-682) is the right shape: parse, validate per-key, drop garbage rather than 400.
  • Mandatory-tool defense in depth — UI locks them client-side, apply_tool_visibility strips them server-side, and the unconditional enable re-arms them. A forged POST can't disable ha_search_entities.
  • local_provider._list_tools() docstring explains the public-API gap clearly. Good response to the Gemini comment.

Test coverage gaps

The [ ] All automated tests pass checkbox is blank in the PR body. The existing unit test (tests/src/unit/test_settings_ui.py, 111 lines) covers persistence and apply_tool_visibility cleanly but the following scenarios have no coverage:

  1. POST /api/settings/tools with bad payloads (G3 would have been caught).
  2. JSON parse errors / non-dict bodies (the validation block at :666-674 is not exercised).
  3. Restart endpoint — missing-SUPERVISOR_TOKEN branch and connection-drop-as-success branch.
  4. Feature-gated stub injection in _get_tool_metadata.
  5. HTML escaping (once G5 is addressed).
  6. is_addon flag in /api/settings/info.
  7. load_tool_configapply_tool_visibility end-to-end.

For ~970 lines of new code with state-mutating HTTP endpoints, current coverage is light. At minimum, adding async tests against the route handlers via Starlette's TestClient covering items 1-3 would catch the G3 class of bugs going forward.


Note on a separable concern

Re your worry about silent setting-loss on tool rename — that's a real issue but separable. A schema_version field in tool_config.json plus a migration map ({"old_name": "new_name"}) on the next major rename would handle it. Worth a follow-up issue, no need to block this PR.

Findings citable as G1-G14 for the implementation summary. Let me know which you'd push back on — happy to pair on the auth question (G1) in particular, since the right answer depends on how strongly you want to commit to "settings UI is add-on-only".

Conflicts:
- homeassistant-addon-dev/{config.yaml,translations/en.yaml,DOCS.md,start.py}:
  combined homeassistant-ai#1030 beta flags (filesystem/yaml/custom-component) with this
  PR's new dev-only options (tool_search_max_results, disabled_tools,
  pinned_tools). New options stay dev-only per homeassistant-ai#942 channel convention.
- src/ha_mcp/server.py: combined homeassistant-ai#955's _apply_search_keyword_enrichment
  refactor with this PR's settings-visibility apply step. Order:
  tools -> enhanced -> skills -> _apply_settings_visibility ->
  _apply_search_keyword_enrichment -> _apply_tool_search.
- homeassistant-addon/start.py: kept homeassistant-ai#806 migrate_skills_as_tools_default
  + relocated supervisor-token validation; added new env var exports.

Patch76 review fixes:
- G1: Mount settings UI under MCP secret_path so Docker/standalone clients
  share the same auth-by-obscurity as the MCP endpoint. Add-on continues
  to mount at root for HA ingress proxy. Routes don't register at all
  when neither path is available (stdio mode, or HTTP without secret).
  Moved register_settings_routes out of _initialize_server into the HTTP
  entry points (_run_http_server, _run_oauth_server, addon start.py).
- G2: Wire tool_search_max_results through CategorizedSearchTransform;
  enforce 2-10 range in Pydantic Field and addon-dev schema int(2,10)?.
- G3: 400 instead of 500 when POST body is JSON but not an object.
- G4: Use SUPERVISOR_TOKEN, not /data existence, to detect add-on mode
  in _get_config_path. Matches the rest of the module.
- G5: HTML-escape interpolated tool metadata in the settings JS.
- G6: Comment explaining MANDATORY_TOOLS vs DEFAULT_PINNED_TOOLS overlap.
- G7: Add ha_install_mcp_tools stub to FEATURE_GATED_TOOLS; rewrite stub
  copy to point at docs/beta.md (covers both stable and dev paths post-homeassistant-ai#942).
- G9: Keep enable_yaml_config_editing guard with defense-in-depth comment;
  drop the discard so AND semantics apply (UI off OR toggle off -> tool off).
- G12: Restore .env.example trailing newline.

Tests cover non-dict body, garbage state values, route mounting under
secret_path, _get_config_path env-driven path, FEATURE_GATED_TOOLS
beta-system alignment, and the G9 AND-semantics regression.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@kingpanther13
kingpanther13 disabled auto-merge May 2, 2026 20:07
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>
@kingpanther13

Copy link
Copy Markdown
Member Author

@Patch76 — addressed in 72cd3ca (merge + bulk fixes) and 3477a39 (mypy followup). Quick rundown:

What was implemented

# Status Notes
G1 ✅ done — option (b) variant Settings routes now mount under the MCP secret path so Docker / standalone clients share the same auth-by-obscurity as the MCP endpoint. Add-on still mounts at root for HA ingress. If neither SUPERVISOR_TOKEN nor a secret path is available, register_settings_routes registers nothing and logs a warning rather than expose them publicly. Moving registration out of _initialize_server into the HTTP entry points (_run_http_server, _run_oauth_server, addon start.py) was a side benefit — closes G10 too.
G2 part 1 tool_search_max_results now actually flows into CategorizedSearchTransform(max_results=...).
G2 part 2 Field(5, ge=2, le=10, ...) in Pydantic, int(2,10)? in dev addon schema. Out-of-range values rejected at the supervisor UI before they reach env vars.
G2 part 3 📝 declined Stable homeassistant-addon/config.yaml is intentionally untouched — release pipeline syncs at the next biweekly cut. Same convention as enable_yaml_config_editing / enable_filesystem_tools / enable_custom_component_integration post-#942.
G3 if not isinstance(body, dict) guard added before body.get. Returns the same 400 / VALIDATION_INVALID_PARAMETER shape.
G4 _get_config_path now keys off SUPERVISOR_TOKEN, matches the rest of the module. Factored into a _is_addon() helper so the GET /api/settings/info endpoint and _get_config_path can't drift.
G5 escapeHtml(s) helper applied to every ${...} interpolation including data-attribute values.
G6 Comment added explaining the MANDATORY_TOOLS / DEFAULT_PINNED_TOOLS overlap (and the deliberate divergence on ha_get_state).
G7 ✅ + extra ha_install_mcp_tools added to FEATURE_GATED_TOOLS. Took the chance to rewrite the stub copy: instead of "Requires <flag> in add-on config" (which is misleading on stable where the flag isn't exposed at all post-#942), it now points at docs/beta.md — covers both the dev-channel toggle path and the env-var path uniformly.
G8 📝 acknowledged, no code change The unconditional mcp.enable(names=MANDATORY_TOOLS) is restart-only by design, your read is correct. Adding a comment felt like noise; happy to add one if you want.
G9 part 1 (redundancy) ✅ kept the if not with a defense-in-depth comment Agree the mcp.disable() call is a no-op today thanks to the early-return in tools_yaml_config.py. Kept the guard because if the registration site ever moves the visibility apply still does the right thing.
G9 part 2 (semantics) ✅ AND semantics Dropped the else: discard("ha_config_set_yaml") line. UI off or safety toggle off → tool off. New regression test in TestApplyToolVisibility::test_yaml_editing_on_but_ui_disabled_keeps_tool_disabled.
G10 Resolved as a side effect of the G1 entry-point relocation — routes only register in HTTP modes now.
G11 PR description rewritten to drop the "stable channel intentionally untouched" claim that contradicted the homeassistant-addon/start.py edit. New body explains the dev/stable split honestly.
G12 Trailing newline restored.
G13 📝 declined load_tool_config() reads from disk on every call so the dict isn't shared state. Worth revisiting if that ever caches; not today.
G14 📝 declined Single-admin contention is genuinely zero. Happy to add an asyncio.Lock if it bothers you, but it's a save-on-toggle UI so even two browser windows would just produce a last-write-wins which is what users expect.

Tests added in tests/src/unit/test_settings_ui.py

  • TestConfigPath_get_config_path honors SUPERVISOR_TOKEN, falls back to ~/.ha-mcp/ otherwise.
  • TestFeatureGatedToolsha_install_mcp_tools is registered as a stub; filesystem tools point at the dev addon option name (alignment check with the beta tag system).
  • TestRouteRegistration — root mount only happens in addon mode, secret-path mount always happens (when provided), no mounts at all if neither.
  • TestSaveToolsValidation — POST handler rejects non-dict body (null, [], 42), rejects non-dict states, drops garbage values silently rather than 400ing the whole batch.
  • AND-semantics regression for ha_config_set_yaml (G9.2).

Merge context

Picked up the #1030 beta flags, #806 migrate_skills_as_tools_default migration, and #955's _apply_search_keyword_enrichment extraction along the way. New PR options stayed dev-only per #942 channel convention. The FEATURE_GATED_TOOLS stub now treats ha_config_set_yaml / filesystem tools / ha_install_mcp_tools consistently as beta-channel tools rather than referencing addon-config keys that don't exist on stable.

Re tool-rename setting-loss — agreed it's separable, will track as a follow-up issue.

@Patch76 Patch76 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review on top of 72cd3ca + 3477a39 — all 14 findings addressed cleanly.

Spot-checked load-bearing fixes:

  • G1 (auth): register_settings_routes refuses to mount when neither SUPERVISOR_TOKEN nor secret_path is available; secret-path mount in non-add-on mode matches the MCP-endpoint auth-by-obscurity model. Relocating registration out of _initialize_server into the HTTP entry points closes G10 as a side effect — clean.
  • G2: max_results wired at server.py:543, Field(5, ge=2, le=10) plus int(2,10)? in the dev addon schema. Stable-channel deferral to the release pipeline is consistent with the post-#942 convention.
  • G3 / G5 / G9.2: isinstance(body, dict) guard, escapeHtml on every interpolation, and AND-semantics regression test all in place.

Declined items (G2.3 / G8 / G13 / G14): reasoning holds, nothing I'd push back on.

One non-blocking note: the restart endpoint's missing-SUPERVISOR_TOKEN branch (settings_ui.py:780) and the drop-connection-as-success branch (settings_ui.py:798) still don't have tests — the rest of my coverage list is now covered. Two short cases would close the loop; happy to follow up post-merge instead.

Approving.

@Patch76
Patch76 merged commit 9783f34 into homeassistant-ai:master May 2, 2026
22 of 23 checks passed
@github-actions

github-actions Bot commented May 2, 2026

Copy link
Copy Markdown
Contributor

🧪 Your changes are now in the dev channel!

Your PR has been merged to master and is available for testing in the dev channel.

Test your changes before the next stable release (biweekly Wednesday):
📖 Dev Channel Documentation

Quick start

# Run dev version
uvx ha-mcp-dev

# Check version
uvx ha-mcp-dev --version

Docker:

docker pull ghcr.io/homeassistant-ai/ha-mcp:dev
docker run --rm -i \
  -e HOMEASSISTANT_URL=http://your-ha:8123 \
  -e HOMEASSISTANT_TOKEN=your_token \
  ghcr.io/homeassistant-ai/ha-mcp:dev

Found an issue? Please open a new bug report and mention this PR for context.

@kingpanther13

Copy link
Copy Markdown
Member Author

Hey I actually had auto-merge disabled on purpose for this one, I wanted to verify how it looked in my browser/the HA UI again before merging it, were you able to look at that yourself? If not no worries I will check on the dev server when I get a chance

@kingpanther13

Copy link
Copy Markdown
Member Author

That should have been posted in our private maintainer chat, but thanks for the heads up. Surprised it made it this far, I'll find the token it's using and kill it.

Patch76 added a commit to Patch76/ha-mcp that referenced this pull request May 5, 2026
Adds unit-test coverage for the two previously-untested branches in
settings_ui.py:_restart_addon:

- Missing SUPERVISOR_TOKEN (settings_ui.py:780-789) — non-addon installs
  hit this when the user clicks Restart against a Docker/pyinstaller
  setup; the structured 400 must surface rather than ever reaching the
  Supervisor URL.
- Connection-drop-as-success (settings_ui.py:798-801) — the Supervisor
  kills our process mid-request during a restart, so a ReadError /
  RemoteProtocolError / ConnectError from the POST is the documented
  success signal.

Mirrors the _capture_handler pattern from TestSaveToolsValidation. The
fixture-level server.settings.verify_ssl = True is required by this
PR's post-G1 access path (httpx accepts only bool/SSLContext for verify=).

Boy-Scout fix while already touching _restart_addon for the
verify_ssl-propagation refactor — closes the test-coverage gap I'd
flagged in homeassistant-ai#960's approve-body but never followed up on at the time.
kingpanther13 added a commit to kingpanther13/ha-mcp-fork that referenced this pull request May 6, 2026
…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
kingpanther13 added a commit that referenced this pull request May 6, 2026
…ix (#1136)

* feat: drop ENABLE_SKILLS / ENABLE_SKILLS_AS_TOOLS toggles, rename to ha_list_resources / ha_read_resource

The two skills toggles were redundant once #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 #1133

* fix: address PR review — harden rename, fix lingering read_resource refs, add coverage

Review feedback from the pr-review-toolkit agents (code-reviewer,
pr-test-analyzer, silent-failure-hunter, type-design-analyzer,
comment-analyzer):

- Update LLM-facing strings still mentioning the unprefixed `read_resource`
  in `_register_skill_guidance_tools` (tool description + handler
  `how_to_use`) and the `_register_skill_guidance_tools` docstring; also
  update the `tests/uat/stories/catalog/s13_dashboard_update_existing.yaml`
  expected-tools list.
- Replace the `result[-2:]` slicing in `HaResourcesAsTools.list_tools`
  with a name-based scan over the full upstream sequence and a
  `_RENAMES` class mapping. Log a warning if the matched count is not
  exactly two so a future fastmcp regression that drops or reorders the
  appended tools surfaces loudly at boot instead of silently leaking
  the unprefixed names.
- Use `HaResourcesAsTools.LIST_TOOL_NAME` / `READ_TOOL_NAME` constants
  for the pinned-tools list and the search-tool description text in
  `server.py` so the rename has a single source of truth.
- Add an upgrade-fragility note to the `HaResourcesAsTools` docstring
  flagging the dependency on fastmcp's `_make_*_tool` private factories.
- Tighten the `best_practice_checker` module docstring: the `skill_prefix`
  kwarg note now says "any URL prefix (e.g., a GitHub mirror)" instead
  of implying a canonical alternative still exists in the module.
- Document the cross-module `TRANSFORM_GENERATED_TOOLS` ↔
  `HaResourcesAsTools` constant invariant and add
  `test_transform_generated_tool_names_match_class_constants`.
- New unit test file `tests/src/unit/test_ha_resources_as_tools.py`:
  rename happy-path for `list_tools`/`get_tool`, fall-through for
  unprefixed and unrelated names, and a drift-warning test that
  monkey-patches the base class to drop one of the appended tools.
- New e2e tests `test_ha_list_resources_invocation` and
  `test_ha_read_resource_invocation` in
  `tests/src/e2e/tools/test_skills_resources.py`: actually invoke the
  renamed tools via `mcp_client.call_tool(...)` to confirm the rename
  doesn't break dispatch routing (catalog presence is necessary but not
  sufficient).

Note on Gemini's two inline comments: both are based on a non-existent
fastmcp API (`Transform.call_tool` / `CallToolNext`) — fastmcp's
transform protocol exposes `list_tools` and `get_tool`, and tool
dispatch in `FastMCP.call_tool` runs through `get_tool` (which this
subclass overrides) before invoking `tool.run`. The new e2e
invocation tests verify this end-to-end.

* fix: address remaining review items — marker cleanup, skill summary log, ToolStub typing

Three follow-ups from the pr-review-toolkit feedback that were initially
deferred but on reconsideration belong in this PR:

- ``cleanup_stale_migration_marker`` in ``homeassistant-addon/start.py``
  removes ``/data/.skills_as_tools_default_migration_v1`` on next boot.
  The marker was created by the previous version's
  ``migrate_skills_as_tools_default`` (deleted in this PR's first
  commit); leaving it on disk forever is permanent ``/data`` litter
  for every existing add-on install. ``unlink(missing_ok=True)`` plus
  best-effort error handling.
- ``HomeAssistantSmartMCPServer._register_skills`` now tracks
  per-phase status (provider / transform / guidance_tools count) and
  emits one summary log line at the end via
  ``_log_skill_registration_summary`` —  ``info`` when both provider
  and transform succeeded, ``warning`` otherwise. Without the toggle,
  every install runs this code path on every boot, so a single line
  operators can grep for is more useful than reconstructing state
  from scattered ``logger.exception`` calls. Per-phase exception logs
  remain for stack traces.
- ``ToolStub`` ``TypedDict`` defined in ``settings_ui.py`` and applied
  to both ``TRANSFORM_GENERATED_TOOLS`` and ``FEATURE_GATED_TOOLS``,
  with ``NotRequired`` keys for ``disabled_by``,
  ``readOnlyHint``, ``destructiveHint``. Annotation values converted
  from ``"true"`` strings to actual ``bool``. ``_render_stub`` helper
  collapses the previously-duplicated stub-build loops into one
  function so a misspelled key now fails type-checking instead of
  silently producing an entry with the wrong shape. Behavioral output
  is unchanged.

---------

Co-authored-by: kingpanther13 <kingpanther13@users.noreply.github.com>
Patch76 added a commit to Patch76/ha-mcp that referenced this pull request May 7, 2026
Merge brings in homeassistant-ai#1126, homeassistant-ai#1135, homeassistant-ai#1136, homeassistant-ai#1138 and the dev-addon publish
chain since the branch's previous head `147ad5f`. Conflict in
`tests/src/unit/test_settings_ui.py` resolved by keeping both adjacent
additions: master's `test_returns_500_when_save_fails` (read-only-fs
500-surfacing test from homeassistant-ai#1138) inside `TestSaveToolsValidation`, plus
this PR's new `TestRestartAddon` class right after.

KP13 round-1 review asks (CHANGES_REQUESTED 2026-05-06 20:38 UTC) all
addressed:

1. **Narrow connection-drop catch** — the `except` tuple in
   `_restart_addon` (in `settings_ui.py`) is now
   `(httpx.ReadError, httpx.RemoteProtocolError)`. `httpx.ConnectError`
   is no longer treated as a successful restart; it falls through to
   the `httpx.HTTPError` handler returning 502 + `CONNECTION_FAILED`.
   Inline comment documents the deliberate exclusion (DNS /
   TCP-refused / supervisor-socket-misconfigured all mean Supervisor
   was unreachable, not that a restart was initiated).

2. **Parametrize connection-drop test** + separate `ConnectError` →
   502 case. `test_treats_connection_drop_as_success` now parametrizes
   over `(httpx.ReadError, httpx.RemoteProtocolError)`. New
   `test_connect_error_returns_502` locks the contract that a
   connection-failure-before-handshake surfaces as 502.

3. **Boy-Scout: pin remaining `_restart_addon` branches.** Two new
   tests: `test_generic_http_error_returns_502` (uses
   `httpx.PoolTimeout` to exercise the `httpx.HTTPError` fall-through)
   and `test_supervisor_4xx_returns_502` (Supervisor returns 401 →
   handler maps to 502).

4. **Symbol-based test docstrings** — class-docstring + method
   docstrings now reference "the `if not token:` guard", "the catch
   on `(ReadError, RemoteProtocolError)`", "the `httpx.HTTPError`
   handler", "the `status_code >= 400` branch" instead of line numbers
   that shift with every kwarg-split / refactor.

5. **Top-level `import httpx`** in `tests/src/unit/test_settings_ui.py`
   replaces the inline `__import__("httpx").ReadError(...)` workaround.

6. **Trim "post-G1 state"** from the `verify_ssl = True` fixture
   comment. Kept the substantive part ("must resolve to a real bool,
   not a MagicMock, because httpx accepts only bool/SSLContext for
   `verify=`") that pays off in 6 months.

7. **Move homeassistant-ai#960 cross-reference** out of the `TestRestartAddon` class
   docstring. Closed-PR review history rots fast in source; the PR
   body is the right place for it.

Local: 1762 unit tests pass, ruff lint + format clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Patch76 added a commit that referenced this pull request May 7, 2026
#1128)

* refactor: pass verify_ssl to remaining direct-Supervisor httpx callers

Closes #1127. Mirrors the verify=self.verify_ssl propagation pattern
established in #1126 (rest_client.py:_get_addon_logs_via_supervisor) at
the two other direct-Supervisor httpx call sites:

- tools_bug_report.py:_fetch_addon_logs uses get_global_settings().verify_ssl
  (module-level helper, no self/closure context).
- settings_ui.py:_restart_addon uses server.client.verify_ssl (closure has
  access to server: HomeAssistantSmartMCPServer).

Both paths effectively propagate Settings.verify_ssl via the access route
appropriate to each call site's scope. The http://supervisor URL is plain
HTTP and TLS-irrelevant in practice — the parameter keeps all three
constructor sites consistent with the established HomeAssistantClient
pattern.

* refactor: read verify_ssl from server.settings instead of server.client

Per Gemini review on PR #1128: server.client is a lazy @Property
(server.py) — accessing it for a single config bool would instantiate
the full HomeAssistantClient (httpx pool, settings re-read, log line)
on first access. server.settings is eager-initialized in the
HomeAssistantSmartMCPServer constructor and is the canonical source of
truth for verify_ssl.

Additional benefit: in OAuth deployment mode (__main__.py:868),
HomeAssistantSmartMCPServer is constructed with an OAuthProxyClient
whose __getattr__ proxies to a per-request OAuth client requiring an
authenticated request context. _restart_addon is a plain admin POST
without that context, so server.client.verify_ssl could have surfaced
as an auth error in OAuth mode. server.settings.verify_ssl sidesteps it
without depending on OAuthProxyClient's attribute-forwarding semantics.

* test: pin _restart_addon untested branches per Boy-Scout

Adds unit-test coverage for the two previously-untested branches in
settings_ui.py:_restart_addon:

- Missing SUPERVISOR_TOKEN (settings_ui.py:780-789) — non-addon installs
  hit this when the user clicks Restart against a Docker/pyinstaller
  setup; the structured 400 must surface rather than ever reaching the
  Supervisor URL.
- Connection-drop-as-success (settings_ui.py:798-801) — the Supervisor
  kills our process mid-request during a restart, so a ReadError /
  RemoteProtocolError / ConnectError from the POST is the documented
  success signal.

Mirrors the _capture_handler pattern from TestSaveToolsValidation. The
fixture-level server.settings.verify_ssl = True is required by this
PR's post-G1 access path (httpx accepts only bool/SSLContext for verify=).

Boy-Scout fix while already touching _restart_addon for the
verify_ssl-propagation refactor — closes the test-coverage gap I'd
flagged in #960's approve-body but never followed up on at the time.

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
eleboucher pushed a commit to eleboucher/homelab that referenced this pull request May 13, 2026
…→ 7.5.0) (#455)

This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [ghcr.io/homeassistant-ai/ha-mcp](https://github.com/homeassistant-ai/ha-mcp) | minor | `7.4.0` → `7.5.0` |

---

> ⚠️ **Warning**
>
> Some dependencies could not be looked up. Check the [Dependency Dashboard](issues/3) for more information.

---

### Release Notes

<details>
<summary>homeassistant-ai/ha-mcp (ghcr.io/homeassistant-ai/ha-mcp)</summary>

### [`v7.5.0`](https://github.com/homeassistant-ai/ha-mcp/blob/HEAD/CHANGELOG.md#v750-2026-05-13)

[Compare Source](homeassistant-ai/ha-mcp@v7.4.0...v7.5.0)

##### Added

- Add ENABLE\_LITE\_DOCSTRINGS beta toggle
  ([#&#8203;1259](homeassistant-ai/ha-mcp#1259))
- Add ha\_call\_event tool for publishing events on the HA event bus ([#&#8203;996](homeassistant-ai/ha-mcp#996))
  ([#&#8203;1239](homeassistant-ai/ha-mcp#1239))
- Pinpoint backslash-escape mistake in python\_sandbox errors
  ([#&#8203;1204](homeassistant-ai/ha-mcp#1204))
- Reject empty-trigger automations targeting scene.create
  ([#&#8203;1187](homeassistant-ai/ha-mcp#1187))
- Add scene config tools — ha\_config\_get/set/remove\_scene
  ([#&#8203;1168](homeassistant-ai/ha-mcp#1168))
- **addon**: Optional OAuth 2.1 mode for webhook proxy (beta)
  ([#&#8203;1184](homeassistant-ai/ha-mcp#1184))
- Surface helper schema inline in ha\_config\_set\_helper validation errors ([#&#8203;1149](homeassistant-ai/ha-mcp#1149))
  ([#&#8203;1179](homeassistant-ai/ha-mcp#1179))
- Emit progress via FastMCP Context in long-running tools
  ([#&#8203;1124](homeassistant-ai/ha-mcp#1124))
- Broaden python\_transform AST allowlist + improve error UX
  ([#&#8203;1163](homeassistant-ai/ha-mcp#1163))
- Add ha\_manage\_custom\_tool — sandboxed code execution escape hatch
  ([#&#8203;854](homeassistant-ai/ha-mcp#854))
- Always-on skills; rename list/read resource tools with ha\_ prefix
  ([#&#8203;1136](homeassistant-ai/ha-mcp#1136))
- Expose device\_class + options on ha\_set\_entity / ha\_get\_entity (Show As)
  ([#&#8203;1135](homeassistant-ai/ha-mcp#1135))
- **site**: Inline wizard data into setup.astro, migrate setup nuggets, drop content collections
  ([#&#8203;1120](homeassistant-ai/ha-mcp#1120))
- Add "Advanced debug logging" toggle for kill-signal diagnostics
  ([#&#8203;1117](homeassistant-ai/ha-mcp#1117))
- **yaml**: Scoped lovelace.dashboards.\<url\_path> support (issue [#&#8203;1034](homeassistant-ai/ha-mcp#1034))
  ([#&#8203;1103](homeassistant-ai/ha-mcp#1103))
- Add HA\_VERIFY\_SSL toggle to disable TLS verification
  ([#&#8203;1104](homeassistant-ai/ha-mcp#1104))
- Per-top-level-key config\_hash for ha\_manage\_energy\_prefs ([#&#8203;1049](homeassistant-ai/ha-mcp#1049))
  ([#&#8203;1098](homeassistant-ai/ha-mcp#1098))
- **site**: Add gemini-cli setup notes + compose hardening to wizard ([#&#8203;1027](homeassistant-ai/ha-mcp#1027))
  ([#&#8203;1087](homeassistant-ai/ha-mcp#1087))
- Add convenience modes to ha\_manage\_energy\_prefs ([#&#8203;1050](homeassistant-ai/ha-mcp#1050))
  ([#&#8203;1073](homeassistant-ai/ha-mcp#1073))
- Surface integration log levels in ha\_get\_logs/integration/addon ([#&#8203;956](homeassistant-ai/ha-mcp#956))
  ([#&#8203;1003](homeassistant-ai/ha-mcp#1003))
- Expose allowlist\_external\_dirs in ha\_get\_overview full system\_info
  ([#&#8203;1053](homeassistant-ai/ha-mcp#1053))
- **dashboards**: Unify identifier handling in ha\_config\_\*\_dashboard tools ([#&#8203;981](homeassistant-ai/ha-mcp#981))
  ([#&#8203;1075](homeassistant-ai/ha-mcp#1075))
- Include addon container logs in bug reports
  ([#&#8203;934](homeassistant-ai/ha-mcp#934))
- Add WebSocket response-shaping controls to ha\_manage\_addon
  ([#&#8203;1009](homeassistant-ai/ha-mcp#1009))
- Web-based settings UI for per-tool enable/disable/pin
  ([#&#8203;960](homeassistant-ai/ha-mcp#960))
- **site**: Add OpenCode support to setup wizard
  ([#&#8203;1080](homeassistant-ai/ha-mcp#1080))

##### Changed

- Clarify standard-mode HTTP deployment guidance
  ([#&#8203;1185](homeassistant-ai/ha-mcp#1185))
- Add Cloudflared add-on hostname alternative for tunnel service
  ([#&#8203;1183](homeassistant-ai/ha-mcp#1183))
- Align tool naming convention between AGENTS.md and styleguide ([#&#8203;943](homeassistant-ai/ha-mcp#943))
  ([#&#8203;1174](homeassistant-ai/ha-mcp#1174))
- **addon**: Note tool-list ([#&#8203;985](homeassistant-ai/ha-mcp#985 divergence; fix [#&#8203;1139](https://github.com/homeassistant-ai/ha-mcp/issues/1139)/[#&#8203;1162](https://github.com/homeassistant-ai/ha-mcp/issues/1162) test conflict
  ([#&#8203;1172](homeassistant-ai/ha-mcp#1172))
- Add brew install option for mcp-proxy on macOS
  ([#&#8203;1171](homeassistant-ai/ha-mcp#1171))
- Update contributors list \[contributors-updated]
  ([`aba01a1`](homeassistant-ai/ha-mcp@aba01a1))
- Warn against enable\_tool\_search on Claude Sonnet/Opus ([#&#8203;1088](homeassistant-ai/ha-mcp#1088))
  ([#&#8203;1140](homeassistant-ai/ha-mcp#1140))
- Address [#&#8203;1094](homeassistant-ai/ha-mcp#1094) review nits on OpenCode mirror comments
  ([#&#8203;1105](homeassistant-ai/ha-mcp#1105))

##### Fixed

- **integrations**: Surface ConfigEntry.options via OptionsFlow probe
  ([#&#8203;1245](homeassistant-ai/ha-mcp#1245))
- **backup**: Discover local agent at call time instead of hardcoding hassio.local
  ([#&#8203;1244](homeassistant-ai/ha-mcp#1244))
- Triage all 10 ha\_search\_entities behaviors from [#&#8203;1170](homeassistant-ai/ha-mcp#1170)
  ([#&#8203;1195](homeassistant-ai/ha-mcp#1195))
- Replace cron with systemd for demo server (prevents process leak)
  ([#&#8203;1110](homeassistant-ai/ha-mcp#1110))
- Improve ha\_manage\_addon discoverability (BM25 keywords + slug examples)
  ([#&#8203;1200](homeassistant-ai/ha-mcp#1200))
- Route Supervisor 401s to structured tool errors + add E2E coverage ([#&#8203;1129](homeassistant-ai/ha-mcp#1129))
  ([#&#8203;1192](homeassistant-ai/ha-mcp#1192))
- Harden \_validate\_category\_id gate to cover dict-promoted category
  ([#&#8203;1190](homeassistant-ai/ha-mcp#1190))
- Broaden template anti-pattern detection + skill discoverability ([#&#8203;1011](homeassistant-ai/ha-mcp#1011))
  ([#&#8203;1181](homeassistant-ai/ha-mcp#1181))
- Return newest automation traces, add offset+order pagination ([#&#8203;1177](homeassistant-ai/ha-mcp#1177))
  ([#&#8203;1178](homeassistant-ai/ha-mcp#1178))
- **security**: Write YAML backups outside www/ (GHSA-g39v-cvjh-8fpf)
  ([#&#8203;1180](homeassistant-ai/ha-mcp#1180))
- **search**: Apply domain\_filter when area\_filter is set ([#&#8203;1162](homeassistant-ai/ha-mcp#1162))
  ([#&#8203;1165](homeassistant-ai/ha-mcp#1165))
- **resources**: Reject HA-config YAML in dashboard resource content
  ([#&#8203;1160](homeassistant-ai/ha-mcp#1160))
- Close 19 bugs in ha\_config\_set\_helper (issue [#&#8203;1150](homeassistant-ai/ha-mcp#1150))
  ([#&#8203;1151](homeassistant-ai/ha-mcp#1151))
- Route addon log fetches directly to supervisor on addon installs
  ([#&#8203;1126](homeassistant-ai/ha-mcp#1126))
- Survive read-only filesystems at startup
  ([#&#8203;1138](homeassistant-ai/ha-mcp#1138))
- **helpers**: Clarify name-required-on-create for ha\_config\_set\_helper
  ([#&#8203;1143](homeassistant-ai/ha-mcp#1143))
- Resolve disabled entities via entity\_registry in helper deletion
  ([#&#8203;1119](homeassistant-ai/ha-mcp#1119))
- Allow unary operators in python\_transform sandbox
  ([#&#8203;1118](homeassistant-ai/ha-mcp#1118))
- **site**: Add github-copilot-agents wizard branch + delete unreferenced data/clients.ts
  ([#&#8203;1108](homeassistant-ai/ha-mcp#1108))
- **addons**: Route addon API calls through HA Core ingress proxy
  ([#&#8203;1069](homeassistant-ai/ha-mcp#1069))
- **webhook-proxy**: Surface webhook registration failures instead of silently loading
  ([#&#8203;1101](homeassistant-ai/ha-mcp#1101))
- **site**: Resolve client display-order collisions and anchor OpenCode shape
  ([#&#8203;1094](homeassistant-ai/ha-mcp#1094))

##### Performance Improvements

- Dedupe lovelace/dashboards/list in ha\_config\_set\_dashboard ([#&#8203;1085](homeassistant-ai/ha-mcp#1085))
  ([#&#8203;1191](homeassistant-ai/ha-mcp#1191))

##### Refactoring

- Drop obsolete ha\_mcp\_tools defensive ruamel.yaml imports ([post-#&#8203;1268](https://github.com/post-/ha-mcp/issues/1268))
  ([#&#8203;1269](homeassistant-ai/ha-mcp#1269))
- Extract shared Supervisor httpx client helper ([#&#8203;1130](homeassistant-ai/ha-mcp#1130))
  ([#&#8203;1203](homeassistant-ai/ha-mcp#1203))
- Surface client identity, AI model, config toggles, and prompt context in ha\_report\_issue
  ([#&#8203;1189](homeassistant-ai/ha-mcp#1189))
- Harden Context injection with safe-emit + branch coverage
  ([#&#8203;1173](homeassistant-ai/ha-mcp#1173))
- Consolidate area/floor set+remove tools (revisit of [#&#8203;813](homeassistant-ai/ha-mcp#813))
  ([#&#8203;1139](homeassistant-ai/ha-mcp#1139))
- Pass verify\_ssl to remaining direct-Supervisor httpx callers
  ([#&#8203;1128](homeassistant-ai/ha-mcp#1128))
- Validate only new entries on convenience-mode writes ([#&#8203;1086](homeassistant-ai/ha-mcp#1086))
  ([#&#8203;1100](homeassistant-ai/ha-mcp#1100))

***

<details>
<summary>Internal Changes</summary>

##### Fixed

- **ci**: Align pr.yml E2E with --dist loadscope ([#&#8203;1206](homeassistant-ai/ha-mcp#1206))
  ([#&#8203;1247](homeassistant-ai/ha-mcp#1247))
- **ci**: Switch Renovate to a GitHub App token to allow workflow-file pushes
  ([#&#8203;1229](homeassistant-ai/ha-mcp#1229))
- **ci**: Break gemini-triage retrigger loop and bump turn budget
  ([#&#8203;1131](homeassistant-ai/ha-mcp#1131))
- **ci**: Harden gemini-triage so failures stop spamming user issues
  ([#&#8203;1122](homeassistant-ai/ha-mcp#1122))
- **ci**: Unbreak hotfix-release semantic-release run
  ([#&#8203;1091](homeassistant-ai/ha-mcp#1091))

##### Chores

- **addon**: Publish dev addon version 7.4.1.dev299 \[skip ci]
  ([`397aa6d`](homeassistant-ai/ha-mcp@397aa6d))
- **addon**: Publish dev addon version 7.4.1.dev298 \[skip ci]
  ([`942b7e0`](homeassistant-ai/ha-mcp@942b7e0))
- Sync tool docs after merge \[skip ci]
  ([`6823c47`](homeassistant-ai/ha-mcp@6823c47))
- **addon**: Publish dev addon version 7.4.1.dev297 \[skip ci]
  ([`6eac062`](homeassistant-ai/ha-mcp@6eac062))
- **addon**: Publish dev addon version 7.4.1.dev296 \[skip ci]
  ([`b2afe93`](homeassistant-ai/ha-mcp@b2afe93))
- **addon**: Publish dev addon version 7.4.1.dev295 \[skip ci]
  ([`4f4c4f3`](homeassistant-ai/ha-mcp@4f4c4f3))
- **deps**: Update ghcr.io/home-assistant/home-assistant docker tag to v2026.5.1
  ([#&#8203;1236](homeassistant-ai/ha-mcp#1236))
- **addon**: Publish dev addon version 7.4.1.dev294 \[skip ci]
  ([`fd24991`](homeassistant-ai/ha-mcp@fd24991))
- **deps**: Update ghcr.io/astral-sh/uv docker tag to v0.11.13
  ([#&#8203;1233](homeassistant-ai/ha-mcp#1233))
- **addon**: Publish dev addon version 7.4.1.dev293 \[skip ci]
  ([`fcc6496`](homeassistant-ai/ha-mcp@fcc6496))
- **addon**: Publish dev addon version 7.4.1.dev292 \[skip ci]
  ([`2961650`](homeassistant-ai/ha-mcp@2961650))
- **addon**: Publish dev addon version 7.4.1.dev291 \[skip ci]
  ([`5703112`](homeassistant-ai/ha-mcp@5703112))
- **addon**: Publish dev addon version 7.4.1.dev290 \[skip ci]
  ([`19b2f65`](homeassistant-ai/ha-mcp@19b2f65))
- **addon**: Publish dev addon version 7.4.1.dev289 \[skip ci]
  ([`e5a1365`](homeassistant-ai/ha-mcp@e5a1365))
- Sync tool docs after merge \[skip ci]
  ([`d2ff93b`](homeassistant-ai/ha-mcp@d2ff93b))
- **addon**: Publish dev addon version 7.4.1.dev288 \[skip ci]
  ([`0f62400`](homeassistant-ai/ha-mcp@0f62400))
- Sync tool docs after merge \[skip ci]
  ([`c7e2066`](homeassistant-ai/ha-mcp@c7e2066))
- **addon**: Publish dev addon version 7.4.1.dev287 \[skip ci]
  ([`c1133d4`](homeassistant-ai/ha-mcp@c1133d4))
- **addon**: Publish dev addon version 7.4.1.dev286 \[skip ci]
  ([`1ae790e`](homeassistant-ai/ha-mcp@1ae790e))
- **addon**: Publish dev addon version 7.4.1.dev285 \[skip ci]
  ([`2387d0c`](homeassistant-ai/ha-mcp@2387d0c))
- **addon**: Publish dev addon version 7.4.1.dev284 \[skip ci]
  ([`dd3a4a5`](homeassistant-ai/ha-mcp@dd3a4a5))
- **addon**: Publish dev addon version 7.4.1.dev283 \[skip ci]
  ([`78af8eb`](homeassistant-ai/ha-mcp@78af8eb))
- Sync tool docs after merge \[skip ci]
  ([`093fd74`](homeassistant-ai/ha-mcp@093fd74))
- **addon**: Publish dev addon version 7.4.1.dev282 \[skip ci]
  ([`2141e15`](homeassistant-ai/ha-mcp@2141e15))
- Sync tool docs after merge \[skip ci]
  ([`7810c95`](homeassistant-ai/ha-mcp@7810c95))
- **addon**: Publish dev addon version 7.4.1.dev281 \[skip ci]
  ([`7d79ec2`](homeassistant-ai/ha-mcp@7d79ec2))
- Sync tool docs after merge \[skip ci]
  ([`a73dc81`](homeassistant-ai/ha-mcp@a73dc81))
- **addon**: Publish dev addon version 7.4.1.dev280 \[skip ci]
  ([`c858ce3`](homeassistant-ai/ha-mcp@c858ce3))
- Sync tool docs after merge \[skip ci]
  ([`a587be0`](homeassistant-ai/ha-mcp@a587be0))
- **addon**: Publish dev addon version 7.4.1.dev279 \[skip ci]
  ([`b78ddb2`](homeassistant-ai/ha-mcp@b78ddb2))
- Sync tool docs after merge \[skip ci]
  ([`1210725`](homeassistant-ai/ha-mcp@1210725))
- **addon**: Publish dev addon version 7.4.1.dev278 \[skip ci]
  ([`a282c17`](homeassistant-ai/ha-mcp@a282c17))
- **addon**: Publish dev addon version 7.4.1.dev277 \[skip ci]
  ([`1081768`](homeassistant-ai/ha-mcp@1081768))
- Sync tool docs after merge \[skip ci]
  ([`e03f5d2`](homeassistant-ai/ha-mcp@e03f5d2))
- **addon**: Publish dev addon version 7.4.1.dev276 \[skip ci]
  ([`c4ef680`](homeassistant-ai/ha-mcp@c4ef680))
- **addon**: Publish dev addon version 7.4.1.dev275 \[skip ci]
  ([`780422d`](homeassistant-ai/ha-mcp@780422d))
- Sync tool docs after merge \[skip ci]
  ([`8a2bd1a`](homeassistant-ai/ha-mcp@8a2bd1a))
- **addon**: Publish dev addon version 7.4.1.dev274 \[skip ci]
  ([`f0f09de`](homeassistant-ai/ha-mcp@f0f09de))
- **addon**: Publish dev addon version 7.4.1.dev273 \[skip ci]
  ([`cb49f68`](homeassistant-ai/ha-mcp@cb49f68))
- **addon**: Publish dev addon version 7.4.1.dev272 \[skip ci]
  ([`5097186`](homeassistant-ai/ha-mcp@5097186))
- **addon**: Publish dev addon version 7.4.1.dev271 \[skip ci]
  ([`4714342`](homeassistant-ai/ha-mcp@4714342))
- **addon**: Publish dev addon version 7.4.1.dev270 \[skip ci]
  ([`217982a`](homeassistant-ai/ha-mcp@217982a))
- **addon**: Publish dev addon version 7.4.1.dev269 \[skip ci]
  ([`a65dd5f`](homeassistant-ai/ha-mcp@a65dd5f))
- Sync tool docs after merge \[skip ci]
  ([`0e6b54f`](homeassistant-ai/ha-mcp@0e6b54f))
- **addon**: Publish dev addon version 7.4.1.dev268 \[skip ci]
  ([`60ba1f2`](homeassistant-ai/ha-mcp@60ba1f2))
- **addon**: Publish dev addon version 7.4.1.dev267 \[skip ci]
  ([`13412aa`](homeassistant-ai/ha-mcp@13412aa))
- Sync tool docs after merge \[skip ci]
  ([`2702a0f`](homeassistant-ai/ha-mcp@2702a0f))
- **addon**: Publish dev addon version 7.4.1.dev266 \[skip ci]
  ([`77abe0b`](homeassistant-ai/ha-mcp@77abe0b))
- **addon**: Publish dev addon version 7.4.1.dev265 \[skip ci]
  ([`08b69db`](homeassistant-ai/ha-mcp@08b69db))
- Sync tool docs after merge \[skip ci]
  ([`c1f24b5`](homeassistant-ai/ha-mcp@c1f24b5))
- **addon**: Publish dev addon version 7.4.1.dev264 \[skip ci]
  ([`f2583f6`](homeassistant-ai/ha-mcp@f2583f6))
- Sync tool docs after merge \[skip ci]
  ([`c2ed2d3`](homeassistant-ai/ha-mcp@c2ed2d3))
- **addon**: Publish dev addon version 7.4.1.dev263 \[skip ci]
  ([`9d43e54`](homeassistant-ai/ha-mcp@9d43e54))
- **addon**: Publish dev addon version 7.4.1.dev262 \[skip ci]
  ([`a7355c8`](homeassistant-ai/ha-mcp@a7355c8))
- Sync tool docs after merge \[skip ci]
  ([`085bd8a`](homeassistant-ai/ha-mcp@085bd8a))
- Convert agents to skills
  ([#&#8203;1084](homeassistant-ai/ha-mcp#1084))
- **addon**: Publish dev addon version 7.4.1.dev261 \[skip ci]
  ([`0d1af36`](homeassistant-ai/ha-mcp@0d1af36))
- **addon**: Publish dev addon version 7.4.1.dev260 \[skip ci]
  ([`29397dc`](homeassistant-ai/ha-mcp@29397dc))
- **addon**: Publish dev addon version 7.4.1.dev259 \[skip ci]
  ([`4bbc74b`](homeassistant-ai/ha-mcp@4bbc74b))
- Sync tool docs after merge \[skip ci]
  ([`0f6d41e`](homeassistant-ai/ha-mcp@0f6d41e))
- **addon**: Publish dev addon version 7.4.1.dev258 \[skip ci]
  ([`6751d08`](homeassistant-ai/ha-mcp@6751d08))
- **addon**: Publish dev addon version 7.4.1.dev257 \[skip ci]
  ([`2213c89`](homeassistant-ai/ha-mcp@2213c89))
- **addon**: Publish dev addon version 7.4.1.dev256 \[skip ci]
  ([`18a366e`](homeassistant-ai/ha-mcp@18a366e))
- **addon**: Publish dev addon version 7.4.1.dev255 \[skip ci]
  ([`0e9b18d`](homeassistant-ai/ha-mcp@0e9b18d))
- **addon**: Publish dev addon version 7.4.1.dev254 \[skip ci]
  ([`39fc65b`](homeassistant-ai/ha-mcp@39fc65b))
- Sync tool docs after merge \[skip ci]
  ([`9fa0aea`](homeassistant-ai/ha-mcp@9fa0aea))
- **addon**: Publish dev addon version 7.4.1.dev253 \[skip ci]
  ([`0dcc59e`](homeassistant-ai/ha-mcp@0dcc59e))
- Sync tool docs after merge \[skip ci]
  ([`ec7413f`](homeassistant-ai/ha-mcp@ec7413f))
- **addon**: Publish dev addon version 7.4.1.dev252 \[skip ci]
  ([`345640c`](homeassistant-ai/ha-mcp@345640c))
- **addon**: Publish dev addon version 7.4.1.dev251 \[skip ci]
  ([`bab9d49`](homeassistant-ai/ha-mcp@bab9d49))
- Sync tool docs after merge \[skip ci]
  ([`726f0a5`](homeassistant-ai/ha-mcp@726f0a5))
- **addon**: Publish dev addon version 7.4.1.dev250 \[skip ci]
  ([`ded04ea`](homeassistant-ai/ha-mcp@ded04ea))
- **addon**: Publish dev addon version 7.4.1.dev249 \[skip ci]
  ([`37d5628`](homeassistant-ai/ha-mcp@37d5628))
- **addon**: Publish dev addon version 7.4.1.dev248 \[skip ci]
  ([`530786a`](homeassistant-ai/ha-mcp@530786a))
- Sync tool docs after merge \[skip ci]
  ([`36719c3`](homeassistant-ai/ha-mcp@36719c3))
- **addon**: Publish dev addon version 7.4.1.dev247 \[skip ci]
  ([`4dc47b5`](homeassistant-ai/ha-mcp@4dc47b5))
- **addon**: Publish dev addon version 7.4.1.dev246 \[skip ci]
  ([`6ffbd6a`](homeassistant-ai/ha-mcp@6ffbd6a))
- Sync tool docs after merge \[skip ci]
  ([`add66e3`](homeassistant-ai/ha-mcp@add66e3))
- **addon**: Publish dev addon version 7.4.1.dev245 \[skip ci]
  ([`d0114af`](homeassistant-ai/ha-mcp@d0114af))
- Sync tool docs after merge \[skip ci]
  ([`0ca41af`](homeassistant-ai/ha-mcp@0ca41af))
- **addon**: Publish dev addon version 7.4.1.dev244 \[skip ci]
  ([`d052dd0`](homeassistant-ai/ha-mcp@d052dd0))
- **addon**: Publish dev addon version 7.4.0.dev243 \[skip ci]
  ([`827bc65`](homeassistant-ai/ha-mcp@827bc65))
- Bump package version to 7.4.1 to match released addon
  ([`4f65497`](homeassistant-ai/ha-mcp@4f65497))
- **addon**: Publish dev addon version 7.4.0.dev242 \[skip ci]
  ([`8ba80ae`](homeassistant-ai/ha-mcp@8ba80ae))
- **addon**: Publish hotfix version 7.4.1
  ([`bda75e6`](homeassistant-ai/ha-mcp@bda75e6))
- **addon**: Publish dev addon version 7.4.0.dev241 \[skip ci]
  ([`2126428`](homeassistant-ai/ha-mcp@2126428))

##### Continuous Integration

- **deps**: Bump renovatebot/github-action in the github-actions group
  ([#&#8203;1218](homeassistant-ai/ha-mcp#1218))
- **deps**: Bump renovatebot/github-action in the github-actions group
  ([#&#8203;1111](homeassistant-ai/ha-mcp#1111))

##### Refactoring

- Extract \_fetch\_dashboards\_list helper ([#&#8203;1193](homeassistant-ai/ha-mcp#1193))
  ([#&#8203;1207](homeassistant-ai/ha-mcp#1207))

##### Testing

- **e2e**: Module-scope bulk\_automations + bulk\_scripts fixtures (refs [#&#8203;366](homeassistant-ai/ha-mcp#366))
  ([#&#8203;1275](homeassistant-ai/ha-mcp#1275))
- **e2e**: Lower INPUT\_BOOLEAN\_WAIT from 30s to 10s (refs [#&#8203;366](homeassistant-ai/ha-mcp#366))
  ([#&#8203;1273](homeassistant-ai/ha-mcp#1273))
- **e2e**: Generalize readiness-gate diagnostics helper (closes [#&#8203;1267](homeassistant-ai/ha-mcp#1267))
  ([#&#8203;1271](homeassistant-ai/ha-mcp#1271))
- **e2e**: Narrow except clauses in e2e polling helpers (closes [#&#8203;1266](homeassistant-ai/ha-mcp#1266))
  ([#&#8203;1270](homeassistant-ai/ha-mcp#1270))
- **e2e**: Drop ha\_mcp\_tools retry-path + pre-install manifest requirements
  ([#&#8203;1268](homeassistant-ai/ha-mcp#1268))
- **e2e**: Instrument and retry ha\_mcp\_tools readiness wait
  ([#&#8203;1262](homeassistant-ai/ha-mcp#1262))
- Use time.monotonic() in UAT runner and test\_env\_manager
  ([#&#8203;1254](homeassistant-ai/ha-mcp#1254))
- **e2e**: Detect partial/corrupt hacs\_frontend dir in fast-path guard
  ([#&#8203;1253](homeassistant-ai/ha-mcp#1253))
- **e2e**: Remove unused wait/assert helpers ([post-#&#8203;1249](https://github.com/post-/ha-mcp/issues/1249) audit)
  ([#&#8203;1256](homeassistant-ai/ha-mcp#1256))
- **e2e**: Clear stale .hacs\_frontend.lock from prior crashed runs
  ([#&#8203;1252](homeassistant-ai/ha-mcp#1252))
- **e2e**: Use time.monotonic() in workflow polling loops
  ([#&#8203;1258](homeassistant-ai/ha-mcp#1258))
- **e2e**: Use time.monotonic() for duration polling ([#&#8203;1234](homeassistant-ai/ha-mcp#1234))
  ([#&#8203;1249](homeassistant-ai/ha-mcp#1249))
- **e2e**: Close ARM ha\_mcp\_tools readiness race under loadscope
  ([#&#8203;1208](homeassistant-ai/ha-mcp#1208))
- **hacs**: Tighten is\_hacs\_unavailable to not match legitimate "Repository not found"
  ([#&#8203;1246](homeassistant-ai/ha-mcp#1246))
- **seed**: Unblock 3 silent-skip pagination/state tests via baked recorder DB
  ([#&#8203;1240](homeassistant-ai/ha-mcp#1240))
- **seed**: Register a writable local\_calendar to unblock event-creation test
  ([#&#8203;1243](homeassistant-ai/ha-mcp#1243))
- **addon**: Fix base64 padding-bit flake in token tamper tests ([#&#8203;1238](homeassistant-ai/ha-mcp#1238))
  ([#&#8203;1241](homeassistant-ai/ha-mcp#1241))
- **seed**: Add a writable scene for test\_call\_service\_scene\_turn\_on
  ([#&#8203;1231](homeassistant-ai/ha-mcp#1231))
- **seed**: Assign demo device to living\_room area for filter test
  ([#&#8203;1230](homeassistant-ai/ha-mcp#1230))
- **e2e**: Drop nonexistent sun service from session readiness wait
  ([#&#8203;1227](homeassistant-ai/ha-mcp#1227))
- **e2e**: Self-contain dashboard register/remove to fix ARM xdist race ([#&#8203;1196](homeassistant-ai/ha-mcp#1196))
  ([#&#8203;1201](homeassistant-ai/ha-mcp#1201))
- Fix EN dash in docstring causing RUF002 lint failure
  ([`eac5916`](homeassistant-ai/ha-mcp@eac5916))
- Address Gemini review feedback on host detection and port allocation
  ([`960305e`](homeassistant-ai/ha-mcp@960305e))
- Fix three categories of E2E test flakiness
  ([`39417ff`](homeassistant-ai/ha-mcp@39417ff))
- **e2e**: Pin config\_hash stability for dashboards
  ([#&#8203;1132](homeassistant-ai/ha-mcp#1132))

</details>

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about these updates again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xMDEuMSIsInVwZGF0ZWRJblZlciI6IjQzLjEwMS4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJyZW5vdmF0ZS9jb250YWluZXIiLCJ0eXBlL21pbm9yIl19-->

Reviewed-on: https://git.erwanleboucher.dev/eleboucher/homelab/pulls/455
doonga added a commit to greyrock-labs/home-ops that referenced this pull request May 13, 2026
….0 ) (#26)

This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [ghcr.io/homeassistant-ai/ha-mcp](https://github.com/homeassistant-ai/ha-mcp) | minor | `7.4.0` → `7.5.0` |

---

### Release Notes

<details>
<summary>homeassistant-ai/ha-mcp (ghcr.io/homeassistant-ai/ha-mcp)</summary>

### [`v7.5.0`](https://github.com/homeassistant-ai/ha-mcp/blob/HEAD/CHANGELOG.md#v750-2026-05-13)

[Compare Source](homeassistant-ai/ha-mcp@v7.4.0...v7.5.0)

##### Added

- Add ENABLE\_LITE\_DOCSTRINGS beta toggle
  ([#&#8203;1259](homeassistant-ai/ha-mcp#1259))
- Add ha\_call\_event tool for publishing events on the HA event bus ([#&#8203;996](homeassistant-ai/ha-mcp#996))
  ([#&#8203;1239](homeassistant-ai/ha-mcp#1239))
- Pinpoint backslash-escape mistake in python\_sandbox errors
  ([#&#8203;1204](homeassistant-ai/ha-mcp#1204))
- Reject empty-trigger automations targeting scene.create
  ([#&#8203;1187](homeassistant-ai/ha-mcp#1187))
- Add scene config tools — ha\_config\_get/set/remove\_scene
  ([#&#8203;1168](homeassistant-ai/ha-mcp#1168))
- **addon**: Optional OAuth 2.1 mode for webhook proxy (beta)
  ([#&#8203;1184](homeassistant-ai/ha-mcp#1184))
- Surface helper schema inline in ha\_config\_set\_helper validation errors ([#&#8203;1149](homeassistant-ai/ha-mcp#1149))
  ([#&#8203;1179](homeassistant-ai/ha-mcp#1179))
- Emit progress via FastMCP Context in long-running tools
  ([#&#8203;1124](homeassistant-ai/ha-mcp#1124))
- Broaden python\_transform AST allowlist + improve error UX
  ([#&#8203;1163](homeassistant-ai/ha-mcp#1163))
- Add ha\_manage\_custom\_tool — sandboxed code execution escape hatch
  ([#&#8203;854](homeassistant-ai/ha-mcp#854))
- Always-on skills; rename list/read resource tools with ha\_ prefix
  ([#&#8203;1136](homeassistant-ai/ha-mcp#1136))
- Expose device\_class + options on ha\_set\_entity / ha\_get\_entity (Show As)
  ([#&#8203;1135](homeassistant-ai/ha-mcp#1135))
- **site**: Inline wizard data into setup.astro, migrate setup nuggets, drop content collections
  ([#&#8203;1120](homeassistant-ai/ha-mcp#1120))
- Add "Advanced debug logging" toggle for kill-signal diagnostics
  ([#&#8203;1117](homeassistant-ai/ha-mcp#1117))
- **yaml**: Scoped lovelace.dashboards.\<url\_path> support (issue [#&#8203;1034](homeassistant-ai/ha-mcp#1034))
  ([#&#8203;1103](homeassistant-ai/ha-mcp#1103))
- Add HA\_VERIFY\_SSL toggle to disable TLS verification
  ([#&#8203;1104](homeassistant-ai/ha-mcp#1104))
- Per-top-level-key config\_hash for ha\_manage\_energy\_prefs ([#&#8203;1049](homeassistant-ai/ha-mcp#1049))
  ([#&#8203;1098](homeassistant-ai/ha-mcp#1098))
- **site**: Add gemini-cli setup notes + compose hardening to wizard ([#&#8203;1027](homeassistant-ai/ha-mcp#1027))
  ([#&#8203;1087](homeassistant-ai/ha-mcp#1087))
- Add convenience modes to ha\_manage\_energy\_prefs ([#&#8203;1050](homeassistant-ai/ha-mcp#1050))
  ([#&#8203;1073](homeassistant-ai/ha-mcp#1073))
- Surface integration log levels in ha\_get\_logs/integration/addon ([#&#8203;956](homeassistant-ai/ha-mcp#956))
  ([#&#8203;1003](homeassistant-ai/ha-mcp#1003))
- Expose allowlist\_external\_dirs in ha\_get\_overview full system\_info
  ([#&#8203;1053](homeassistant-ai/ha-mcp#1053))
- **dashboards**: Unify identifier handling in ha\_config\_\*\_dashboard tools ([#&#8203;981](homeassistant-ai/ha-mcp#981))
  ([#&#8203;1075](homeassistant-ai/ha-mcp#1075))
- Include addon container logs in bug reports
  ([#&#8203;934](homeassistant-ai/ha-mcp#934))
- Add WebSocket response-shaping controls to ha\_manage\_addon
  ([#&#8203;1009](homeassistant-ai/ha-mcp#1009))
- Web-based settings UI for per-tool enable/disable/pin
  ([#&#8203;960](homeassistant-ai/ha-mcp#960))
- **site**: Add OpenCode support to setup wizard
  ([#&#8203;1080](homeassistant-ai/ha-mcp#1080))

##### Changed

- Clarify standard-mode HTTP deployment guidance
  ([#&#8203;1185](homeassistant-ai/ha-mcp#1185))
- Add Cloudflared add-on hostname alternative for tunnel service
  ([#&#8203;1183](homeassistant-ai/ha-mcp#1183))
- Align tool naming convention between AGENTS.md and styleguide ([#&#8203;943](homeassistant-ai/ha-mcp#943))
  ([#&#8203;1174](homeassistant-ai/ha-mcp#1174))
- **addon**: Note tool-list ([#&#8203;985](homeassistant-ai/ha-mcp#985 divergence; fix [#&#8203;1139](https://github.com/homeassistant-ai/ha-mcp/issues/1139)/[#&#8203;1162](https://github.com/homeassistant-ai/ha-mcp/issues/1162) test conflict
  ([#&#8203;1172](homeassistant-ai/ha-mcp#1172))
- Add brew install option for mcp-proxy on macOS
  ([#&#8203;1171](homeassistant-ai/ha-mcp#1171))
- Update contributors list \[contributors-updated]
  ([`aba01a1`](homeassistant-ai/ha-mcp@aba01a1))
- Warn against enable\_tool\_search on Claude Sonnet/Opus ([#&#8203;1088](homeassistant-ai/ha-mcp#1088))
  ([#&#8203;1140](homeassistant-ai/ha-mcp#1140))
- Address [#&#8203;1094](homeassistant-ai/ha-mcp#1094) review nits on OpenCode mirror comments
  ([#&#8203;1105](homeassistant-ai/ha-mcp#1105))

##### Fixed

- **integrations**: Surface ConfigEntry.options via OptionsFlow probe
  ([#&#8203;1245](homeassistant-ai/ha-mcp#1245))
- **backup**: Discover local agent at call time instead of hardcoding hassio.local
  ([#&#8203;1244](homeassistant-ai/ha-mcp#1244))
- Triage all 10 ha\_search\_entities behaviors from [#&#8203;1170](homeassistant-ai/ha-mcp#1170)
  ([#&#8203;1195](homeassistant-ai/ha-mcp#1195))
- Replace cron with systemd for demo server (prevents process leak)
  ([#&#8203;1110](homeassistant-ai/ha-mcp#1110))
- Improve ha\_manage\_addon discoverability (BM25 keywords + slug examples)
  ([#&#8203;1200](homeassistant-ai/ha-mcp#1200))
- Route Supervisor 401s to structured tool errors + add E2E coverage ([#&#8203;1129](homeassistant-ai/ha-mcp#1129))
  ([#&#8203;1192](homeassistant-ai/ha-mcp#1192))
- Harden \_validate\_category\_id gate to cover dict-promoted category
  ([#&#8203;1190](homeassistant-ai/ha-mcp#1190))
- Broaden template anti-pattern detection + skill discoverability ([#&#8203;1011](homeassistant-ai/ha-mcp#1011))
  ([#&#8203;1181](homeassistant-ai/ha-mcp#1181))
- Return newest automation traces, add offset+order pagination ([#&#8203;1177](homeassistant-ai/ha-mcp#1177))
  ([#&#8203;1178](homeassistant-ai/ha-mcp#1178))
- **security**: Write YAML backups outside www/ (GHSA-g39v-cvjh-8fpf)
  ([#&#8203;1180](homeassistant-ai/ha-mcp#1180))
- **search**: Apply domain\_filter when area\_filter is set ([#&#8203;1162](homeassistant-ai/ha-mcp#1162))
  ([#&#8203;1165](homeassistant-ai/ha-mcp#1165))
- **resources**: Reject HA-config YAML in dashboard resource content
  ([#&#8203;1160](homeassistant-ai/ha-mcp#1160))
- Close 19 bugs in ha\_config\_set\_helper (issue [#&#8203;1150](homeassistant-ai/ha-mcp#1150))
  ([#&#8203;1151](homeassistant-ai/ha-mcp#1151))
- Route addon log fetches directly to supervisor on addon installs
  ([#&#8203;1126](homeassistant-ai/ha-mcp#1126))
- Survive read-only filesystems at startup
  ([#&#8203;1138](homeassistant-ai/ha-mcp#1138))
- **helpers**: Clarify name-required-on-create for ha\_config\_set\_helper
  ([#&#8203;1143](homeassistant-ai/ha-mcp#1143))
- Resolve disabled entities via entity\_registry in helper deletion
  ([#&#8203;1119](homeassistant-ai/ha-mcp#1119))
- Allow unary operators in python\_transform sandbox
  ([#&#8203;1118](homeassistant-ai/ha-mcp#1118))
- **site**: Add github-copilot-agents wizard branch + delete unreferenced data/clients.ts
  ([#&#8203;1108](homeassistant-ai/ha-mcp#1108))
- **addons**: Route addon API calls through HA Core ingress proxy
  ([#&#8203;1069](homeassistant-ai/ha-mcp#1069))
- **webhook-proxy**: Surface webhook registration failures instead of silently loading
  ([#&#8203;1101](homeassistant-ai/ha-mcp#1101))
- **site**: Resolve client display-order collisions and anchor OpenCode shape
  ([#&#8203;1094](homeassistant-ai/ha-mcp#1094))

##### Performance Improvements

- Dedupe lovelace/dashboards/list in ha\_config\_set\_dashboard ([#&#8203;1085](homeassistant-ai/ha-mcp#1085))
  ([#&#8203;1191](homeassistant-ai/ha-mcp#1191))

##### Refactoring

- Drop obsolete ha\_mcp\_tools defensive ruamel.yaml imports ([post-#&#8203;1268](https://github.com/post-/ha-mcp/issues/1268))
  ([#&#8203;1269](homeassistant-ai/ha-mcp#1269))
- Extract shared Supervisor httpx client helper ([#&#8203;1130](homeassistant-ai/ha-mcp#1130))
  ([#&#8203;1203](homeassistant-ai/ha-mcp#1203))
- Surface client identity, AI model, config toggles, and prompt context in ha\_report\_issue
  ([#&#8203;1189](homeassistant-ai/ha-mcp#1189))
- Harden Context injection with safe-emit + branch coverage
  ([#&#8203;1173](homeassistant-ai/ha-mcp#1173))
- Consolidate area/floor set+remove tools (revisit of [#&#8203;813](homeassistant-ai/ha-mcp#813))
  ([#&#8203;1139](homeassistant-ai/ha-mcp#1139))
- Pass verify\_ssl to remaining direct-Supervisor httpx callers
  ([#&#8203;1128](homeassistant-ai/ha-mcp#1128))
- Validate only new entries on convenience-mode writes ([#&#8203;1086](homeassistant-ai/ha-mcp#1086))
  ([#&#8203;1100](homeassistant-ai/ha-mcp#1100))

***

<details>
<summary>Internal Changes</summary>

##### Fixed

- **ci**: Align pr.yml E2E with --dist loadscope ([#&#8203;1206](homeassistant-ai/ha-mcp#1206))
  ([#&#8203;1247](homeassistant-ai/ha-mcp#1247))
- **ci**: Switch Renovate to a GitHub App token to allow workflow-file pushes
  ([#&#8203;1229](homeassistant-ai/ha-mcp#1229))
- **ci**: Break gemini-triage retrigger loop and bump turn budget
  ([#&#8203;1131](homeassistant-ai/ha-mcp#1131))
- **ci**: Harden gemini-triage so failures stop spamming user issues
  ([#&#8203;1122](homeassistant-ai/ha-mcp#1122))
- **ci**: Unbreak hotfix-release semantic-release run
  ([#&#8203;1091](homeassistant-ai/ha-mcp#1091))

##### Chores

- **addon**: Publish dev addon version 7.4.1.dev299 \[skip ci]
  ([`397aa6d`](homeassistant-ai/ha-mcp@397aa6d))
- **addon**: Publish dev addon version 7.4.1.dev298 \[skip ci]
  ([`942b7e0`](homeassistant-ai/ha-mcp@942b7e0))
- Sync tool docs after merge \[skip ci]
  ([`6823c47`](homeassistant-ai/ha-mcp@6823c47))
- **addon**: Publish dev addon version 7.4.1.dev297 \[skip ci]
  ([`6eac062`](homeassistant-ai/ha-mcp@6eac062))
- **addon**: Publish dev addon version 7.4.1.dev296 \[skip ci]
  ([`b2afe93`](homeassistant-ai/ha-mcp@b2afe93))
- **addon**: Publish dev addon version 7.4.1.dev295 \[skip ci]
  ([`4f4c4f3`](homeassistant-ai/ha-mcp@4f4c4f3))
- **deps**: Update ghcr.io/home-assistant/home-assistant docker tag to v2026.5.1
  ([#&#8203;1236](homeassistant-ai/ha-mcp#1236))
- **addon**: Publish dev addon version 7.4.1.dev294 \[skip ci]
  ([`fd24991`](homeassistant-ai/ha-mcp@fd24991))
- **deps**: Update ghcr.io/astral-sh/uv docker tag to v0.11.13
  ([#&#8203;1233](homeassistant-ai/ha-mcp#1233))
- **addon**: Publish dev addon version 7.4.1.dev293 \[skip ci]
  ([`fcc6496`](homeassistant-ai/ha-mcp@fcc6496))
- **addon**: Publish dev addon version 7.4.1.dev292 \[skip ci]
  ([`2961650`](homeassistant-ai/ha-mcp@2961650))
- **addon**: Publish dev addon version 7.4.1.dev291 \[skip ci]
  ([`5703112`](homeassistant-ai/ha-mcp@5703112))
- **addon**: Publish dev addon version 7.4.1.dev290 \[skip ci]
  ([`19b2f65`](homeassistant-ai/ha-mcp@19b2f65))
- **addon**: Publish dev addon version 7.4.1.dev289 \[skip ci]
  ([`e5a1365`](homeassistant-ai/ha-mcp@e5a1365))
- Sync tool docs after merge \[skip ci]
  ([`d2ff93b`](homeassistant-ai/ha-mcp@d2ff93b))
- **addon**: Publish dev addon version 7.4.1.dev288 \[skip ci]
  ([`0f62400`](homeassistant-ai/ha-mcp@0f62400))
- Sync tool docs after merge \[skip ci]
  ([`c7e2066`](homeassistant-ai/ha-mcp@c7e2066))
- **addon**: Publish dev addon version 7.4.1.dev287 \[skip ci]
  ([`c1133d4`](homeassistant-ai/ha-mcp@c1133d4))
- **addon**: Publish dev addon version 7.4.1.dev286 \[skip ci]
  ([`1ae790e`](homeassistant-ai/ha-mcp@1ae790e))
- **addon**: Publish dev addon version 7.4.1.dev285 \[skip ci]
  ([`2387d0c`](homeassistant-ai/ha-mcp@2387d0c))
- **addon**: Publish dev addon version 7.4.1.dev284 \[skip ci]
  ([`dd3a4a5`](homeassistant-ai/ha-mcp@dd3a4a5))
- **addon**: Publish dev addon version 7.4.1.dev283 \[skip ci]
  ([`78af8eb`](homeassistant-ai/ha-mcp@78af8eb))
- Sync tool docs after merge \[skip ci]
  ([`093fd74`](homeassistant-ai/ha-mcp@093fd74))
- **addon**: Publish dev addon version 7.4.1.dev282 \[skip ci]
  ([`2141e15`](homeassistant-ai/ha-mcp@2141e15))
- Sync tool docs after merge \[skip ci]
  ([`7810c95`](homeassistant-ai/ha-mcp@7810c95))
- **addon**: Publish dev addon version 7.4.1.dev281 \[skip ci]
  ([`7d79ec2`](homeassistant-ai/ha-mcp@7d79ec2))
- Sync tool docs after merge \[skip ci]
  ([`a73dc81`](homeassistant-ai/ha-mcp@a73dc81))
- **addon**: Publish dev addon version 7.4.1.dev280 \[skip ci]
  ([`c858ce3`](homeassistant-ai/ha-mcp@c858ce3))
- Sync tool docs after merge \[skip ci]
  ([`a587be0`](homeassistant-ai/ha-mcp@a587be0))
- **addon**: Publish dev addon version 7.4.1.dev279 \[skip ci]
  ([`b78ddb2`](homeassistant-ai/ha-mcp@b78ddb2))
- Sync tool docs after merge \[skip ci]
  ([`1210725`](homeassistant-ai/ha-mcp@1210725))
- **addon**: Publish dev addon version 7.4.1.dev278 \[skip ci]
  ([`a282c17`](homeassistant-ai/ha-mcp@a282c17))
- **addon**: Publish dev addon version 7.4.1.dev277 \[skip ci]
  ([`1081768`](homeassistant-ai/ha-mcp@1081768))
- Sync tool docs after merge \[skip ci]
  ([`e03f5d2`](homeassistant-ai/ha-mcp@e03f5d2))
- **addon**: Publish dev addon version 7.4.1.dev276 \[skip ci]
  ([`c4ef680`](homeassistant-ai/ha-mcp@c4ef680))
- **addon**: Publish dev addon version 7.4.1.dev275 \[skip ci]
  ([`780422d`](homeassistant-ai/ha-mcp@780422d))
- Sync tool docs after merge \[skip ci]
  ([`8a2bd1a`](homeassistant-ai/ha-mcp@8a2bd1a))
- **addon**: Publish dev addon version 7.4.1.dev274 \[skip ci]
  ([`f0f09de`](homeassistant-ai/ha-mcp@f0f09de))
- **addon**: Publish dev addon version 7.4.1.dev273 \[skip ci]
  ([`cb49f68`](homeassistant-ai/ha-mcp@cb49f68))
- **addon**: Publish dev addon version 7.4.1.dev272 \[skip ci]
  ([`5097186`](homeassistant-ai/ha-mcp@5097186))
- **addon**: Publish dev addon version 7.4.1.dev271 \[skip ci]
  ([`4714342`](homeassistant-ai/ha-mcp@4714342))
- **addon**: Publish dev addon version 7.4.1.dev270 \[skip ci]
  ([`217982a`](homeassistant-ai/ha-mcp@217982a))
- **addon**: Publish dev addon version 7.4.1.dev269 \[skip ci]
  ([`a65dd5f`](homeassistant-ai/ha-mcp@a65dd5f))
- Sync tool docs after merge \[skip ci]
  ([`0e6b54f`](homeassistant-ai/ha-mcp@0e6b54f))
- **addon**: Publish dev addon version 7.4.1.dev268 \[skip ci]
  ([`60ba1f2`](homeassistant-ai/ha-mcp@60ba1f2))
- **addon**: Publish dev addon version 7.4.1.dev267 \[skip ci]
  ([`13412aa`](homeassistant-ai/ha-mcp@13412aa))
- Sync tool docs after merge \[skip ci]
  ([`2702a0f`](homeassistant-ai/ha-mcp@2702a0f))
- **addon**: Publish dev addon version 7.4.1.dev266 \[skip ci]
  ([`77abe0b`](homeassistant-ai/ha-mcp@77abe0b))
- **addon**: Publish dev addon version 7.4.1.dev265 \[skip ci]
  ([`08b69db`](homeassistant-ai/ha-mcp@08b69db))
- Sync tool docs after merge \[skip ci]
  ([`c1f24b5`](homeassistant-ai/ha-mcp@c1f24b5))
- **addon**: Publish dev addon version 7.4.1.dev264 \[skip ci]
  ([`f2583f6`](homeassistant-ai/ha-mcp@f2583f6))
- Sync tool docs after merge \[skip ci]
  ([`c2ed2d3`](homeassistant-ai/ha-mcp@c2ed2d3))
- **addon**: Publish dev addon version 7.4.1.dev263 \[skip ci]
  ([`9d43e54`](homeassistant-ai/ha-mcp@9d43e54))
- **addon**: Publish dev addon version 7.4.1.dev262 \[skip ci]
  ([`a7355c8`](homeassistant-ai/ha-mcp@a7355c8))
- Sync tool docs after merge \[skip ci]
  ([`085bd8a`](homeassistant-ai/ha-mcp@085bd8a))
- Convert agents to skills
  ([#&#8203;1084](homeassistant-ai/ha-mcp#1084))
- **addon**: Publish dev addon version 7.4.1.dev261 \[skip ci]
  ([`0d1af36`](homeassistant-ai/ha-mcp@0d1af36))
- **addon**: Publish dev addon version 7.4.1.dev260 \[skip ci]
  ([`29397dc`](homeassistant-ai/ha-mcp@29397dc))
- **addon**: Publish dev addon version 7.4.1.dev259 \[skip ci]
  ([`4bbc74b`](homeassistant-ai/ha-mcp@4bbc74b))
- Sync tool docs after merge \[skip ci]
  ([`0f6d41e`](homeassistant-ai/ha-mcp@0f6d41e))
- **addon**: Publish dev addon version 7.4.1.dev258 \[skip ci]
  ([`6751d08`](homeassistant-ai/ha-mcp@6751d08))
- **addon**: Publish dev addon version 7.4.1.dev257 \[skip ci]
  ([`2213c89`](homeassistant-ai/ha-mcp@2213c89))
- **addon**: Publish dev addon version 7.4.1.dev256 \[skip ci]
  ([`18a366e`](homeassistant-ai/ha-mcp@18a366e))
- **addon**: Publish dev addon version 7.4.1.dev255 \[skip ci]
  ([`0e9b18d`](homeassistant-ai/ha-mcp@0e9b18d))
- **addon**: Publish dev addon version 7.4.1.dev254 \[skip ci]
  ([`39fc65b`](homeassistant-ai/ha-mcp@39fc65b))
- Sync tool docs after merge \[skip ci]
  ([`9fa0aea`](homeassistant-ai/ha-mcp@9fa0aea))
- **addon**: Publish dev addon version 7.4.1.dev253 \[skip ci]
  ([`0dcc59e`](homeassistant-ai/ha-mcp@0dcc59e))
- Sync tool docs after merge \[skip ci]
  ([`ec7413f`](homeassistant-ai/ha-mcp@ec7413f))
- **addon**: Publish dev addon version 7.4.1.dev252 \[skip ci]
  ([`345640c`](homeassistant-ai/ha-mcp@345640c))
- **addon**: Publish dev addon version 7.4.1.dev251 \[skip ci]
  ([`bab9d49`](homeassistant-ai/ha-mcp@bab9d49))
- Sync tool docs after merge \[skip ci]
  ([`726f0a5`](homeassistant-ai/ha-mcp@726f0a5))
- **addon**: Publish dev addon version 7.4.1.dev250 \[skip ci]
  ([`ded04ea`](homeassistant-ai/ha-mcp@ded04ea))
- **addon**: Publish dev addon version 7.4.1.dev249 \[skip ci]
  ([`37d5628`](homeassistant-ai/ha-mcp@37d5628))
- **addon**: Publish dev addon version 7.4.1.dev248 \[skip ci]
  ([`530786a`](homeassistant-ai/ha-mcp@530786a))
- Sync tool docs after merge \[skip ci]
  ([`36719c3`](homeassistant-ai/ha-mcp@36719c3))
- **addon**: Publish dev addon version 7.4.1.dev247 \[skip ci]
  ([`4dc47b5`](homeassistant-ai/ha-mcp@4dc47b5))
- **addon**: Publish dev addon version 7.4.1.dev246 \[skip ci]
  ([`6ffbd6a`](homeassistant-ai/ha-mcp@6ffbd6a))
- Sync tool docs after merge \[skip ci]
  ([`add66e3`](homeassistant-ai/ha-mcp@add66e3))
- **addon**: Publish dev addon version 7.4.1.dev245 \[skip ci]
  ([`d0114af`](homeassistant-ai/ha-mcp@d0114af))
- Sync tool docs after merge \[skip ci]
  ([`0ca41af`](homeassistant-ai/ha-mcp@0ca41af))
- **addon**: Publish dev addon version 7.4.1.dev244 \[skip ci]
  ([`d052dd0`](homeassistant-ai/ha-mcp@d052dd0))
- **addon**: Publish dev addon version 7.4.0.dev243 \[skip ci]
  ([`827bc65`](homeassistant-ai/ha-mcp@827bc65))
- Bump package version to 7.4.1 to match released addon
  ([`4f65497`](homeassistant-ai/ha-mcp@4f65497))
- **addon**: Publish dev addon version 7.4.0.dev242 \[skip ci]
  ([`8ba80ae`](homeassistant-ai/ha-mcp@8ba80ae))
- **addon**: Publish hotfix version 7.4.1
  ([`bda75e6`](homeassistant-ai/ha-mcp@bda75e6))
- **addon**: Publish dev addon version 7.4.0.dev241 \[skip ci]
  ([`2126428`](homeassistant-ai/ha-mcp@2126428))

##### Continuous Integration

- **deps**: Bump renovatebot/github-action in the github-actions group
  ([#&#8203;1218](homeassistant-ai/ha-mcp#1218))
- **deps**: Bump renovatebot/github-action in the github-actions group
  ([#&#8203;1111](homeassistant-ai/ha-mcp#1111))

##### Refactoring

- Extract \_fetch\_dashboards\_list helper ([#&#8203;1193](homeassistant-ai/ha-mcp#1193))
  ([#&#8203;1207](homeassistant-ai/ha-mcp#1207))

##### Testing

- **e2e**: Module-scope bulk\_automations + bulk\_scripts fixtures (refs [#&#8203;366](homeassistant-ai/ha-mcp#366))
  ([#&#8203;1275](homeassistant-ai/ha-mcp#1275))
- **e2e**: Lower INPUT\_BOOLEAN\_WAIT from 30s to 10s (refs [#&#8203;366](homeassistant-ai/ha-mcp#366))
  ([#&#8203;1273](homeassistant-ai/ha-mcp#1273))
- **e2e**: Generalize readiness-gate diagnostics helper (closes [#&#8203;1267](homeassistant-ai/ha-mcp#1267))
  ([#&#8203;1271](homeassistant-ai/ha-mcp#1271))
- **e2e**: Narrow except clauses in e2e polling helpers (closes [#&#8203;1266](homeassistant-ai/ha-mcp#1266))
  ([#&#8203;1270](homeassistant-ai/ha-mcp#1270))
- **e2e**: Drop ha\_mcp\_tools retry-path + pre-install manifest requirements
  ([#&#8203;1268](homeassistant-ai/ha-mcp#1268))
- **e2e**: Instrument and retry ha\_mcp\_tools readiness wait
  ([#&#8203;1262](homeassistant-ai/ha-mcp#1262))
- Use time.monotonic() in UAT runner and test\_env\_manager
  ([#&#8203;1254](homeassistant-ai/ha-mcp#1254))
- **e2e**: Detect partial/corrupt hacs\_frontend dir in fast-path guard
  ([#&#8203;1253](homeassistant-ai/ha-mcp#1253))
- **e2e**: Remove unused wait/assert helpers ([post-#&#8203;1249](https://github.com/post-/ha-mcp/issues/1249) audit)
  ([#&#8203;1256](homeassistant-ai/ha-mcp#1256))
- **e2e**: Clear stale .hacs\_frontend.lock from prior crashed runs
  ([#&#8203;1252](homeassistant-ai/ha-mcp#1252))
- **e2e**: Use time.monotonic() in workflow polling loops
  ([#&#8203;1258](homeassistant-ai/ha-mcp#1258))
- **e2e**: Use time.monotonic() for duration polling ([#&#8203;1234](homeassistant-ai/ha-mcp#1234))
  ([#&#8203;1249](homeassistant-ai/ha-mcp#1249))
- **e2e**: Close ARM ha\_mcp\_tools readiness race under loadscope
  ([#&#8203;1208](homeassistant-ai/ha-mcp#1208))
- **hacs**: Tighten is\_hacs\_unavailable to not match legitimate "Repository not found"
  ([#&#8203;1246](homeassistant-ai/ha-mcp#1246))
- **seed**: Unblock 3 silent-skip pagination/state tests via baked recorder DB
  ([#&#8203;1240](homeassistant-ai/ha-mcp#1240))
- **seed**: Register a writable local\_calendar to unblock event-creation test
  ([#&#8203;1243](homeassistant-ai/ha-mcp#1243))
- **addon**: Fix base64 padding-bit flake in token tamper tests ([#&#8203;1238](homeassistant-ai/ha-mcp#1238))
  ([#&#8203;1241](homeassistant-ai/ha-mcp#1241))
- **seed**: Add a writable scene for test\_call\_service\_scene\_turn\_on
  ([#&#8203;1231](homeassistant-ai/ha-mcp#1231))
- **seed**: Assign demo device to living\_room area for filter test
  ([#&#8203;1230](homeassistant-ai/ha-mcp#1230))
- **e2e**: Drop nonexistent sun service from session readiness wait
  ([#&#8203;1227](homeassistant-ai/ha-mcp#1227))
- **e2e**: Self-contain dashboard register/remove to fix ARM xdist race ([#&#8203;1196](homeassistant-ai/ha-mcp#1196))
  ([#&#8203;1201](homeassistant-ai/ha-mcp#1201))
- Fix EN dash in docstring causing RUF002 lint failure
  ([`eac5916`](homeassistant-ai/ha-mcp@eac5916))
- Address Gemini review feedback on host detection and port allocation
  ([`960305e`](homeassistant-ai/ha-mcp@960305e))
- Fix three categories of E2E test flakiness
  ([`39417ff`](homeassistant-ai/ha-mcp@39417ff))
- **e2e**: Pin config\_hash stability for dashboards
  ([#&#8203;1132](homeassistant-ai/ha-mcp#1132))

</details>

</details>

---

### Configuration

📅 **Schedule**: (in timezone America/New_York)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about these updates again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xNjAuNyIsInVwZGF0ZWRJblZlciI6IjQzLjE2MC43IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJyZW5vdmF0ZS9jb250YWluZXIiLCJ0eXBlL21pbm9yIl19-->

Co-authored-by: todd <tpunderson@greyrock.io>
Reviewed-on: https://git.greyrock.io/greyrock-labs/home-ops/pulls/26
kingpanther13 added a commit that referenced this pull request May 29, 2026
… / Settings UI (#1486)

* fix(addon): enable ingress so the stable add-on shows the Open Web UI / Settings UI

The stable Home Assistant add-on (homeassistant-addon/) never declared
ingress, so HA rendered no "Open Web UI" button and the web Settings UI was
unreachable on stable -- even though start.py already mounts the settings
routes for the ingress proxy (#960) and #1431 wired beta access into stable's
code. Mirror the dev add-on's proven ingress block, add a regression
assertion, and document that functional addon config is not auto-synced
between the dev/stable flavors.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(addon): assert ingress_stream in the stable add-on config

Reviewers (Gemini + PR toolkit) noted the regression test locked ingress and
ingress_port but not ingress_stream, which the fix also added. Assert it so a
silent drop of the streaming key is caught too.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(addon): document the stable web Settings UI and clarify config sync

- Add a "Tool Settings Web UI" section to the stable add-on DOCS.md (it already
  referenced the web UI / Tool Security Policies tab but never said how to open
  it); stable-adjusted from the dev flavor, dropping dev-only text-field options.
- Note in config.yaml why ingress_stream is required (streamable-HTTP transport).
- Fix AGENTS.md: changelog is synced by the "Copy changelog" step in the
  semantic-release job, not by update-addon-config (which only bumps version).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: kingpanther13 <kingpanther13@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] User configurable tools and pinned tools on config screen in HAOS addon UI (for after #727 is merged)

2 participants