mcp.ymlΒΆ

---
- name: "πŸ” Resolve the Nextcloud 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: "{{ NEXTCLOUD_SERVICE }}"
    resolve_output_prefix: NEXTCLOUD
    resolve_compose_cid: "{{ NEXTCLOUD_CONTAINER }}"

- name: "πŸ” Resolve the proxy 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: "{{ NEXTCLOUD_PROXY_SERVICE }}"
    resolve_output_prefix: NEXTCLOUD_PROXY
    resolve_compose_cid: "{{ NEXTCLOUD_PROXY_CONTAINER }}"

- name: "πŸ” Resolve the Context Agent 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: "{{ NEXTCLOUD_CONTEXT_AGENT_SERVICE }}"
    resolve_output_prefix: NEXTCLOUD_CONTEXT_AGENT
    resolve_compose_cid: "{{ NEXTCLOUD_CONTEXT_AGENT_CONTAINER }}"

- name: "⏳ Wait until the Context Agent answers its heartbeat"
  ansible.builtin.command:
    argv:
      - container
      - exec
      - "{{ NEXTCLOUD_CONTEXT_AGENT_LOCAL_CID }}" # nocheck: container-exec-resolver
      - python3
      - -c
      - "import urllib.request; urllib.request.urlopen('http://127.0.0.1:{{ NEXTCLOUD_CONTEXT_AGENT_PORT }}/heartbeat', timeout=5)"
  register: nextcloud_context_agent_heartbeat
  retries: 60
  delay: 5
  until: nextcloud_context_agent_heartbeat.rc == 0
  changed_when: false
  delegate_to: "{{ NEXTCLOUD_CONTEXT_AGENT_HOST_NODE }}"
  run_once: true

- name: "πŸš€ Register the Context Agent as an AppAPI ExApp"
  ansible.builtin.include_tasks:
    file: "{{ role_path }}/tasks/utils/mcp/register.yml"
    apply:
      delegate_to: "{{ NEXTCLOUD_HOST_NODE }}"
      run_once: true
  vars:
    nextcloud_exapp_renew: false

- name: "πŸ” Read the MCP app password this deployment already issued"
  ansible.builtin.set_fact:
    nextcloud_mcp_token: "{{ lookup('mcp_credential', application_id) }}"
  no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"

- name: "🩺 Check the stored app password against the MCP endpoint"
  ansible.builtin.include_tasks: "{{ role_path }}/tasks/utils/mcp/probe.yml"
  when: nextcloud_mcp_token | length > 0

- name: "πŸ”‘ Mint and persist a fresh MCP app password"
  ansible.builtin.include_tasks: "{{ role_path }}/tasks/utils/mcp/token.yml"
  when: nextcloud_mcp_token | length == 0 or nextcloud_mcp_probe.stdout | default('') != '200'

- name: "🩺 Re-check the freshly minted app password"
  ansible.builtin.include_tasks: "{{ role_path }}/tasks/utils/mcp/probe.yml"
  when: nextcloud_mcp_probe is not defined or nextcloud_mcp_probe.stdout != '200'

- name: "♻️ Re-register the ExApp a rotated app secret left behind"
  ansible.builtin.include_tasks:
    file: "{{ role_path }}/tasks/utils/mcp/register.yml"
    apply:
      delegate_to: "{{ NEXTCLOUD_HOST_NODE }}"
      run_once: true
  vars:
    nextcloud_exapp_renew: true
  when: nextcloud_mcp_probe.stdout != '200'

- name: "🩺 Re-check the endpoint behind the re-registered ExApp"
  ansible.builtin.include_tasks: "{{ role_path }}/tasks/utils/mcp/probe.yml"
  when: nextcloud_mcp_probe.stdout != '200'

- name: "πŸ›‘ Refuse an MCP endpoint that rejects the stored app password"
  ansible.builtin.assert:
    that:
      - nextcloud_mcp_probe.stdout == '200'
    fail_msg: >-
      The MCP endpoint {{ NEXTCLOUD_MCP_PATH }} answered
      {{ nextcloud_mcp_probe.stdout | default('no status') }} for the app password stored
      for {{ application_id }}, so every MCP client would be configured with a
      bearer the endpoint rejects.

- name: "πŸ”‘ Hand the minted app password to the adapter"
  ansible.builtin.include_tasks: "{{ lookup('path_absolute', 'roles/svc-ai-mcp-adapter/tasks/upstream_key.yml') }}"
  vars:
    mcp_upstream_key_path: "{{ NEXTCLOUD_MCP_ADAPTER_UPSTREAM_ENV }}"
    mcp_upstream_key: "{{ lookup('mcp_credential', application_id) }}"

- name: "🧱 Rebuild the MCP adapter of {{ application_id }} now the credential exists"
  ansible.builtin.include_tasks: "{{ lookup('path_absolute', 'roles/svc-ai-mcp-adapter/tasks/rebuild.yml') }}"

- name: "🚿 Flush compose handlers for {{ application_id }}"
  ansible.builtin.meta: flush_handlers