refactor(ansible): rename freecad_mcp env vars and rework deployment
- 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
This commit is contained in:
@@ -4,18 +4,17 @@
|
||||
# =============================================================================
|
||||
# MCP Transport Configuration
|
||||
# =============================================================================
|
||||
FREECAD_MCP_TRANSPORT=http
|
||||
FREECAD_MCP_HTTP_PORT={{ freecad_mcp_port }}
|
||||
FREECAD_TRANSPORT=http
|
||||
FREECAD_HTTP_PORT={{ freecad_mcp_port }}
|
||||
|
||||
# =============================================================================
|
||||
# FreeCAD Connection Mode
|
||||
# =============================================================================
|
||||
FREECAD_MCP_MODE={{ freecad_mcp_mode | default('xmlrpc') }}
|
||||
FREECAD_MCP_XMLRPC_HOST={{ freecad_mcp_xmlrpc_host | default('localhost') }}
|
||||
FREECAD_MCP_XMLRPC_PORT={{ freecad_mcp_xmlrpc_port | default('9875') }}
|
||||
FREECAD_MCP_TIMEOUT_MS={{ freecad_mcp_timeout_ms | default('30000') }}
|
||||
FREECAD_MODE={{ freecad_mcp_mode | default('xmlrpc') }}
|
||||
FREECAD_XMLRPC_PORT={{ freecad_mcp_xmlrpc_port | default('9875') }}
|
||||
FREECAD_TIMEOUT_MS={{ freecad_mcp_timeout_ms | default('30000') }}
|
||||
|
||||
# =============================================================================
|
||||
# Logging
|
||||
# =============================================================================
|
||||
FREECAD_MCP_LOG_LEVEL={{ freecad_mcp_log_level | default('INFO') }}
|
||||
FREECAD_LOG_LEVEL={{ freecad_mcp_log_level | default('INFO') }}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
# FreeCAD Robust MCP Server — Ansible Deployment
|
||||
|
||||
Deploys the [FreeCAD Robust MCP Server](https://pypi.org/project/freecad-robust-mcp/)
|
||||
to Caliban as a systemd service with HTTP transport, ready for MCP Switchboard
|
||||
consumption.
|
||||
to Caliban as a systemd service with HTTP transport.
|
||||
|
||||
## Architecture
|
||||
|
||||
@@ -12,8 +11,8 @@ consumption.
|
||||
│ │
|
||||
│ ┌──────────────────────┐ │
|
||||
│ │ freecad-mcp.service │ │
|
||||
│ │ (streamable-http) │◄─── :22032 ──────────┤◄── MCP Switchboard
|
||||
│ │ venv + PyPI package │ │ (oberon.incus)
|
||||
│ │ (streamable-http) │◄─── :22061 ──────────┤◄── MCP Client
|
||||
│ │ venv + PyPI package │ │
|
||||
│ └──────────────────────┘ │
|
||||
│ │ │
|
||||
│ │ xmlrpc :9875 │
|
||||
@@ -25,6 +24,18 @@ consumption.
|
||||
└─────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## FreeCAD bridge required for tool calls
|
||||
|
||||
The service starts and answers the MCP `initialize` handshake **without** FreeCAD
|
||||
running — the XML-RPC connection to FreeCAD is only attempted on the first CAD
|
||||
tool call (lazy connect). So a green Ansible healthcheck means "transport up",
|
||||
**not** "FreeCAD reachable".
|
||||
|
||||
Actual CAD tool calls require FreeCAD running with the Robust MCP Bridge
|
||||
workbench started, listening on XML-RPC `localhost:9875`. Standing up that bridge
|
||||
(GUI or headless) on Caliban is a separate step from getting this service to
|
||||
boot.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Caliban host in Ansible inventory (already exists in Ouranos)
|
||||
@@ -62,7 +73,7 @@ Add to `ansible/inventory/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_port: 22061
|
||||
freecad_mcp_version: "0.5.0"
|
||||
```
|
||||
|
||||
@@ -100,7 +111,7 @@ The playbook automatically validates the deployment by:
|
||||
You can also manually test:
|
||||
|
||||
```bash
|
||||
curl -X POST http://caliban.incus:22032/mcp \
|
||||
curl -X POST http://caliban.incus:22061/mcp \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"jsonrpc":"2.0","method":"initialize","id":1,"params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"curl","version":"1.0.0"}}}'
|
||||
```
|
||||
@@ -126,5 +137,4 @@ The systemd service runs with hardened settings:
|
||||
| `PrivateTmp` | `true` | Isolated /tmp namespace |
|
||||
| `ReadWritePaths` | `/srv/freecad-mcp` | Only app directory is writable |
|
||||
|
||||
This is significantly more hardened than the Kernos service (which needs
|
||||
broad filesystem access for shell commands).
|
||||
|
||||
|
||||
@@ -216,3 +216,102 @@
|
||||
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
|
||||
|
||||
21
ansible/freecad_mcp/freecad-mcp-bridge.service.j2
Normal file
21
ansible/freecad_mcp/freecad-mcp-bridge.service.j2
Normal file
@@ -0,0 +1,21 @@
|
||||
[Unit]
|
||||
Description=FreeCAD MCP XML-RPC Bridge (GUI)
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User={{ principal_user }}
|
||||
WorkingDirectory={{ freecad_mcp_bridge_directory }}
|
||||
Environment=DISPLAY={{ freecad_mcp_bridge_display }}
|
||||
Environment=XAUTHORITY=/home/{{ principal_user }}/.Xauthority
|
||||
Environment=FREECAD_XMLRPC_PORT={{ freecad_mcp_xmlrpc_port | default('9875') }}
|
||||
Environment=FREECAD_SOCKET_PORT={{ freecad_mcp_socket_port | default('9876') }}
|
||||
ExecStart=/usr/bin/freecad {{ freecad_mcp_bridge_directory }}/freecad/RobustMCPBridge/freecad_mcp_bridge/startup_bridge.py
|
||||
Restart=on-failure
|
||||
RestartSec=10
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
SyslogIdentifier=freecad-mcp-bridge
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
46
ansible/freecad_mcp/stage.yml
Normal file
46
ansible/freecad_mcp/stage.yml
Normal file
@@ -0,0 +1,46 @@
|
||||
---
|
||||
- name: Stage FreeCAD MCP bridge release tarball
|
||||
hosts: localhost
|
||||
gather_facts: false
|
||||
vars:
|
||||
freecad_mcp_archive: "{{rel_dir}}/freecad_mcp_bridge_{{freecad_mcp_git_ref}}.tar"
|
||||
freecad_mcp_repo_url: "git@github.com:heluca/freecad-addon-robust-mcp-server.git"
|
||||
freecad_mcp_repo_dir: "{{github_dir}}/freecad-addon-robust-mcp-server"
|
||||
|
||||
tasks:
|
||||
- name: Ensure release directory exists
|
||||
file:
|
||||
path: "{{rel_dir}}"
|
||||
state: directory
|
||||
mode: '755'
|
||||
|
||||
- name: Ensure github directory exists
|
||||
file:
|
||||
path: "{{github_dir}}"
|
||||
state: directory
|
||||
mode: '755'
|
||||
|
||||
- name: Clone freecad-addon-robust-mcp-server repository if not present
|
||||
ansible.builtin.git:
|
||||
repo: "{{freecad_mcp_repo_url}}"
|
||||
dest: "{{freecad_mcp_repo_dir}}"
|
||||
version: "{{freecad_mcp_git_ref}}"
|
||||
accept_hostkey: true
|
||||
register: freecad_mcp_clone
|
||||
|
||||
- name: Fetch all remote branches and tags
|
||||
ansible.builtin.command: git fetch --all
|
||||
args:
|
||||
chdir: "{{freecad_mcp_repo_dir}}"
|
||||
when: freecad_mcp_clone is not changed
|
||||
|
||||
- name: Pull latest changes
|
||||
ansible.builtin.command: git pull
|
||||
args:
|
||||
chdir: "{{freecad_mcp_repo_dir}}"
|
||||
when: freecad_mcp_clone is not changed
|
||||
|
||||
- name: Create FreeCAD MCP bridge archive for specified release
|
||||
ansible.builtin.command: git archive -o "{{freecad_mcp_archive}}" "{{freecad_mcp_git_ref}}"
|
||||
args:
|
||||
chdir: "{{freecad_mcp_repo_dir}}"
|
||||
Reference in New Issue
Block a user