mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 10:59:59 +00:00
Every MCP role keeps its task tree under tasks/utils/ without numeric
prefixes, so a caller can reach any of them through include_role
tasks_from. utils/roles/mapping.py holds that path as the single source.
Ansible resolves a relative include against the including file's own
directory when that file was reached by path, but against tasks/ when it
was reached through tasks_from. The same file therefore loaded from its
own role and failed from a caller using tasks_from. Every nested include
now names {{ role_path }}/tasks/utils/..., which holds under both bases,
and a lint walks each tasks_from entrypoint transitively to keep it so.
The exec-gating lint learns the same form, otherwise it loses the include
chain and reports inherited IS_STACK_HOST gates as missing.
The expression resolving a provider's stored token appeared eighteen
times. plugins/lookup/mcp_credential.py replaces it, and mcp_probe_owner
disappears from eight roles: it was only ever a step towards the bearer
and no probe read it.
The Hermes and OpenClaw probes stop aborting the play while it is still
running. Roles deploy in dependency order, so a client probing during its
own role can run before a provider exists at all, and failing there
prevents that provider from ever deploying. The verdict moves to the
reconciliation pass, gated on MCP_RECONCILE_STRICT, which the reconciler
sets and group_vars declares so the readers need no default.
The sys-svc-mcp-reconcile role is added here; wiring it into a stage is a
separate change.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
67 lines
2.5 KiB
YAML
67 lines
2.5 KiB
YAML
---
|
|
- name: "🩺 Mattermost | Check whether the stored token is still active"
|
|
database_query:
|
|
config: "{{ lookup('database', application_id) }}"
|
|
query_file: "{{ role_path }}/files/sql/select/user/access-token.sql"
|
|
named_args:
|
|
token: "{{ mattermost_mcp_token }}"
|
|
expect_rows: true
|
|
mask_values: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
|
|
register: mattermost_mcp_token_state
|
|
when: mattermost_mcp_token | length > 0
|
|
changed_when: false
|
|
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
|
|
delegate_to: "{{ STACK_HOST }}"
|
|
run_once: true
|
|
|
|
- name: "🔑 Mattermost | Mint a personal access token for MCP clients"
|
|
when: mattermost_mcp_token | length == 0 or (mattermost_mcp_token_state.rows[0][0] | default('0') | trim) != '1'
|
|
block:
|
|
- name: "🔑 Mattermost | Ask the server for a personal access token"
|
|
ansible.builtin.command:
|
|
argv:
|
|
- container
|
|
- exec
|
|
- -i
|
|
- "{{ MATTERMOST_MCP_LOCAL_CID }}" # nocheck: container-exec-resolver
|
|
- /mattermost/bin/mmctl
|
|
- --local
|
|
- --json
|
|
- --suppress-warnings
|
|
- token
|
|
- generate
|
|
- "{{ lookup('users', 'administrator').username }}"
|
|
- "{{ MATTERMOST_MCP_TOKEN_DESCRIPTION }}"
|
|
register: mattermost_mcp_token_mint
|
|
retries: 30
|
|
delay: 10
|
|
until: mattermost_mcp_token_mint.rc == 0
|
|
changed_when: mattermost_mcp_token_mint.rc == 0
|
|
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
|
|
delegate_to: "{{ MATTERMOST_MCP_HOST_NODE }}"
|
|
run_once: true
|
|
|
|
- name: "🔧 Mattermost | Take the minted token from the mmctl payload"
|
|
ansible.builtin.set_fact:
|
|
mattermost_mcp_token: "{{ (mattermost_mcp_token_mint.stdout | from_json | first).token | trim }}"
|
|
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
|
|
run_once: true
|
|
|
|
- name: "🛑 Refuse a server that issued no usable token"
|
|
ansible.builtin.assert:
|
|
that:
|
|
- mattermost_mcp_token | length >= 26
|
|
fail_msg: >-
|
|
Mattermost returned no personal access token, so every MCP client would
|
|
be configured with an empty bearer and be rejected at first use.
|
|
|
|
- name: "💾 Persist the token so MCP clients can read it"
|
|
ansible.builtin.include_role:
|
|
name: sys-token-store
|
|
tasks_from: write.yml
|
|
vars:
|
|
sys_token_store_user_key: "{{ lookup('config', application_id, 'mcp.credential.owner') }}"
|
|
sys_token_store_app: "{{ application_id }}"
|
|
sys_token_store_token: "{{ mattermost_mcp_token }}"
|
|
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
|