homelab/ansible/ansible-old/playbooks/ai/deploy_ansible_mcp_watchtower.yml

175 lines
6.4 KiB
YAML

---
# Deploy a custom Ansible MCP server on Watchtower.
#
# Usage:
# cd /home/chester/homelab/ansible
# ansible-playbook -i inventory/hosts.ini playbooks/ai/deploy_ansible_mcp_watchtower.yml
#
# Validate only:
# ansible-playbook -i inventory/hosts.ini playbooks/ai/deploy_ansible_mcp_watchtower.yml --check
- name: Deploy Ansible MCP server on Watchtower
hosts: watchtower
become: true
gather_facts: true
vars:
mcp_service_name: ansible-mcp
mcp_install_dir: /opt/ansible-mcp
mcp_state_dir: /var/lib/ansible-mcp
mcp_user: chester
mcp_group: chester
mcp_transport: streamable-http
mcp_host: 0.0.0.0
mcp_port: 8449
mcp_repo_root: /home/chester/homelab/ansible
mcp_inventory: inventory/hosts.ini
mcp_allowed_playbook_dirs: playbooks
mcp_allowed_playbooks: ""
mcp_api_token: "{{ lookup('env', 'ANSIBLE_MCP_API_TOKEN') | default('', true) }}"
mcp_max_extra_vars_bytes: 16384
mcp_blocked_extra_vars_keys: "ansible_password,ansible_become_password,vault_password"
# Full-write mode is enabled by default here to match requested behavior.
# Keep confirm enforcement enabled in server guardrails.
mcp_allow_write: true
mcp_require_confirm_for_write: true
mcp_default_timeout: 900
mcp_max_timeout: 3600
mcp_python_packages:
- ansible-core>=2.16,<2.19
- mcp>=1.0.0
tasks:
- name: Assert API token is configured for HTTP transport
ansible.builtin.assert:
that:
- mcp_transport == "stdio" or (mcp_api_token | length) > 0
fail_msg: >-
HTTP transport requires ANSIBLE_MCP_API_TOKEN to be set in the control
shell environment before running this playbook.
success_msg: "Transport/auth configuration validated."
- name: Assert service account exists
ansible.builtin.getent:
database: passwd
key: "{{ mcp_user }}"
- name: Ensure installation and state directories exist
ansible.builtin.file:
path: "{{ item.path }}"
state: directory
owner: "{{ item.owner }}"
group: "{{ item.group }}"
mode: "{{ item.mode }}"
loop:
- { path: "{{ mcp_install_dir }}", owner: "{{ mcp_user }}", group: "{{ mcp_group }}", mode: "0755" }
- { path: "{{ mcp_state_dir }}", owner: "{{ mcp_user }}", group: "{{ mcp_group }}", mode: "0750" }
- name: Copy MCP server script
ansible.builtin.copy:
src: ../../scripts/ansible_mcp_server.py
dest: "{{ mcp_install_dir }}/ansible_mcp_server.py"
owner: "{{ mcp_user }}"
group: "{{ mcp_group }}"
mode: "0755"
notify: Restart ansible mcp service
- name: Ensure Python venv exists
ansible.builtin.command: "python3 -m venv {{ mcp_install_dir }}/.venv"
args:
creates: "{{ mcp_install_dir }}/.venv/bin/python"
changed_when: false
- name: Install MCP server dependencies in venv
ansible.builtin.pip:
name: "{{ mcp_python_packages }}"
virtualenv: "{{ mcp_install_dir }}/.venv"
state: present
notify: Restart ansible mcp service
- name: Install systemd unit for ansible mcp service
ansible.builtin.copy:
dest: "/etc/systemd/system/{{ mcp_service_name }}.service"
owner: root
group: root
mode: "0644"
content: |
[Unit]
Description=Ansible MCP Server
Wants=network-online.target
After=network-online.target
[Service]
Type=simple
User={{ mcp_user }}
Group={{ mcp_group }}
WorkingDirectory={{ mcp_repo_root }}
Environment=ANSIBLE_MCP_REPO_ROOT={{ mcp_repo_root }}
Environment=ANSIBLE_MCP_INVENTORY={{ mcp_inventory }}
Environment=ANSIBLE_MCP_ALLOWED_PLAYBOOK_DIRS={{ mcp_allowed_playbook_dirs }}
Environment=ANSIBLE_MCP_ALLOWED_PLAYBOOKS={{ mcp_allowed_playbooks }}
Environment=ANSIBLE_MCP_API_TOKEN={{ mcp_api_token }}
Environment=ANSIBLE_MCP_ALLOW_WRITE={{ mcp_allow_write | ternary('true', 'false') }}
Environment=ANSIBLE_MCP_REQUIRE_CONFIRM={{ mcp_require_confirm_for_write | ternary('true', 'false') }}
Environment=ANSIBLE_MCP_DEFAULT_TIMEOUT={{ mcp_default_timeout }}
Environment=ANSIBLE_MCP_MAX_TIMEOUT={{ mcp_max_timeout }}
Environment=ANSIBLE_MCP_MAX_EXTRA_VARS_BYTES={{ mcp_max_extra_vars_bytes }}
Environment=ANSIBLE_MCP_BLOCKED_EXTRA_VARS_KEYS={{ mcp_blocked_extra_vars_keys }}
Environment=ANSIBLE_MCP_STATE_DIR={{ mcp_state_dir }}
Environment=ANSIBLE_MCP_TRANSPORT={{ mcp_transport }}
Environment=ANSIBLE_MCP_HOST={{ mcp_host }}
Environment=ANSIBLE_MCP_PORT={{ mcp_port }}
ExecStart={{ mcp_install_dir }}/.venv/bin/python {{ mcp_install_dir }}/ansible_mcp_server.py --transport {{ mcp_transport }} --host {{ mcp_host }} --port {{ mcp_port }}
Restart=on-failure
RestartSec=3
[Install]
WantedBy=multi-user.target
notify:
- Reload systemd
- Restart ansible mcp service
- name: Ensure ansible mcp service is enabled and running
ansible.builtin.systemd:
name: "{{ mcp_service_name }}"
enabled: true
state: started
- name: Verify MCP health endpoint
ansible.builtin.uri:
url: "http://127.0.0.1:{{ mcp_port }}"
method: GET
return_content: true
status_code: 200
changed_when: false
register: _mcp_http_probe
failed_when: false
- name: Show deployment summary
ansible.builtin.debug:
msg:
- "Ansible MCP deployed to watchtower"
- "Service: {{ mcp_service_name }}"
- "Transport: {{ mcp_transport }}"
- "Endpoint: {{ mcp_host }}:{{ mcp_port }}"
- "Repo root: {{ mcp_repo_root }}"
- "Allow write: {{ mcp_allow_write }}"
- "Auth enabled: {{ (mcp_api_token | length) > 0 }}"
- "Require confirm for write: {{ mcp_require_confirm_for_write }}"
- "Explicit playbook allowlist set: {{ (mcp_allowed_playbooks | length) > 0 }}"
- "HTTP probe status: {{ _mcp_http_probe.status | default('n/a') }}"
handlers:
- name: Reload systemd
ansible.builtin.systemd:
daemon_reload: true
- name: Restart ansible mcp service
ansible.builtin.systemd:
name: "{{ mcp_service_name }}"
state: restarted