Skip to main content
Glama

forge3d

A 3D-model toolchain any AI agent can drive. One MCP server, one agent skill, five interchangeable geometry engines, a validation layer that knows what "correct" means for print vs web vs game, and a Three.js viewer that turns a mesh into a single HTML file you can send to anyone.

Most "AI makes 3D" demos stop at a pretty mesh in a viewport. The hard parts are everything after: is it watertight, is it the right size in millimetres, will it survive a 60k-triangle budget, can the next agent pick up where this one left off without re-reading a megabyte of vertices. forge3d is built around those.

A chair generated by forge3d's procedural backend, rendered by its own CPU preview renderer

Nothing in that image is stock: the geometry came from examples/procedural_chair.py and the render from forge3d.render — the same loop an agent runs. Open examples/chair.html for the interactive version.


What it actually does

brief ──► plan_model ──► create_model ──► validate_model ──► repair_model ──► review_model
                              │                                                 │
                              ├──► previews (PNG, so the agent can *see* it)     │
                              └──► viewer (single-file Three.js page) ◄─────────┘

Step

Tool

What it prevents

Brief

plan_model

generating from a vague prompt with no scale or purpose

Build

create_model

being locked into one engine

Inspect

inspect_model / model_card

pasting geometry into a context window

Check

validate_model

shipping a non-manifold mesh to a printer

Fix

repair_model

silent holes, flipped normals, absurd poly counts

Look

render_model

the agent guessing instead of iterating

Ship

make_viewer

needing a host to show someone the result

Hand off

create_handoff / checklist

two agents diverging on the same model

Related MCP server: rupa3d

Install

git clone https://github.com/chaudharyvivek1/forge3d.git
cd forge3d
pip install -e ".[render]"

Optional engines — install only what you need:

# exact CSG parts (brackets, enclosures)
sudo apt install openscad        # macOS: brew install --cask openscad
                                 # Windows: winget install OpenSCAD.OpenSCAD

# organic forms, bevels, materials, animation
sudo snap install blender --classic   # macOS: brew install --cask blender
                                      # Windows: winget install BlenderFoundation.Blender

# real B-rep CAD with STEP export
pip install cadquery

# decimation in repair_model
pip install fast-simplification

# boolean unions in trimesh (procedural backend)
pip install manifold3d

Check what this machine can actually do — never guess:

forge3d doctor      # OS, Python, engines, packages, workdir, and how to fix gaps
forge3d backends    # just the engine list

doctor is also an MCP tool, so an agent can diagnose its own runtime instead of guessing why a backend is unavailable. It reports whether an env var is set, never its value — an API key is not echoed back.

Works on Linux, macOS and Windows, in Docker, and in CPU-only agent sandboxes. The full matrix, including what does not work where, is in docs/platforms.md.

Quick start (agent)

Add to Claude Desktop, Cursor, or any MCP client:

{
  "mcpServers": {
    "forge3d": {
      "command": "forge3d-mcp",
      "env": { "FORGE3D_WORKDIR": "/absolute/path/to/forge3d-work" }
    }
  }
}

For a remote agent or your own app, run it over HTTP instead:

pip install "forge3d[http]"

forge3d-mcp --http 8000                                    # loopback only, no auth
FORGE3D_HTTP_TOKEN=$(openssl rand -hex 32) \
  forge3d-mcp --http 8000 --host 0.0.0.0                    # network, authenticated

The HTTP transport binds 127.0.0.1 by default and refuses a public bind without a token — v0.2.0 bound 0.0.0.0 with no authentication, which meant anyone who could reach the port could run arbitrary model code. HTTP mode also forces subprocess isolation for that code. Read docs/security.md before exposing it anywhere; it states plainly what is still not protected.

Then a single call gets you from brief to shareable page:

create_model(
    prompt="a modern side chair with cross braces",
    name="chair",
    purpose="game",
    backend="procedural",
    source=open("examples/procedural_chair.py").read(),
    dimensions={"x": 450, "y": 480, "z": 850},
    units="mm",
    scale_to_spec=True,
)
# -> { "ok": true, "path": ".../chair.glb", "viewer": ".../chair.html",
#      "validation": { "ok": true, "summary": "PASS with 1 warning(s) ..." }, ... }

Quick start (human, no agent)

forge3d plan   --prompt "wall bracket for a 32mm pipe" --purpose print --dims x=120,y=60,z=40
forge3d create --name chair --purpose game --backend procedural \
               --source examples/procedural_chair.py --dims x=450,y=480,z=850 \
               --scale-to-spec --viewer
forge3d validate output/chair.glb --profile print
forge3d render   output/chair.glb
forge3d viewer   output/chair.glb --out chair.html

The five engines

Backend

Needs

Best for

Weak at

procedural

nothing (trimesh)

precise dimensions, repeated structure, editable output, offline

organic forms

openscad

openscad binary

hard-surface CSG, brackets, enclosures, jigs

sculpting, texturing

blender

blender binary

bevels, subdivision, materials, rigging, animation

exact mechanical dims

cadquery

pip install cadquery

manufacturing, STEP deliverables, fillets

organic forms

hosted

a text-to-3D service

stylised props, characters, textured meshes fast

exact scale, watertight print geometry

backend="auto" routes from the spec: print → OpenSCAD if present, cad → CadQuery, game → Blender, web with references → hosted, otherwise procedural. The routing table is data (backends/registry.py), so it is testable and easy to change.

Three.js

Two ways, both in the repo:

Single-file page — make_viewer embeds the GLB as a data URI, so the HTML needs no server, no CORS and no hosting. The model is embedded; three.js still loads from a CDN by default, so for an air-gapped machine point FORGE3D_THREE_URL at a local mirror of three before building the page:

forge3d viewer output/chair.glb --out chair.html
FORGE3D_THREE_URL=http://localhost:8080/three forge3d viewer output/chair.glb --out chair.html

Embeddable element — for your own app, a custom element that takes a URL:

<script type="importmap">
{"imports":{"three":"https://unpkg.com/three@0.169.0/build/three.module.js",
            "three/addons/":"https://unpkg.com/three@0.169.0/examples/jsm/"}}
</script>
<script type="module" src="/forge3d-viewer.js"></script>

<forge3d-viewer src="/models/chair.glb" height="480" autorotate grid></forge3d-viewer>

Events: forge3d:loaded (detail.triangles), forge3d:error. See docs/threejs.md.

Why the context-window rules matter

A 100k-triangle GLB is a few megabytes; as text it is millions of tokens. So every tool in forge3d takes and returns paths, never geometry. The one way to describe a model in context is model_card — roughly 300 tokens:

**chair.glb** — glb
96 tris · 418.77 x 412.56 x 850 mm · watertight · 8 body
validation: PASS with 1 warning(s) — game profile (single_body)
ship as: .glb

Multi-agent, multi-person

handoff.py defines the packet that lets a second agent — or a designer and a 3D artist — take over without re-deriving anything: spec, artifacts, checks, owners, open questions, and who is next. It writes both JSON (for agents) and Markdown (for humans). See docs/multi-agent.md.

create_handoff(
    spec=spec, artifacts={"model": "chair.glb", "viewer": "chair.html"},
    backend="procedural",
    checks=checklist("game"),
    owners={"design": "priya", "geometry": "agent-a"},
    next_agent="review-agent",
)

What was fixed in v0.2.0

The audit that produced 0.2.0 found seven real bugs, each now covered by a test in tests/test_regressions.py:

  1. The default backend crashed inside a worker thread. The code timeout used signal.alarm, which is unavailable on Windows and raises ValueError: signal only works in main thread elsewhere — and MCP servers run sync tools in a threadpool. Timeouts are now thread-based.

  2. .gltf export was impossible. trimesh returns a dict of filename→payload for multi-file formats; that dict went straight to write_bytes.

  3. A model name could break out of the viewer page. </script> in a title terminated the script block — a real XSS vector on user-supplied names.

  4. embed=False produced a page pointing at a file it never wrote.

  5. Non-Latin model names collapsed to model. Hindi and CJK names are now preserved, matras included.

  6. Windows-illegal filenames were not avoided (con, aux, COM1…).

  7. Subprocess output was decoded with the platform default, so a non-ASCII error message raised UnicodeDecodeError on Windows.

Plus: materials and seed are now reachable from create_model, the OpenSCAD source detector no longer needs the module keyword, and the viewer explains a blocked CDN instead of showing a blank canvas.

Repository layout

src/forge3d/
  spec.py          the brief as data — purpose, units, dimensions, budget
  backends/        procedural · openscad · blender · cadquery · hosted
  validate.py      profile-based checks, every failure carries a fix hint
  repair.py        merge, drop, fill, re-orient, decimate
  render.py        CPU previews (matplotlib) — works headless, no GPU
  viewer.py        single-file Three.js page
  viewer_assets/   template.html + forge3d-viewer.js custom element
  platform_support.py  binary discovery, UTF-8 subprocess, thread-safe timeouts, doctor
  http_server.py   HTTP transport: loopback default, bearer auth, bind policy
  sandbox_runner.py  the child process for FORGE3D_EXEC_MODE=subprocess
  context.py       model cards — the only sanctioned way to describe a mesh
  handoff.py       checklists + multi-agent handoff packets
  jobs.py          durable job records for slow generations
  server.py        the MCP surface (16 tools)
  cli.py           same capabilities without an agent
skill/             agent skill: SKILL.md + references
docs/              architecture, security, platforms, backends, threejs, multi-agent, licensing
examples/          chair (trimesh), bracket (OpenSCAD), gear (bpy), plate (CadQuery)
tests/             pytest — unit, platform and regression suites
Dockerfile         CPU-only image, non-root, /work volume

Security

Two things worth knowing before you point this at anything other than your own machine:

Agent code execution. The procedural, cadquery and blender backends run code the agent wrote. FORGE3D_ALLOW_CODE_EXEC=false disables that, and FORGE3D_EXEC_MODE=subprocess runs it in a killable child process with rlimits instead of in the server. Neither is a sandbox against a determined attacker — for that, use the container and keep the caller trusted.

The HTTP transport requires a bearer token for any non-loopback bind, compares it in constant time, and forces subprocess isolation. It does not do TLS, rate limiting or per-user identity; put a reverse proxy in front for those.

Full detail, including the threat model and what is deliberately not covered: docs/security.md.

Honest limitations

  • Textures. The local engines produce geometry, not painted PBR materials. For textured output, route to a hosted text-to-3D service.

  • Hosted adapter. Provider APIs (Meshy, Tripo, Rodin, Hunyuan3D) differ. hosted.py speaks one small documented contract — point it at a thin worker. It does not pretend to be a drop-in client for all of them.

  • Rigging. Blender can rig; forge3d does not generate skeletons or weights for you. Auto-rig quality varies badly.

  • Preview renders are review-quality, not beauty shots. For beauty, the Three.js viewer with an environment map is the better tool.

  • Licensing. Output rights differ sharply between hosted services. That is your provider's terms, not this repo's. See docs/licensing.md.

  • No provider-specific integrations. The hosted adapter speaks one documented contract and ships a reference worker (examples/hosted_worker.py) you adapt. A "universal" client for Meshy/Tripo/Rodin would be wrong in ways you would only find in production, so it does not pretend to be one.

  • Subprocess isolation is not a security boundary. It gives real cancellation and resource ceilings; it does not stop a child process reading your files.

Licence

MIT — see LICENSE. Built by Vivek Chaudhary.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables AI agents and software to create, edit, inspect, validate, and export CAD geometry through MCP using backend-neutral typed operations, with FreeCAD/OpenCascade as the authoritative B-rep backend and JSCAD preview.
    1
    Apache 2.0
  • A
    license
    A
    quality
    C
    maintenance
    Enables AI agents and MCP clients to perform 3D asset creation and verification: exact b-rep CAD, GLB validation with embedded certificates, mesh inspection, and physics scenes, with most tools running without Blender.
    36
    48 npm
    MIT
  • A
    license
    Not graded
    quality
    A
    maintenance
    Enables AI agents to model, sculpt, rig, animate and bake particle effects by editing a semantic scene document, with built-in software rendering, measurable critics that flag floating or broken geometry, and game-ready glTF export. It exposes templates, atomic edit batches, motion and effect previews, undo history, and no GPU, browser, or Blender required.
    0
    Apache 2.0
  • A
    license
    Not graded
    quality
    A
    maintenance
    Enables AI agents to design, modify, and verify exact B-rep 3D CAD models by running build123d scripts on an OpenCascade kernel, with live three.js preview, collision and FDM printability checks, text-based spatial perception, user notes/sketch annotations, version history, and one-click export to OrcaSlicer.
    1
    MIT