Files
homelable/.github/workflows/quality.yml
T
Pouzor 4c8831d5df feat(mcp): expose the rack canvas and the inventory write routes
The MCP server covered the logical canvas only. A client could create a
design whose design_type is `rack` and then had no way to put a rack, a
mount or a cable in it, and it could triage the Device Inventory but
never create, edit or delete an entry — though every route already
existed. 24 tools become 47.

Racks (mcp/app/racks.py), 13 tools: list_racks, get_rack,
list_rack_inventory, list_faceplates, create_rack, update_rack,
delete_rack, mount_device, mount_accessory, unmount_device, move_device,
set_device_faceplate, patch_cable, unpatch_cable.

`POST /api/v1/racks/save` is full-state upsert-and-prune, so every write
is load, mutate, save it all back. Two traps are encoded once, in the
helpers: the response rows carry `design_id` and the save schemas do not
(echoing one back verbatim is a 422), and the viewport has to be echoed
or the save resets the user's pan and zoom. The plate, its colour and its
ports need no special handling — /racks/save writes those through to the
inventory row itself.

Two frontend files are copied rather than imported, since the MCP server
is a separate service:

  - mcp/app/faceplates.py — the 21-plate catalog from
    frontend/src/rack/faceplates.ts, data only. `bank()` reproduces the
    port arithmetic exactly, so a plate seeded here lands its ports where
    the renderer draws them.
  - mcp/app/rack_layout.py — canPlace / findSlot / freeUnits from
    frontend/src/rack/layout.ts, to *choose* a slot. The backend still
    polices the one it is sent.

Both are guarded against drift: test_faceplates_sync.py parses
faceplates.ts and compares every plate down to each port's coordinates,
the way test_node_types_sync.py guards NODE_TYPES, and
test_layout_parity.py runs the frontend's own layout cases against the
Python port.

Devices (mcp/app/devices.py), 10 tools: create_device, update_device,
delete_device, bulk_approve_devices, bulk_hide_devices,
bulk_restore_devices, rescan_device, list_proxmox_children,
get_scan_config, update_scan_config.

Verified against a running backend: the rack flow end to end (create,
mount, patch, move, swap a plate, shrink, unmount) plus every refusal
path, and the device tools including a byte-identical scan-config
write-back.

ha-relevant: no
2026-09-04 15:10:05 +02:00

83 lines
2.2 KiB
YAML

name: Quality
on:
push:
branches: [main]
pull_request:
branches: [main]
permissions:
contents: read
jobs:
lint-scripts:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: ShellCheck — lxc-install.sh
uses: ludeeus/action-shellcheck@2.0.0
with:
scandir: './scripts'
- name: Hadolint — Dockerfile.backend
uses: hadolint/hadolint-action@v3.1.0
with:
dockerfile: Dockerfile.backend
ignore: DL3008
- name: Hadolint — Dockerfile.frontend
uses: hadolint/hadolint-action@v3.1.0
with:
dockerfile: Dockerfile.frontend
ignore: DL3008
frontend:
runs-on: ubuntu-latest
defaults:
run:
working-directory: frontend
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: frontend/package-lock.json
- run: npm ci
- run: npm run lint
- run: npm run typecheck
- run: npm test -- --run
backend:
runs-on: ubuntu-latest
defaults:
run:
working-directory: backend
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.11'
- run: pip install -r requirements.txt
- run: ruff check .
- run: mypy app/
- run: pytest
# Also guards the copies the MCP server keeps of frontend data against drift:
# NODE_TYPES / EDGE_TYPES in mcp/app/tools.py against frontend/src/types/index.ts,
# and the faceplate catalog in mcp/app/faceplates.py against
# frontend/src/rack/faceplates.ts (test_node_types_sync.py,
# test_edge_types_sync.py, test_faceplates_sync.py) — those tests need the
# frontend sources, so this job runs from a full checkout rather than the mcp
# Docker context.
mcp:
runs-on: ubuntu-latest
defaults:
run:
working-directory: mcp
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.13'
- run: pip install -r requirements.txt
- run: pytest