Skip to main content
Glama
README.md
# 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/`.