Commit c5ecdde
feat: Add generic add-on API proxy tool (ha_call_addon_api) (#641)
* feat: add ha_call_addon_api tool for add-on Ingress proxy
Add a generic tool that proxies HTTP requests to any add-on's web API
through Home Assistant's Ingress system. Works with Node-RED, Frigate,
or any add-on that exposes a web API.
Changes:
- New tool: ha_call_addon_api (slug, path, method, body)
- Extend ha_get_addon with optional slug param for detailed add-on info
- Refactor duplicate WS boilerplate into _supervisor_api_call() helper
Closes #634
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address Gemini code review feedback
- Add path traversal sanitization (reject '..' components)
- Use structured error helpers (create_error_response, create_validation_error,
create_timeout_error, create_connection_error, exception_to_structured_error)
instead of raw error dicts, per repo style guidelines
- Change destructiveHint to False (default method is GET)
- Remove unused import
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: resolve ruff I001 and missing readOnlyHint annotation
- Wrap .helpers import to multiline (91 chars exceeded 88-char line limit)
- Add readOnlyHint: False to ha_call_addon_api annotations (required by test_tool_annotations)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: add ha_call_addon_api to README tool list
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: set destructiveHint to True for ha_call_addon_api
The test requires every tool to have at least one of readOnlyHint: True
or destructiveHint: True. Since ha_call_addon_api can make POST/PUT/DELETE
requests to add-on APIs, destructiveHint: True is correct.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: raise ToolError on failure + add unit tests for _call_addon_api
Address PR review feedback from julienld:
- Tool functions (ha_get_addon, ha_call_addon_api) now raise ToolError
via raise_tool_error() on failure instead of returning error dicts,
setting isError: true in the MCP protocol response.
- Add 9 unit tests covering _call_addon_api error paths: path traversal
rejection, addon not found, no Ingress support, addon not running,
missing ingress_entry, HTTP timeout, and connection errors.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: create Ingress session before proxying add-on API requests
The Supervisor's Ingress proxy validates requests using a session cookie,
not Bearer tokens. Without a valid session, all requests fail with
"No valid ingress session" and HTTP 500.
Fix: call POST /ingress/session via Supervisor WebSocket API to obtain
a session token, then pass it as the `ingress_session` cookie on the
HTTP request to the add-on's Ingress URL.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: use direct add-on communication instead of Ingress proxy
The Ingress proxy requires session cookies that are difficult to
obtain programmatically from within an add-on context. Instead,
communicate directly with the target add-on via its internal Docker
network IP and ingress_port (e.g., http://172.30.33.10:5000/...).
All add-ons on HA OS share the hassio Docker network, so direct
container-to-container communication works without authentication.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: add Ingress headers for direct add-on communication
Add X-Ingress-Path and X-Hass-Source headers when making direct
requests to add-on containers. Add-ons like Frigate and Node-RED
check for these headers to bypass their own auth layer, since the
Supervisor Ingress proxy normally sets them.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* debug: add opt-in diagnostic info to addon API responses
Add debug=true parameter to ha_call_addon_api that includes request
URL, sent headers, and response headers in the response. Off by
default to avoid wasting tokens.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: add Supervisor Ingress proxy fallback for IP-restricted add-ons
When direct Docker network access returns 403 (community add-ons with
IP-restricted Nginx), automatically retry through the Supervisor
Ingress proxy using SUPERVISOR_TOKEN. Also fixes ruff C416 lint error.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* debug: add fallback diagnostics to debug output
Return status message from Supervisor Ingress fallback so debug=true
shows why the fallback succeeded or failed.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: use WS-based Ingress session + HA Core proxy for fallback
The direct Supervisor API call was rejected (403) due to hassio_role
restrictions. Instead, create the Ingress session via the WebSocket
proxy (which already works for other Supervisor calls) and route
through HA Core's /api/hassio_ingress/ endpoint.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: use hassio/ingress_session WS command for session creation
The supervisor/api WS command rejects addon in data. Use the dedicated
hassio/ingress_session WS command instead, which is what HA Core's
frontend uses to create Ingress sessions.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: use HA Core REST API for Ingress session creation
hassio/ingress_session WS command doesn't exist. The HA frontend
uses POST /api/hassio/ingress/session REST endpoint instead.
Simpler too — no WS connection needed, just an HTTP call with
the existing Bearer token.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: try both Supervisor and HA Core fallback paths with diagnostics
When direct Docker access returns 403, try creating an Ingress session
via SUPERVISOR_TOKEN first, then fall back to HA Core REST API. Both
attempt results are reported in debug output for troubleshooting.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: remove dead Ingress session fallback code
The Ingress session approach doesn't work for community add-ons due to
IP-based Nginx restrictions + Supervisor auth limitations. Remove the
entire _try_ingress_proxy function, fallback logic, and os import.
The tool works for add-ons without IP restrictions (Frigate, EVCC) via
direct Docker network + X-Ingress-Path headers. Add-ons with IP-
restricted Nginx (Node-RED, Glances) need per-addon workarounds.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Update ha_call_addon_api description with troubleshooting guidance
Explain that community add-ons may need config changes (IP restrictions,
direct port access) and recommend debug=True for diagnosing issues.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Include addon config in 401/403 errors for LLM troubleshooting
When an add-on returns 401/403, the response now includes the add-on's
options, port mappings, and a suggestion to review settings. This helps
the LLM identify configuration fixes (e.g., leave_front_door_open)
without a separate ha_get_addon call.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Address review feedback: path traversal and JSON truncation
- URL-decode path before traversal check to catch %2e%2e%2f sequences
- Return structured error instead of broken truncated JSON for oversized responses
- Keep destructiveHint: True (tool can POST/PUT/DELETE to any add-on API)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add port parameter to ha_call_addon_api for direct port access
Community add-ons (Node-RED, ESPHome, etc.) use Nginx IP restrictions
on their ingress port that block direct container connections. The new
`port` parameter lets callers connect to the add-on's direct access
port instead (e.g., 1880 for Node-RED, 6052 for ESPHome), bypassing
the IP-restricted ingress port entirely.
Requires `leave_front_door_open` or equivalent to be enabled on the
add-on. When `port` is specified, Ingress headers are omitted since
the direct port doesn't use them.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add offset/limit pagination for large addon API responses
Array responses (e.g., Node-RED /flows, Frigate /api/events) can now
be paginated with offset and limit parameters, avoiding the 50KB
response size cap.
For large dict responses (e.g., evcc /api/state), the too-large error
now includes top-level keys with their types and sizes, helping callers
make more targeted API calls.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add WebSocket support to ha_call_addon_api
Add `websocket` and `wait_for_close` parameters to ha_call_addon_api
for connecting to addon WebSocket APIs (e.g., ESPHome compile/validate,
Node-RED comms, Z-Wave JS commands).
When websocket=true:
- Upgrades to WebSocket connection at ws://{addon_ip}:{port}/{path}
- Sends `body` as initial WebSocket message
- Collects response messages until server closes, timeout, or 50KB limit
- Strips ANSI escape codes from addon output
- Returns collected messages with metadata (count, close reason, duration)
When websocket=false (default): existing HTTP behavior unchanged.
`wait_for_close` controls collection strategy:
- true (default): wait for server to close or timeout (120s) — for
operations that stream until done (compile, validate, install)
- false: return after first batch of messages (2s silence) — for
quick commands or checking status
Security: mandatory timeout, 50KB response cap, 1000 message limit,
ANSI stripping, no persistent connections.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address idiot-check findings for WebSocket and port features
- Add missing websockets.exceptions.ConnectionClosed handler to outer
exception chain in _call_addon_ws (ws.send() could raise it before
reaching the inner recv loop's handler)
- Add missing Ingress support check to _call_addon_ws (matching the
HTTP path's validation)
- Fix ingress_port UnboundLocalError in HTTP 401/403 handler when
using port parameter (latent bug made reachable by port feature)
- Add unit tests for _call_addon_ws covering: path traversal, addon
not found, no Ingress support, port override bypass, addon not
running, handshake failure, connection closed during send, connection
error, message collection, ANSI stripping, binary frame skipping,
wait_for_close=false early return, missing network info
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: cap WS serialized output size, trim tool description, update README
Address idiot-check findings:
1. Cap serialized WS result size: raw byte collection undercounts due
to JSON + MCP protocol overhead (50KB raw -> 62KB serialized). Now
checks serialized size and returns RESPONSE_TOO_LARGE summary if
it exceeds the 50KB limit.
2. Fix duplicate step number comments in _call_addon_ws (5,5,6,7 -> 5,6,7,8).
3. Mark offset/limit param descriptions as "HTTP only" so LLMs don't
try to use them with websocket=true.
4. Trim tool docstring per progressive disclosure pattern — removed
verbose explanations, kept summary + examples. Details are
discoverable via ha_get_addon and error-driven guidance.
5. Shorten port, websocket, wait_for_close param descriptions.
6. Update README Add-ons row to mention WebSocket and direct port
access capabilities.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: kingpanther13 <kingpanther13@users.noreply.github.com>1 parent aa350c8 commit c5ecdde
3 files changed
Lines changed: 1423 additions & 190 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
129 | 129 | | |
130 | 130 | | |
131 | 131 | | |
132 | | - | |
| 132 | + | |
133 | 133 | | |
134 | 134 | | |
135 | 135 | | |
| |||
148 | 148 | | |
149 | 149 | | |
150 | 150 | | |
151 | | - | |
| 151 | + | |
152 | 152 | | |
153 | 153 | | |
154 | 154 | | |
| |||
0 commit comments