Skip to content

Commit 58b2b09

Browse files
committed
Merge branch 'pr-1421' into addon-repo
2 parents 18a8aef + 7113760 commit 58b2b09

31 files changed

Lines changed: 1841 additions & 9 deletions

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,7 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
312312
- **[FastMCP](https://github.com/jlowin/fastmcp)**: Excellent MCP server framework
313313
- **[Model Context Protocol](https://modelcontextprotocol.io/)**: Standardized AI-application communication
314314
- **[Claude Code](https://github.com/anthropics/claude-code)**: AI-powered coding assistant
315+
- **[PolicyLayer](https://policylayer.com/)**: Argument-path predicate DSL shape (`args.domain in [...]` with `eq`/`in`/`regex`/`contains`/`exists`/...) inspired the per-tool approval rule schema (#966).
315316

316317
## 👥 Contributors
317318

@@ -360,6 +361,7 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
360361
- **[@drseanwing](https://github.com/drseanwing)** — Progress emission via FastMCP `Context` in long-running tools (#1124); tool-discovery / categorized-search docs (#1123).
361362
- **[@fnordpig](https://github.com/fnordpig)** — Config subentry support (#1393) and Assist pipeline management tool (#1392).
362363
- **[@paul43210](https://github.com/paul43210)**`array_patch` mode in `ha_manage_addon` for atomic GET-modify-POST (#1063).
364+
- **[@L1AD](https://github.com/L1AD)** — Filed #966 proposing per-tool approval gating; pointed to PolicyLayer's MCP-security work as prior art that inspired the predicate DSL shape.
363365

364366
---
365367

homeassistant-addon-dev/DOCS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ The dev add-on uses the same configuration as the stable version. See the main a
1515
| `backup_hint` | Backup strength preference | `normal` |
1616
| `secret_path` | Custom secret path (optional) | auto-generated |
1717
| `enable_tool_search` | Replace full tool catalog with search-based discovery (~46K → ~5K tokens). ⚠️ Do NOT enable for Claude Sonnet/Opus — their built-in tool search conflicts with ha-mcp's. Disable one or the other. | `false` |
18+
| `enable_per_tool_approval` *(beta)* | Gate high-stakes tool calls (lock/alarm control, automation writes, etc.) behind user approval. Guarded calls return an approval URL the user clicks to allow. Per-tool rules with optional argument predicates are configured in the Policies tab of the web UI. | `false` |
1819
| `enable_yaml_config_editing` *(beta)* | Enables `ha_config_set_yaml` for editing `configuration.yaml` directly. Requires `ha_mcp_tools` custom component. | `false` |
1920
| `enable_filesystem_tools` *(beta)* | Enables file read/write tools (`ha_list_files`, `ha_read_file`, `ha_write_file`, `ha_delete_file`). Requires `ha_mcp_tools` custom component. | `false` |
2021
| `enable_custom_component_integration` *(beta)* | Enables `ha_install_mcp_tools` installer tool for the `ha_mcp_tools` custom component. | `false` |

homeassistant-addon-dev/config.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ image: "ghcr.io/homeassistant-ai/ha-mcp-addon-dev-{arch}"
2424
options:
2525
backup_hint: "normal"
2626
enable_tool_search: false
27+
enable_per_tool_approval: false
2728
enable_yaml_config_editing: false
2829
enable_code_mode: false
2930
enable_lite_docstrings: false
@@ -38,6 +39,7 @@ schema:
3839
backup_hint: list(strong|normal|weak|auto)
3940
secret_path: str?
4041
enable_tool_search: bool?
42+
enable_per_tool_approval: bool?
4143
enable_yaml_config_editing: bool?
4244
enable_code_mode: bool?
4345
enable_lite_docstrings: bool?

homeassistant-addon-dev/translations/en.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,15 @@ configuration:
2828
OpenAI-compatible models) or with smaller context windows. Tools
2929
are found via ha_search_tools and executed via categorized proxies
3030
(read/write/delete). Requires restart to take effect.
31+
enable_per_tool_approval:
32+
name: Enable per-tool approval (advanced, beta)
33+
description: >-
34+
Gate high-stakes tool calls (lock/alarm control, automation writes,
35+
etc.) behind user approval. When a guarded tool is called, the agent
36+
receives an approval URL; the user must click Approve in the
37+
Policies tab of the web UI before the call proceeds. Per-tool rules
38+
with optional argument predicates are configured in the Policies
39+
tab. Off by default. Requires restart to take effect.
3140
enable_yaml_config_editing:
3241
name: Enable YAML config editing (beta)
3342
description: >-

homeassistant-addon/DOCS.md

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -272,13 +272,33 @@ Replaces the full tool catalog (~88 tools, ~46K tokens) with search-based discov
272272

273273
Requires add-on restart to take effect.
274274

275+
### enable_per_tool_approval
276+
277+
**Default:** `false`
278+
279+
Gates high-stakes tool calls (lock/alarm control, automation writes, etc.) behind explicit user approval. When a guarded tool is called, the agent receives an approval URL and the call is held until the user clicks **Approve** in the **Policies** tab of the web UI. Per-tool rules — with optional argument predicates — are configured from the same Policies tab.
280+
281+
**When to enable:**
282+
- Shared installations where you want a human in the loop for destructive or security-relevant operations
283+
- Locks, alarms, and other entities where an LLM mistake has real-world consequences
284+
- Whenever you want a hard audit trail of which tool calls were explicitly allowed
285+
286+
**When to leave disabled (default):**
287+
- Single-user setups where you're comfortable with the LLM acting autonomously
288+
- You haven't configured any policy rules yet (with no rules, the toggle has no effect — but the runtime cost is small either way)
289+
290+
The policy engine is in beta. Off by default. Requires add-on restart to take effect.
291+
275292
**Example Configuration:**
276293

277294
```yaml
278-
backup_hint: normal
279-
secret_path: "" # Leave empty for auto-generation
295+
enable_per_tool_approval: true
280296
```
281297

298+
Per-tool rules (including argument predicates like `args.domain in ['lock', 'alarm_control_panel']`) are configured from the **Policies** tab in the web UI, not from `config.yaml`.
299+
300+
*Inspired by [PolicyLayer](https://policylayer.com/)'s policy DSL shape, originally proposed in [#966](https://github.com/homeassistant-ai/ha-mcp/issues/966) by [@L1AD](https://github.com/L1AD).*
301+
282302
---
283303

284304
## Security

homeassistant-addon/config.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ image: "ghcr.io/homeassistant-ai/ha-mcp-addon-{arch}"
3232
options:
3333
backup_hint: "normal"
3434
enable_tool_search: false
35+
enable_per_tool_approval: false
3536
enable_auto_backup: true
3637
auto_backup_throttle_minutes: 0
3738
auto_backup_retain_per_entity: 100
@@ -40,6 +41,7 @@ schema:
4041
backup_hint: list(strong|normal|weak|auto)
4142
secret_path: str?
4243
enable_tool_search: bool?
44+
enable_per_tool_approval: bool?
4345
enable_auto_backup: bool?
4446
auto_backup_throttle_minutes: int(0,1440)?
4547
auto_backup_retain_per_entity: int(1,10000)?

homeassistant-addon/start.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,7 @@ def main() -> int:
219219
backup_hint = "normal" # default
220220
custom_secret_path = "" # default
221221
enable_tool_search = False # default
222+
enable_per_tool_approval = False # default
222223
enable_yaml_config_editing = False # default
223224
enable_filesystem_tools = False # default
224225
enable_custom_component_integration = False # default
@@ -245,6 +246,12 @@ def main() -> int:
245246
enable_tool_search = (
246247
raw_tool_search if isinstance(raw_tool_search, bool) else False
247248
)
249+
raw_per_tool_approval = config.get("enable_per_tool_approval", False)
250+
enable_per_tool_approval = (
251+
raw_per_tool_approval
252+
if isinstance(raw_per_tool_approval, bool)
253+
else False
254+
)
248255
raw_yaml_config = config.get("enable_yaml_config_editing", False)
249256
enable_yaml_config_editing = (
250257
raw_yaml_config if isinstance(raw_yaml_config, bool) else False
@@ -322,6 +329,7 @@ def main() -> int:
322329
os.environ["HOMEASSISTANT_URL"] = "http://supervisor/core"
323330
os.environ["BACKUP_HINT"] = backup_hint
324331
os.environ["ENABLE_TOOL_SEARCH"] = str(enable_tool_search).lower()
332+
os.environ["ENABLE_PER_TOOL_APPROVAL"] = str(enable_per_tool_approval).lower()
325333
os.environ["ENABLE_YAML_CONFIG_EDITING"] = str(enable_yaml_config_editing).lower()
326334
os.environ["HAMCP_ENABLE_FILESYSTEM_TOOLS"] = str(enable_filesystem_tools).lower()
327335
os.environ["HAMCP_ENABLE_CUSTOM_COMPONENT_INTEGRATION"] = str(

homeassistant-addon/translations/en.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,15 @@ configuration:
2828
OpenAI-compatible models) or with smaller context windows. Tools
2929
are found via ha_search_tools and executed via categorized proxies
3030
(read/write/delete). Requires restart to take effect.
31+
enable_per_tool_approval:
32+
name: Enable per-tool approval (advanced, beta)
33+
description: >-
34+
Gate high-stakes tool calls (lock/alarm control, automation writes,
35+
etc.) behind user approval. When a guarded tool is called, the agent
36+
receives an approval URL; the user must click Approve in the
37+
Policies tab of the web UI before the call proceeds. Per-tool rules
38+
with optional argument predicates are configured in the Policies
39+
tab. Off by default. Requires restart to take effect.
3140
enable_auto_backup:
3241
name: Enable auto-backup of edits
3342
description: >-

src/ha_mcp/config.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,11 @@ class Settings(BaseSettings):
100100
# Dramatically reduces idle context token usage for LLMs.
101101
enable_tool_search: bool = Field(False, alias="ENABLE_TOOL_SEARCH")
102102

103+
# Per-tool approval middleware — opt-in gate that routes high-stakes tool
104+
# calls through a per-tool policy with out-of-band web-UI approval
105+
# (issue #966). Disabled by default.
106+
enable_per_tool_approval: bool = Field(False, alias="ENABLE_PER_TOOL_APPROVAL")
107+
103108
# Managed YAML config editing — allows ha_config_set_yaml to add,
104109
# replace, or remove top-level keys in configuration.yaml and package
105110
# files. Disabled by default; only for YAML-only features with no UI/API path.

src/ha_mcp/policy/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""Per-tool approval policy for high-stakes MCP tool calls."""

0 commit comments

Comments
 (0)