---
- name: "π Resolve {{ application_id }} hosting node + container id"
ansible.builtin.include_tasks: "{{ lookup('path_absolute', 'roles/sys-svc-compose/tasks/utils/swarm/resolve_host_cid.yml') }}"
vars:
resolve_service_key: "{{ entity_name }}"
resolve_output_prefix: WORDPRESS_MCP
resolve_compose_cid: "{{ WORDPRESS_CONTAINER_ADDRESS }}"
- name: "π Read the MCP application password this site already issued"
ansible.builtin.set_fact:
wordpress_mcp_token: "{{ lookup('mcp_credential', application_id) }}"
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
- name: "π List the accounts this site knows"
ansible.builtin.shell: | # nocheck: container-exec-resolver runtime-resolved CID, delegated to the hosting node
container exec -u {{ WORDPRESS_USER }} {{ WORDPRESS_MCP_LOCAL_CID | quote }} \
wp user list --field=user_login --path={{ WORDPRESS_DOCKER_HTML_PATH | quote }}
register: wordpress_mcp_users
changed_when: false
delegate_to: "{{ WORDPRESS_MCP_HOST_NODE }}"
run_once: true
- name: "π€ Create the subscriber the MCP adapter authenticates as"
ansible.builtin.shell: | # nocheck: container-exec-resolver runtime-resolved CID, delegated to the hosting node
container exec -u {{ WORDPRESS_USER }} -e WP_MCP_USER_PASS {{ WORDPRESS_MCP_LOCAL_CID | quote }} \
sh -c 'exec wp user create "$@" --user_pass="$WP_MCP_USER_PASS"' _ \
{{ WORDPRESS_MCP_USERNAME | quote }} {{ WORDPRESS_MCP_EMAIL | quote }} \
--role={{ WORDPRESS_MCP_ROLE | quote }} \
--path={{ WORDPRESS_DOCKER_HTML_PATH | quote }}
environment:
WP_MCP_USER_PASS: "{{ WORDPRESS_MCP_USER_PASSWORD }}"
when: WORDPRESS_MCP_USERNAME not in wordpress_mcp_users.stdout_lines
changed_when: true
delegate_to: "{{ WORDPRESS_MCP_HOST_NODE }}"
run_once: true
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
- name: "π List the application passwords that account holds"
ansible.builtin.shell: | # nocheck: container-exec-resolver runtime-resolved CID, delegated to the hosting node
container exec -u {{ WORDPRESS_USER }} {{ WORDPRESS_MCP_LOCAL_CID | quote }} \
wp user application-password list {{ WORDPRESS_MCP_USERNAME | quote }} \
--fields=uuid,name --format=json --path={{ WORDPRESS_DOCKER_HTML_PATH | quote }}
register: wordpress_mcp_app_passwords
changed_when: false
delegate_to: "{{ WORDPRESS_MCP_HOST_NODE }}"
run_once: true
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
- name: "π§ Take the uuids wp-cli deletes that password by"
ansible.builtin.set_fact:
wordpress_mcp_credential_uuids: >-
{{ wordpress_mcp_app_passwords.stdout | from_json
| selectattr('name', 'equalto', WORDPRESS_MCP_APP_PASSWORD_NAME)
| map(attribute='uuid') | list }}
run_once: true
- name: "π Issue the application password MCP clients present"
when: >-
wordpress_mcp_token | length == 0
or wordpress_mcp_credential_uuids | length == 0
block:
- name: "π§Ή Drop the application password the store cannot read back"
ansible.builtin.shell: | # nocheck: container-exec-resolver runtime-resolved CID, delegated to the hosting node
container exec -u {{ WORDPRESS_USER }} {{ WORDPRESS_MCP_LOCAL_CID | quote }} \
wp user application-password delete {{ WORDPRESS_MCP_USERNAME | quote }} \
{{ wordpress_mcp_credential_uuids | map('quote') | join(' ') }} \
--path={{ WORDPRESS_DOCKER_HTML_PATH | quote }}
when: wordpress_mcp_credential_uuids | length > 0
changed_when: true
delegate_to: "{{ WORDPRESS_MCP_HOST_NODE }}"
run_once: true
- name: "π Mint the application password without its display spaces"
ansible.builtin.shell: | # nocheck: container-exec-resolver runtime-resolved CID, delegated to the hosting node
container exec -u {{ WORDPRESS_USER }} {{ WORDPRESS_MCP_LOCAL_CID | quote }} \
wp user application-password create {{ WORDPRESS_MCP_USERNAME | quote }} \
{{ WORDPRESS_MCP_APP_PASSWORD_NAME | quote }} --porcelain --path={{ WORDPRESS_DOCKER_HTML_PATH | quote }}
register: wordpress_mcp_mint
changed_when: true
delegate_to: "{{ WORDPRESS_MCP_HOST_NODE }}"
run_once: true
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
- name: "π§ Take the minted password from the wp-cli output"
ansible.builtin.set_fact:
wordpress_mcp_token: "{{ wordpress_mcp_mint.stdout_lines | last | trim | replace(' ', '') }}"
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
run_once: true
- name: "π Refuse a site that issued no usable application password"
ansible.builtin.assert:
that:
- wordpress_mcp_token | length >= 24
fail_msg: >-
WordPress issued no application password for {{ WORDPRESS_MCP_USERNAME }},
so every MCP client would be configured with an empty credential and the
reconciliation stage would refuse to render this provider.
- name: "πΎ Persist the password 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: "{{ wordpress_mcp_token }}"
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"