--- # ============================================================================= # FreeCAD Robust MCP Server — Ansible Deployment Playbook # ============================================================================= # Deploys the FreeCAD MCP Server to Caliban as a systemd service. # # The server runs in xmlrpc mode with HTTP transport, exposing the MCP # protocol over streamable-http for consumption by MCP Switchboard on Oberon. # # Pattern: venv + pip install from PyPI (matches Kernos deployment) # # Usage: # ansible-playbook freecad_mcp/deploy.yml # # Required host_vars (caliban.incus.yml): # freecad_mcp_user: harper # freecad_mcp_group: harper # freecad_mcp_directory: /srv/freecad-mcp # freecad_mcp_port: 22032 # freecad_mcp_version: "0.5.0" # PyPI version to pin # # Optional host_vars: # freecad_mcp_host: "0.0.0.0" # freecad_mcp_log_level: INFO # freecad_mcp_mode: xmlrpc # freecad_mcp_xmlrpc_host: "localhost" # freecad_mcp_xmlrpc_port: 9875 # freecad_mcp_timeout_ms: 30000 # ============================================================================= - name: Deploy FreeCAD Robust MCP Server hosts: freecad_mcp vars: ansible_common_remote_group: "{{ freecad_mcp_group }}" allow_world_readable_tmpfiles: true tasks: # ========================================================================= # User & Directory Setup # ========================================================================= - name: Create FreeCAD MCP group become: true ansible.builtin.group: name: "{{ freecad_mcp_group }}" state: present - name: Create FreeCAD MCP user become: true ansible.builtin.user: name: "{{ freecad_mcp_user }}" group: "{{ freecad_mcp_group }}" home: "/home/{{ freecad_mcp_user }}" shell: /bin/bash system: false create_home: true - name: Add keeper_user to FreeCAD MCP group become: true ansible.builtin.user: name: "{{ keeper_user }}" groups: "{{ freecad_mcp_group }}" append: true - name: Add principal_user to FreeCAD MCP group become: true ansible.builtin.user: name: "{{ principal_user }}" groups: "{{ freecad_mcp_group }}" append: true - name: Add freecad_mcp_user to principal_user's primary group become: true ansible.builtin.user: name: "{{ freecad_mcp_user }}" groups: "{{ principal_user }}" append: true - name: Reset connection to pick up new group membership ansible.builtin.meta: reset_connection - name: Create application directory become: true ansible.builtin.file: path: "{{ freecad_mcp_directory }}" owner: "{{ freecad_mcp_user }}" group: "{{ freecad_mcp_group }}" state: directory mode: '750' # ========================================================================= # System Dependencies # ========================================================================= - name: Ensure Python 3, venv, dev headers, and FreeCAD are installed become: true ansible.builtin.apt: name: [python3, python3-venv, python3-dev, freecad] state: present update_cache: true # ========================================================================= # Virtual Environment & Package Installation # ========================================================================= - name: Create virtual environment become: true become_user: "{{ freecad_mcp_user }}" ansible.builtin.command: cmd: "python3 -m venv {{ freecad_mcp_directory }}/.venv/" creates: "{{ freecad_mcp_directory }}/.venv/bin/activate" - name: Install wheel in virtual environment become: true become_user: "{{ freecad_mcp_user }}" ansible.builtin.pip: name: - wheel state: latest virtualenv: "{{ freecad_mcp_directory }}/.venv" - name: Install freecad-robust-mcp from PyPI become: true become_user: "{{ freecad_mcp_user }}" ansible.builtin.pip: name: - "freecad-robust-mcp=={{ freecad_mcp_version }}" virtualenv: "{{ freecad_mcp_directory }}/.venv" virtualenv_command: python3 -m venv notify: restart freecad-mcp # ========================================================================= # Configuration # ========================================================================= - name: Template FreeCAD MCP .env configuration become: true ansible.builtin.template: src: .env.j2 dest: "{{ freecad_mcp_directory }}/.env" owner: "{{ freecad_mcp_user }}" group: "{{ freecad_mcp_group }}" mode: '640' notify: restart freecad-mcp # ========================================================================= # Systemd Service # ========================================================================= - name: Template systemd service file become: true ansible.builtin.template: src: freecad-mcp.service.j2 dest: /etc/systemd/system/freecad-mcp.service owner: root group: root mode: '644' notify: restart freecad-mcp - name: Enable and start freecad-mcp service become: true ansible.builtin.systemd: name: freecad-mcp enabled: true state: started daemon_reload: true # ========================================================================= # Validation # ========================================================================= - name: Flush handlers to restart service before validation ansible.builtin.meta: flush_handlers - name: Wait for FreeCAD MCP server to start ansible.builtin.wait_for: port: "{{ freecad_mcp_port }}" host: localhost delay: 3 timeout: 30 - name: Validate FreeCAD MCP HTTP endpoint is responding ansible.builtin.uri: url: "http://localhost:{{ freecad_mcp_port }}/mcp" method: POST body_format: json body: jsonrpc: "2.0" method: "initialize" id: 1 params: protocolVersion: "2025-03-26" capabilities: {} clientInfo: name: "ansible-healthcheck" version: "1.0.0" status_code: [200] return_content: true register: mcp_check retries: 5 delay: 5 until: mcp_check.status == 200 - name: Display server info ansible.builtin.debug: msg: >- FreeCAD MCP Server deployed successfully on caliban.incus:{{ freecad_mcp_port }} # ========================================================================= # Handlers # ========================================================================= handlers: - name: restart freecad-mcp become: true ansible.builtin.systemd: name: freecad-mcp state: restarted