175 lines
6.4 KiB
YAML
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
|