Content
# Forge
A local **MCP server** that turns an AI agent into a parametric CAD operator.
The agent describes intent ("a phone stand", "make it taller", "add four M3
holes") and Forge turns that into a typed, validated, replayable **operation
graph** that compiles to real, printable CAD geometry (STL/STEP) plus rendered
previews.
The LLM never generates executable Python — it emits JSON conforming to a
Pydantic schema. Forge validates it, then compiles it to [CadQuery](https://github.com/CadQuery/cadquery)
internally. Every model is persisted as JSON on disk and keyed by `model_id`,
so edits across turns are deterministic changes to a saved structure.
## Requirements
- **Python 3.12** (not 3.14 — CadQuery/OCP and PyVista/VTK wheels lag new
CPython releases). The pin lives in `.python-version` and `pyproject.toml`.
- [uv](https://github.com/astral-sh/uv).
## Install
```bash
uv python install 3.12 # if you don't have 3.12
uv sync
uv run python -c "import cadquery" # sanity-check the native stack resolved
```
## Run
```bash
uv run forge
```
The server speaks MCP over stdio and blocks until the client disconnects
(Ctrl-C to exit).
### Connect from an MCP client
Point your client (Claude Code, Cursor, etc.) at the `forge` command. Example
stdio config:
```json
{
"mcpServers": {
"forge": {
"command": "uv",
"args": ["run", "forge"],
"cwd": "/path/to/forge"
}
}
}
```
## Tools
All tools take and return an explicit `model_id`. Failures come back as
structured `{"ok": false, "error": {"code", "detail"}}` payloads (never raised
exceptions), so an agent can read and recover.
| Tool | Purpose | Key output |
|---|---|---|
| `create_model` | Create a model from an initial operation graph. | `model_id`, `bounding_box`, `num_operations` |
| `modify_model` | Edit the graph: `append` / `update` / `remove` ops. Old state is preserved if the edit is invalid. | `bounding_box`, `applied_edits` |
| `preview_model` | Render an isometric PNG (also returned inline for vision-capable agents). | `image_path` |
| `validate_model` | Printability report (watertight, positive volume, bed fit). | `printable`, `errors`, `warnings`, `stats` |
| `measure_model` | Exact BREP metrics. | `bounding_box`, `volume_mm3`, `surface_area_mm2`, `center_of_mass`, `num_solids` |
| `export_model` | Write `model.stl` / `model.step`; STL is re-checked watertight. | `file_path`, `mesh_ok` |
### Operations
The graph is an ordered list; op #1 must be a primitive. Supported ops:
`box`, `cylinder`, `sphere`, `translate`, `boolean` (union/cut/intersect),
`fillet`, `chamfer`, `hole`. Units are millimeters everywhere.
Example — a phone stand:
```json
[
{ "id": "op1", "type": "box", "width": 80, "depth": 60, "height": 8 },
{ "id": "op2", "type": "box", "width": 80, "depth": 10, "height": 70 },
{ "id": "op3", "type": "fillet", "radius": 2.0, "edges": "all" }
]
```
## Workspace
Models and artifacts live under a workspace directory, one subdir per model:
```
<workspace>/<model_id>/
model.json # the operation graph — the single source of truth
preview.png # from preview_model
model.stl # from export_model
model.step
```
The workspace defaults to `./forge_workspace/` and is overridable via the
`FORGE_WORKSPACE` environment variable.
## Headless rendering
`preview_model` uses PyVista/VTK offscreen. On macOS this works natively. On
Linux (e.g. CI) VTK needs a GL context — install `libgl1` and run under
`xvfb-run`, or call `pyvista.start_xvfb()` before rendering.
## Development
```bash
uv run pytest -q
```
Tests mirror modules 1:1 plus `tests/test_tools.py` (tool-level integration)
and a golden determinism snapshot in `tests/golden/`.