- Drop `FREECAD_MCP_` prefix from env vars (use `FREECAD_*`) - Update freecad_mcp port from 22032 to 22061 - Document that FreeCAD bridge is required for tool calls - Replace kottos deployment with pallas deployment
318 lines
9.6 KiB
YAML
318 lines
9.6 KiB
YAML
---
|
|
# =============================================================================
|
|
# FreeCAD Robust MCP Server — Ansible Deployment Playbook
|
|
# =============================================================================
|
|
# Deploys the FreeCAD MCP Server to Larissa 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 Triton.
|
|
#
|
|
# Pattern: venv + pip install from GitHub fork (fastmcp>=3.2.0)
|
|
#
|
|
# Usage:
|
|
# ansible-playbook freecad_mcp/deploy.yml
|
|
#
|
|
# Required host_vars:
|
|
# freecad_mcp_user: freecad-mcp
|
|
# freecad_mcp_group: freecad-mcp
|
|
# freecad_mcp_directory: /srv/freecad-mcp
|
|
# freecad_mcp_port: 22063
|
|
#
|
|
# 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, git, 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 Heluca fork
|
|
become: true
|
|
become_user: "{{ freecad_mcp_user }}"
|
|
ansible.builtin.pip:
|
|
name:
|
|
- "freecad-robust-mcp @ git+https://github.com/heluca/freecad-addon-robust-mcp-server.git@{{ freecad_mcp_git_ref }}"
|
|
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
|
|
{{ inventory_hostname }}:{{ freecad_mcp_port }}
|
|
|
|
# =========================================================================
|
|
# Handlers
|
|
# =========================================================================
|
|
|
|
handlers:
|
|
- name: restart freecad-mcp
|
|
become: true
|
|
ansible.builtin.systemd:
|
|
name: freecad-mcp
|
|
state: restarted
|
|
|
|
|
|
# =============================================================================
|
|
# FreeCAD MCP Bridge (GUI) — runs FreeCAD on the XRDP desktop as principal_user,
|
|
# exposing the XML-RPC bridge on localhost:9875 that the MCP server connects to.
|
|
# =============================================================================
|
|
- name: Deploy FreeCAD MCP Bridge (GUI)
|
|
hosts: freecad_mcp
|
|
tasks:
|
|
|
|
- name: Ensure FreeCAD is installed
|
|
become: true
|
|
ansible.builtin.apt:
|
|
name: [freecad, tar]
|
|
state: present
|
|
update_cache: true
|
|
|
|
- name: Create FreeCAD MCP bridge directory
|
|
become: true
|
|
become_user: "{{ principal_user }}"
|
|
ansible.builtin.file:
|
|
path: "{{ freecad_mcp_bridge_directory }}"
|
|
state: directory
|
|
mode: '0755'
|
|
|
|
- name: Transfer and extract FreeCAD MCP bridge release
|
|
become: true
|
|
become_user: "{{ principal_user }}"
|
|
ansible.builtin.unarchive:
|
|
src: "~/rel/freecad_mcp_bridge_{{ freecad_mcp_git_ref }}.tar"
|
|
dest: "{{ freecad_mcp_bridge_directory }}"
|
|
notify: restart freecad-mcp-bridge
|
|
|
|
- name: Template FreeCAD MCP bridge systemd service
|
|
become: true
|
|
ansible.builtin.template:
|
|
src: freecad-mcp-bridge.service.j2
|
|
dest: /etc/systemd/system/freecad-mcp-bridge.service
|
|
owner: root
|
|
group: root
|
|
mode: '644'
|
|
notify:
|
|
- reload systemd
|
|
- restart freecad-mcp-bridge
|
|
|
|
- name: Enable and start freecad-mcp-bridge service
|
|
become: true
|
|
ansible.builtin.systemd:
|
|
name: freecad-mcp-bridge
|
|
enabled: true
|
|
state: started
|
|
daemon_reload: true
|
|
|
|
- name: Flush handlers to restart bridge before validation
|
|
ansible.builtin.meta: flush_handlers
|
|
|
|
- name: Wait for FreeCAD XML-RPC bridge to listen
|
|
ansible.builtin.wait_for:
|
|
port: "{{ freecad_mcp_xmlrpc_port | default(9875) }}"
|
|
host: localhost
|
|
delay: 5
|
|
timeout: 60
|
|
|
|
- name: Verify bridge is in GUI mode (FreeCAD.GuiUp via XML-RPC execute)
|
|
ansible.builtin.command:
|
|
argv:
|
|
- python3
|
|
- -c
|
|
- |
|
|
import sys, xmlrpc.client
|
|
proxy = xmlrpc.client.ServerProxy(
|
|
"http://localhost:{{ freecad_mcp_xmlrpc_port | default(9875) }}", allow_none=True)
|
|
resp = proxy.execute("_result_ = bool(FreeCAD.GuiUp)")
|
|
if not (resp.get("success") and resp.get("result") is True):
|
|
sys.exit("Bridge reachable but not in GUI mode: %r" % resp)
|
|
print("FreeCAD bridge GUI mode confirmed")
|
|
register: bridge_gui_check
|
|
retries: 5
|
|
delay: 5
|
|
until: bridge_gui_check.rc == 0
|
|
changed_when: false
|
|
|
|
- name: Display bridge info
|
|
ansible.builtin.debug:
|
|
msg: >-
|
|
FreeCAD MCP Bridge running in GUI mode on {{ inventory_hostname }},
|
|
XML-RPC localhost:{{ freecad_mcp_xmlrpc_port | default(9875) }}
|
|
|
|
handlers:
|
|
- name: reload systemd
|
|
become: true
|
|
ansible.builtin.systemd:
|
|
daemon_reload: true
|
|
|
|
- name: restart freecad-mcp-bridge
|
|
become: true
|
|
ansible.builtin.systemd:
|
|
name: freecad-mcp-bridge
|
|
state: restarted
|