fix(mcp): hold three guarantees on every run, not only on the first

Home Assistant pinned the MCP service account to its read-only group when it created the account and never again, so an account promoted to administrator by hand kept every mutating tool that scope denies while the deploy stayed green, because nothing looked at the existing case. The group is now asserted on every run and a refusal aborts instead of passing.

Nextcloud minted an app password under a fixed managed name and never revoked the previous one, so every run that found the stored password rejected added another valid token while the old ones kept full account access, accumulating across deploys. The mint is now preceded by a listing and the revocation of every token carrying that name. Both new tasks mask their output, because the listing names the account and its token ids even though the JSON strips the secrets.

Prometheus ran with no query timeout, so its two-minute default applied: a query the adapter abandoned after fifteen seconds kept the server busy for the rest of it. The flag is now derived from the adapter's own limit and doubled, which gives the server room past the adapter's deadline while keeping the two from drifting apart.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
kevinveenbirkenbachandClaude Opus 5 committed 2026-08-29 00:52:21 +02:00
1 parent ce29195b64
commit 938426da01
4 files changed
+54

No files matched your search

@@ -163,6 +163,16 @@ async def ensure_service_account(admin_token):
user_id = created["result"]["user"]["id"]
if existing:
regrouped = await command(
{
"type": "config/auth/update",
"user_id": user_id,
"group_ids": [SERVICE_GROUP],
}
)
if not regrouped.get("success"):
raise SystemExit("MCP account group refused: " + json.dumps(regrouped))
changed = await command(
{
"type": "config/auth_provider/homeassistant/admin_change_password",
@@ -58,6 +58,47 @@
delegate_to: "{{ NEXTCLOUD_HOST_NODE }}"
run_once: true
- name: "🔍 List the app passwords the MCP account already holds"
ansible.builtin.command:
argv:
- container
- exec
- -u
- "{{ NEXTCLOUD_DOCKER_USER }}"
- "{{ NEXTCLOUD_LOCAL_CID }}" # nocheck: container-exec-resolver
- php
- "{{ NEXTCLOUD_INTERNAL_OCC_COMMAND }}"
- user:auth-tokens:list
- "{{ NEXTCLOUD_MCP_USERNAME }}"
- --output=json
register: nextcloud_mcp_tokens_held
changed_when: false
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
delegate_to: "{{ NEXTCLOUD_HOST_NODE }}"
run_once: true
- name: "🧹 Revoke the app passwords an earlier run minted under the managed name"
ansible.builtin.command:
argv:
- container
- exec
- -u
- "{{ NEXTCLOUD_DOCKER_USER }}"
- "{{ NEXTCLOUD_LOCAL_CID }}" # nocheck: container-exec-resolver
- php
- "{{ NEXTCLOUD_INTERNAL_OCC_COMMAND }}"
- user:auth-tokens:delete
- "{{ NEXTCLOUD_MCP_USERNAME }}"
- "{{ item | string }}"
loop: >-
{{ nextcloud_mcp_tokens_held.stdout | from_json
| selectattr('name', 'equalto', NEXTCLOUD_MCP_TOKEN_NAME)
| map(attribute='id') | list }}
changed_when: true
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
delegate_to: "{{ NEXTCLOUD_HOST_NODE }}"
run_once: true
- name: "🔑 Mint an app password for the MCP clients"
ansible.builtin.command:
argv:
@@ -11,6 +11,7 @@
command:
- "--config.file={{ PROMETHEUS_CONFIG_DOCKER }}"
- "--storage.tsdb.path={{ lookup('volume', 'web-app-prometheus', 'data').target }}"
- "--query.timeout={{ PROMETHEUS_QUERY_TIMEOUT }}"
- "--web.enable-lifecycle"
{{ lookup('container_volumes', service_name) | indent(4) }}
+2
View File
@@ -23,6 +23,8 @@ BLACKBOX_CONTAINER: "{{ lookup('config', application_id, 'services.blackbox-expo
CADVISOR_CONTAINER: "{{ lookup('config', application_id, 'services.cadvisor.name') }}"
NODE_EXPORTER_CONTAINER: "{{ lookup('config', application_id, 'services.node-exporter.name') }}"
PROMETHEUS_QUERY_TIMEOUT: "{{ (lookup('config', application_id, 'mcp.limits.timeout_seconds') | int * 2) ~ 's' }}"
PROMETHEUS_MCP_ENABLED: "{{ lookup('config', application_id, 'mcp.enabled') | bool }}"
PROMETHEUS_MCP_SERVICE_KEY: "prometheusmcp"
PROMETHEUS_MCP_CONTAINER: "{{ lookup('config', application_id, 'services.prometheusmcp.name') }}"