Skip to main content
Glama
Horuspokus

mesh-prep-mcp

by Horuspokus

mesh-prep-mcp

An MCP server that lets an LLM agent turn a raw 3D asset into an experience-ready one — inspect, repair, decimate, rescale and export triangle meshes through the Model Context Protocol, with a small C++17 core for the numbers the agent bases its decisions on.

CI

Why

Getting a scan or CAD export into a real-time scene is a chain of small judgement calls: is it watertight? are these millimetres? how far can I decimate before the silhouette suffers? Those calls are usually made by hand. This project exposes the underlying operators to an agent as MCP tools, returns before/after statistics for every step, and lets the agent decide — and explain — what it did. It is deliberately small: a rehearsal of agent-driven 3D data preparation, not a product.

Related MCP server: openusd-mcp

What the agent gets

Tool

What it does

load_mesh(path)

Loads OBJ / STL / PLY / GLB / glTF / OFF / 3MF …, registers it, returns a first inspection

inspect_mesh(mesh_id)

Bounds, extent, area, volume, face/vertex counts, degenerate faces, boundary & non-manifold edges, duplicate vertices, plus an issue list with the operator that fixes each one

repair_mesh(mesh_id)

Welds duplicates, drops degenerate/duplicate faces and fold-overs, fixes winding, fills small holes → new id, before/after

decimate_mesh(mesh_id, target_faces | ratio)

Quadric-error decimation to a face budget → new id, reduction %, surface-area drift

scale_mesh(mesh_id, factor)

Unit conversion (e.g. mm → m) → new id

export_mesh(mesh_id, path, format?)

OBJ / GLB / glTF / STL / PLY / OFF

list_meshes()

Everything in the session with full provenance

resource mesh://{id}/report

Markdown report of one mesh

prompt prepare_for_realtime

A guided full preparation pass

Every operator returns a new mesh_id; earlier ids stay valid, so the agent can compare, branch or roll back without any undo logic.

Demo (no LLM needed)

demo: scripts/demo_client.py preparing a noisy 82k-face scan

$ python scripts/make_sample_mesh.py      # 82k-face noisy "scan" in millimetres, with holes and degenerate faces
$ python scripts/demo_client.py           # talks to the server over stdio, like Claude Desktop does

tools: load_mesh, inspect_mesh, repair_mesh, decimate_mesh, scale_mesh, export_mesh, list_meshes

[load_mesh]     m1: 81,920 faces, 40,962 verts, extent (502, 501, 502), backend=cpp
                 issue: 3 degenerate (zero-area or collapsed) faces  ->  repair_mesh
                 issue: open surface: 9 boundary edges (not watertight)  ->  repair_mesh fills small holes; ...

[repair_mesh]   m1: 81,920 faces, 40,962 verts, watertight=False, issues=2
             -> m2: 81,920 faces, 40,962 verts, watertight=True, issues=0

[decimate_mesh] m2: 81,920 faces, 40,962 verts, watertight=True, issues=0
             -> m3: 20,000 faces, 10,002 verts, watertight=False, issues=1
                 75.6% fewer faces, surface area -0.49%
                 issue: 1 non-manifold edges (shared by 3+ faces)

[repair_mesh]   verify after decimation
             -> m4: 19,998 faces, 10,001 verts, watertight=True, issues=0

[scale_mesh]    x0.001 (mm -> m): extent (0.501, 0.501, 0.502)

[export_mesh]   examples/output/sample_scan_ready.glb  (360,752 bytes)

Note the step after decimation: quadric collapse can leave a fold-over, so the client inspects the result and repairs once more before exporting — the agent gets numbers, not "done", precisely so it can make that call. With Claude Desktop the same run is one sentence: "Prepare examples/sample_scan.obj for a real-time scene with a 20k-face budget and explain what you changed."

Architecture

Claude Desktop / Claude Code
        │  MCP (stdio, JSON-RPC)
        ▼
 src/mesh_prep_mcp/server.py   tools · resource · prompt   (mcp SDK, 1.x and 2.x)
        │
 src/mesh_prep_mcp/ops.py      load / inspect / repair / decimate / scale / export   (trimesh, fast-simplification)
        │
 cpp/ meshstats                C++17: bounds, area, signed volume, degenerate faces,
        └─ bindings.cpp        boundary & non-manifold edges, duplicate vertices   (pybind11)

The C++ backend is optional: if the extension is not built, ops.inspect falls back to a NumPy implementation and reports backend: "python". The C++ library has no Python dependency and its own tests (ctest).

Quick start

git clone https://github.com/Horuspokus/mesh-prep-mcp && cd mesh-prep-mcp
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"

# C++ core + pybind11 module (optional but recommended)
cmake -S cpp -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build
ctest --test-dir build --output-on-failure

pytest -q                                   # Python tests
python scripts/make_sample_mesh.py && python scripts/demo_client.py

If import mesh_prep_mcp fails after the editable install (seen once on macOS with Python 3.14 in an iCloud-synced folder), install it non-editable instead: python -m pip install --force-reinstall --no-deps . — and re-run that after editing files under src/.

Claude Desktop

Copy examples/claude_desktop_config.json into your claude_desktop_config.json and replace the two absolute paths. MESH_PREP_ROOT sandboxes file access to one directory (recommended).

Claude Code

claude mcp add mesh-prep -e MESH_PREP_ROOT=$PWD/examples -- $PWD/.venv/bin/mesh-prep-mcp

Design notes

  • Immutable results. Operators never mutate a registered mesh; they add a new one with its provenance (history). Cheap, and it makes "compare budgets A and B" or "go back to the repaired version" trivial for the agent.

  • Numbers, not adjectives. Every tool returns measured before/after values (face count, surface-area drift, watertightness) so the agent's explanation to the user is grounded.

  • Issue → operator mapping. inspect_mesh does the triage once, in code; the agent only has to follow the recommendation or argue with it.

  • Safety. MESH_PREP_ROOT rejects paths outside one directory; exports never overwrite the input by default naming.

  • C++ where it pays. The statistics pass is the one thing that runs on every step and on every mesh size, so it lives in C++ with the GIL released; everything else stays in Python for readability.

Roadmap

  • IFC input through IfcOpenShell (element-level preparation, properties kept as metadata)

  • glTF LOD chains (decimate_chain(budgets=[200k, 50k, 10k]))

  • Material / texture checks (missing UVs, oversized textures)

  • Scene assembly: place several prepared assets into one glTF scene

  • Streamable-HTTP transport for use behind a web app

License

MIT — Gökhan Dönmez, 2026.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables AI assistants to read, inspect, and manipulate OpenUSD 3D scenes, including scene graph traversal, property querying, mesh export, and variant switching.
    10
    MIT
  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables LLMs to create and manipulate 3D models using OpenSCAD through MCP tools for code-based modeling, preview, and export.
    1
    MIT
  • F
    license
    Not graded
    quality
    D
    maintenance
    Converts GeoJSON geometries into 3D triangulated meshes using a local LLM (Ollama), exposing an MCP tool for mesh generation.
    -