Files
solidcraft-upstream/docs/mcp.md
T

14 KiB
Raw Blame History

SolidCraft and AI agents (MCP)

SolidCraft speaks the Model Context Protocol over stdio (JSON-RPC 2.0, one message per line; protocol revisions 2025-06-18, 2025-03-26 and 2024-11-05). An agent models exactly like a person does: every tool runs engine commands, the same ones the toolbar, palette, scripts and control channel use, and never opens a dialog.

Start it

# Headless: an in-process design session, no window (CPU renders for screenshots).
solidcraft-cli mcp

# Headless, starting from a design file or a command script.
solidcraft-cli mcp --in part.solidcraft

# Bridged to the running app, so you can watch the agent model:
solidcraft --control 7878 &
solidcraft-cli mcp --connect 127.0.0.1:7878

Bridged, the server finds the app's control token in the token file the app wrote for that port (control-protocol.md); --token T or SOLIDCRAFT_CONTROL_TOKEN give it explicitly when the app runs in a sandbox whose data folder the server can't see (prefer the variable: command-line arguments are visible to other users in the process list).

stdout carries only protocol messages; errors go to stderr. Bridged screenshots are written to a temporary file by the app and read back by the server, so both must run on the same machine.

Which files the agent can reach

Tool calls read, write and delete files only inside the server's folders:

  • the folder of the open design (the --in design, or whatever the agent last opened or saved), but only when it lies strictly inside your home folder (or the temporary folder) and not in a hidden folder. Home itself, a folder above it (/, /home, /Users, a drive's root), hidden folders such as ~/.config and folders elsewhere don't count. Hidden files and folders inside the design's folder (.env, .git) are out of reach too;
  • SolidCraft's own recovery folder, so doc.recovery_list, doc.recover and doc.recovery_discard work out of the box (discarding deletes only real recovery entries);
  • the server's own scratch folder (a new private solidcraft-mcp-* folder in the temporary folder), where screenshot without a path goes;
  • every --allow-path DIR (repeat it for more). A folder named here is reached in full, hidden files included.

The working folder the server was started in does not count by itself, so ~/.ssh, ~/.aws, ~/.bashrc and other dotfiles are never in reach by default. stderr lists the folders at start-up and says when the --in design's folder was left out; --allow-path adds it back.

Paths are resolved before they are compared, so .. and symlinks that lead out are refused (saving through a link writes the file it points to, so the link's target is what counts), and a new file must go into a folder that exists. On Windows a path must be on a local drive (C:\… or \\?\C:\…): UNC, device and NT paths in any spelling (\\server\share, //server/share, \\?\UNC\…, \??\…, \\.\…), drive-relative paths (C:file), a : in a file name (alternate data streams), reserved device names with any extension (NUL, CON, COM1.txt, …) and short 8.3 names (PROGRA~1) are refused before any file system call, and the resolved path is checked again, so a link or junction that leads to a share is refused too. This includes a sketch.text font given as a file path; installed fonts named by family still work. A refused path is a tool error saying why.

A hard link inside an allowed folder can't be told apart from an ordinary file, so a hard link to a file elsewhere is reachable through it; don't keep such links in a folder you allow.

solidcraft-cli mcp --allow-path ~/designs --allow-path ~/exports
solidcraft-cli mcp --allow-any-path   # no limit (the behaviour before this option existed)

Bridged, the server sends its folders with every request (allow_paths, see control-protocol.md) and the app adds its own recovery folder and the folder of the design open in it, under the same rules, so give absolute paths: a relative one resolves against the app's working folder, not the server's. The app's own file dialogs are never limited.

Tool annotations

Each tool says what it may do, for clients that ask before running tools (or auto-approve on these hints). list_commands, inspect_design, measure, body_topology and crash_reports are read-only. set_parameter, undo and redo change the design undoably. new_design replaces it (destructive). execute, batch, open, save, export and screenshot (whose path writes a file) can read, write, overwrite or delete files: they are not read-only, destructiveHint and openWorldHint are true.

Client configuration

Claude Code (project .mcp.json, or claude mcp add solidcraft -- solidcraft-cli mcp; a setup added under the previous name keeps working, see the compatibility notes):

{
  "mcpServers": {
    "solidcraft": { "command": "solidcraft-cli", "args": ["mcp"] }
  }
}

Claude Desktop (claude_desktop_config.json), bridged to a running app:

{
  "mcpServers": {
    "solidcraft": { "command": "/path/to/solidcraft-cli", "args": ["mcp", "--connect", "127.0.0.1:7878"] }
  }
}

Agent skills

skills/ holds three Agent Skills for agents that model through this server:

  • solidcraft-modeling covers building a new part.
  • solidcraft-editing covers changing an existing design.
  • solidcraft-checking adds checks for errors SolidCraft does not report, regression tests and porting, next to the other two.

They cover the workflow, the command shapes, how to verify with measure, and the known traps. With them an agent builds a part in a few batch calls and reads far less output. To install them for Claude Code, copy the folders into ~/.claude/skills/; skills/README.md has the details.

Tools

Tool Arguments What it does
list_commands filter? The command catalog: id, label, tab/panel, shortcut, parameter docs, enabled now. filter matches id, label, tab and panel.
execute command, params?, verbose? (default true) Run any command with JSON parameters (list_commands shows them). Failures leave the design unchanged. A feature command replies with the feature (feature, name), every body (bodies) and the bodies it created, changed and removed (created_bodies, changed_bodies, removed_bodies). verbose: false uses the same compact reply as batch.
batch commands: [{command, params}], rollback? (default true), verbose? (default true) Run steps in order, stop at the first error and undo the completed steps. The reply names the failing step (failed.index, failed.command, failed.error) and every result before it. verbose: false keeps the reply small in large designs: feature steps leave out the full body list (created_bodies, changed_bodies, removed_bodies stay) and sketch steps the ids they repeat outside created. Bridged to an app that has engine.batch, the whole batch is one control request (one frame of the app). Older apps, and apps showing the agent cursor, get the steps one by one.
inspect_design measure? (default true), sketches? (default true) Parameters, timeline (type, suppressed, rolled back, error, warning, timing), bodies (volume, area, centre of mass, bbox, face/edge/vertex counts, visible), sketches (curves, points, constraints, profiles, DOF, solve status, visible), selection, undo depth. Hide helper sketches, bodies or construction planes with execute browser.visibility ({items, visible} or {folder, visible}).
measure body? Volume (mm³), area (mm²), centre of mass, bbox and topology counts per body, with totals.
body_topology body Edges (index, midpoint, length, ends) and faces (index, area, centroid, normal). An edge midpoint is the [x,y,z] that picks that edge for fillet and chamfer.
set_parameter name, value (number or expression), unit?, comment? Create or change a user parameter and recompute; reports recomputed (features rebuilt), restored (features whose earlier result for the same inputs was reused, e.g. a value set back) and features that now fail.
screenshot view? (iso, top, front, back, bottom, left, right, home, fit, or a named view saved with view.save), camera? ({target, yaw, pitch, distance, fov?}, as view.apply), path?, width?, height?, source? (window, model) A PNG image content block. Headless: CPU render fitted to the model, or from the named view or camera. Bridged: the app window (default) or a model render with the app's camera; a named view or camera moves the app's viewport there first (execute view.apply does the same without a screenshot).
export path, format? (step, 3mf, 3mf-tagged, stl, stla, obj, urdf), bodies?, mesh_prefix?, effort?, velocity? STEP, 3MF or mesh export; URDF writes the whole assembly with its meshes next to it (docs/urdf.md).
undo, redo — Undo / redo one change.
crash_reports id?, limit? The crash reports on this computer, newest first; with id, the whole report (backtrace, command, recent commands, design_path/replay_path). See crash-reports.md.
new_design name? Start an empty design.
open path Open a .solidcraft design, a STEP file (.step/.stp) as a new design holding its bodies (an Import base feature), or a 3MF/STL file as mesh bodies.
save path? Save the design (to its current file without path).

To change a feature after it is made, read its definition with execute timeline.inspect ({feature}: id or name) and change fields with timeline.edit ({feature, set}; e.g. {"extent": {"distance": "30"}}). The fields per feature type are listed in the book's Solid features chapter.

Resources: solidcraft://design (inspect JSON with measurements) and solidcraft://commands (the catalog).

Errors: an unknown tool or arguments that don't match the tool's schema are JSON-RPC -32602 errors; an engine failure (bad command parameters, a feature that fails) is a tool result with isError: true and the engine's message, which the model can read and correct.

batch rollback goes back to an engine checkpoint (edit.checkpoint) taken before the first step: the design, the active sketch and the undo history are restored exactly as they were, however long the history is. Batches that call file.new or doc.open get the design back, not its file path. (An older app without checkpoints falls back to undoing as many steps as the batch added to the undo history.)

Compact replies

execute and batch default to verbose: true, preserving the full command results. For long modelling sessions, set verbose: false at the tool-argument level:

{"name": "execute", "arguments": {"command": "solid.box", "params": {"length": 10, "width": 10, "height": 10}, "verbose": false}}

Feature replies omit the full bodies list while keeping feature, name, created_bodies, changed_bodies and removed_bodies. A cut can therefore report a changed body even when no body name was added or removed. Sketch replies keep new IDs under created and omit only identical top-level points, curves, constraints and dimensions lists. Other fields and errors are unchanged; inspection commands still return their complete results. Compaction uses the engine's reply directly, without an additional design-inspection request.

Demo: an enclosure built over MCP

examples/demo/enclosure_mcp.py is a small MCP client. It drives a running app (solidcraft --control PORT) through solidcraft-cli mcp --connect and builds an electronics enclosure one execute call at a time (enclosure_steps.json): a rounded box, shelled open at the top, a USB port cut through a side wall, four screw bosses, and then a change of the width parameter that the timeline rebuilds. examples/demo/record.sh records the run with ffmpeg, with each step's caption burnt in.

examples/demo/enclosure_ui_mcp.py (v2) builds the same part the way a person would: the modelling goes through the app's UI over its control channel. The pointer glides to toolbar buttons and faces and clicks them, the real dialogs open, the sketches go on the model's faces, and the camera follows. The parameters, the sketch dimensions bound to them and two extrudes go over MCP. With --story v3 (the default) one standoff is patterned 2 × 2 in the Rectangular Pattern dialog with spacings from the parameters, and width and wall are then changed in the Parameters dialog. record_ui.sh records it and draws the pointer and the captions.

A typical session

→ {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"me","version":"1"}}}
→ {"jsonrpc":"2.0","method":"notifications/initialized"}
→ {"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"batch","arguments":{"commands":[
     {"command":"sketch.create","params":{"plane":"XY"}},
     {"command":"sketch.rectangle.two_point","params":{"p0":[0,0],"p1":[40,30]}},
     {"command":"sketch.finish"},
     {"command":"solid.extrude","params":{"distance":20}},
     {"command":"solid.fillet","params":{"edges":[[0,0,10]],"radius":3}},
     {"command":"sketch.create","params":{"plane":"XY"}},
     {"command":"sketch.circle.center","params":{"center":[20,15],"radius":5}},
     {"command":"solid.extrude","params":{"distance":20,"operation":"cut"}}]}}}
← {"jsonrpc":"2.0","id":2,"result":{"content":[{"type":"text","text":"{\"ok\": true, \"completed\": 8, \"results\": [...]}"}]}}
→ {"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"measure","arguments":{}}}
← … "total": {"volume_mm3": 22390.6, …}
→ {"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"screenshot","arguments":{"view":"iso"}}}
← {"jsonrpc":"2.0","id":4,"result":{"content":[{"type":"image","mimeType":"image/png","data":"iVBORw0…"},{"type":"text","text":"{\"source\":\"model\",…}"}]}}
→ {"jsonrpc":"2.0","id":5,"method":"tools/call","params":{"name":"export","arguments":{"path":"/tmp/part.step"}}}

The tests in crates/mcp/src/tests.rs run sessions like this (box → fillet → hole, parameters, batch rollback, hostile arguments) and check the results only through MCP.