feat: add FreeCAD and Rommie MCP server configurations and deployment playbooks

This commit is contained in:
2026-03-21 00:21:48 +00:00
parent 8fddef6050
commit 83170bf6ce
15 changed files with 585 additions and 4 deletions

View File

@@ -0,0 +1,21 @@
# FreeCAD Robust MCP Server — Environment Configuration
# Managed by Ansible — do not edit manually
# =============================================================================
# MCP Transport Configuration
# =============================================================================
FREECAD_TRANSPORT=http
FREECAD_HTTP_PORT={{ freecad_mcp_port }}
# =============================================================================
# FreeCAD Connection Mode
# =============================================================================
FREECAD_MODE={{ freecad_mcp_mode | default('xmlrpc') }}
FREECAD_XMLRPC_HOST={{ freecad_mcp_xmlrpc_host | default('localhost') }}
FREECAD_XMLRPC_PORT={{ freecad_mcp_xmlrpc_port | default('9875') }}
FREECAD_TIMEOUT_MS={{ freecad_mcp_timeout_ms | default('30000') }}
# =============================================================================
# Logging
# =============================================================================
FREECAD_LOG_LEVEL={{ freecad_mcp_log_level | default('INFO') }}

View File

@@ -0,0 +1,130 @@
# 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.
## Architecture
```
┌─────────────────────────────────────────────────┐
│ caliban.incus │
│ │
│ ┌──────────────────────┐ │
│ │ freecad-mcp.service │ │
│ │ (streamable-http) │◄─── :22082 ──────────┤◄── MCP Switchboard
│ │ venv + PyPI package │ │ (oberon.incus)
│ └──────────────────────┘ │
│ │ │
│ │ xmlrpc :9875 │
│ ▼ │
│ ┌──────────────────────┐ │
│ │ FreeCAD (future) │ │
│ │ XML-RPC server │ │
│ └──────────────────────┘ │
└─────────────────────────────────────────────────┘
```
## Prerequisites
- Caliban host in Ansible inventory (already exists in Ouranos)
- Python 3.11+ on Caliban (already present)
## Deployment
### 1. Copy playbook files to Ouranos
Copy the contents of this directory into your Ouranos repo:
```
ansible/freecad_mcp/
├── deploy.yml
├── .env.j2
└── freecad-mcp.service.j2
```
### 2. Add inventory group
Add to `ansible/inventory/hosts`:
```yaml
freecad_mcp:
hosts:
caliban.incus:
```
### 3. Add host variables
Add to `ansible/inventory/host_vars/caliban.incus.yml`:
```yaml
# FreeCAD Robust MCP Server
freecad_mcp_user: harper
freecad_mcp_group: harper
freecad_mcp_directory: /srv/freecad-mcp
freecad_mcp_port: 22082
freecad_mcp_version: "0.5.0"
```
Update `services` list:
```yaml
services:
- alloy
- caliban
- docker
- freecad_mcp
- kernos
```
### 4. Run the playbook
```bash
ansible-playbook freecad_mcp/deploy.yml
```
## Upgrading
To upgrade to a new PyPI version, update `freecad_mcp_version` in host_vars
and re-run the playbook. The pip install task will detect the version change
and the handler will restart the service.
## Validation
The playbook automatically validates the deployment by:
1. Waiting for the HTTP port to become available
2. Sending an MCP `initialize` JSON-RPC request to `/mcp`
3. Verifying a 200 response
You can also manually test:
```bash
curl -X POST http://caliban.incus:22082/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"}}}'
```
## Service Management
```bash
# On Caliban
sudo systemctl status freecad-mcp
sudo systemctl restart freecad-mcp
sudo journalctl -u freecad-mcp -f
```
## Security
The systemd service runs with hardened settings:
| Setting | Value | Rationale |
|---------|-------|-----------|
| `NoNewPrivileges` | `true` | No privilege escalation |
| `ProtectSystem` | `strict` | Filesystem is read-only except allowed paths |
| `ProtectHome` | `read-only` | Home directories protected |
| `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).

View File

@@ -0,0 +1,219 @@
---
# =============================================================================
# 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: 22082
# 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

View File

@@ -0,0 +1,23 @@
[Unit]
Description=FreeCAD Robust MCP Server
After=network.target
[Service]
Type=simple
User={{ freecad_mcp_user }}
Group={{ freecad_mcp_group }}
WorkingDirectory={{ freecad_mcp_directory }}
ExecStart={{ freecad_mcp_directory }}/.venv/bin/freecad-mcp
EnvironmentFile={{ freecad_mcp_directory }}/.env
Restart=on-failure
RestartSec=5
# Security hardening — MCP server needs no special privileges
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=read-only
PrivateTmp=true
ReadWritePaths={{ freecad_mcp_directory }}
[Install]
WantedBy=multi-user.target