Skip to main content
Glama
vanovri

mcp-spatial-critic

by vanovri

mcp-spatial-critic

PyPI tests license python

An MCP server (Model Context Protocol, stdio JSON-RPC 2.0) that gives headless CAD agents a deterministic 3D-printing critic. Zero dependencies — pure Python standard library, no numpy, no trimesh, no Blender, no OpenSCAD at runtime.

An LLM can write OpenSCAD that looks correct in a screenshot and still exports a mesh that is inside-out, floating 5 mm above the plate, split into three bodies, or full of zero-area slivers. mcp-spatial-critic fails the build before the printer does.

agent → generate STL → mcp-spatial-critic → {"approved": false, "errors": ["NON_MANIFOLD"]}
                                          → agent repairs → re-audit → {"approved": true}

Why agents need this

Text-only agents cannot see geometry. Vision models see a render, not a manifold. The failure modes below all render beautifully and all fail in a slicer:

Defect

What the render shows

What the slicer does

Non-manifold edges

Normal shade

Refuses to slice, or silently deletes shells

Floating base

Normal shade

Starts printing in mid-air

Disconnected shells

Normal shade

Prints 3 loose parts instead of one

Inverted normals

Normal shade (mostly)

Treats solid as void, prints nothing

Sub-nozzle features

Normal shade

Vase mode collapse / missing wall

Related MCP server: ReverseCAD MCP

The five invariants

Every audit returns a critic_score from 0.0 to 10.0 and a list of critiques.

Check

Penalty

Error code

Every edge shared by exactly 2 triangles

−5.0

NON_MANIFOLD

Lowest Z sits on the build plate

−2.0

FLOATING_BASE

Exactly one connected shell

−2.0

MULTIPLE_SHELLS

Smallest bounding dimension ≥ min_wall_mm

−1.5

THIN_FEATURE

Positive signed volume (outward normals)

−1.0

INVERTED_OR_ZERO_VOLUME

No zero-area facets

−1.0

DEGENERATE_TRIANGLES

approved is true only when the error list is empty, the mesh is watertight and there is a single shell. The score is a ranking signal for repair loops; approved is the gate.

Install

# from GitHub — works today
pip install git+https://github.com/vanovri/mcp-spatial-critic

# or a checkout you can hack on
git clone https://github.com/vanovri/mcp-spatial-critic
cd mcp-spatial-critic
pip install -e .

Requires Python 3.10+. No third-party runtime packages.

Register the MCP server

Add this to your agent's MCP configuration (Claude Desktop claude_desktop_config.json, Claude Code .mcp.json, or any MCP-compatible client):

{
  "mcpServers": {
    "spatial-critic": {
      "command": "mcp-spatial-critic",
      "args": []
    }
  }
}

The server resolves relative paths against the directory it was launched from. Set MCP_SPATIAL_CRITIC_PRESETS to override where list_presets looks.

Tools

audit_stl

{
  "name": "audit_stl",
  "arguments": { "path": "build/bracket.stl", "min_wall_mm": 2.0 }
}

Response payload (also usable directly as a Python dict via audit_stl_file):

{
  "file": "bracket.stl",
  "path": "build/bracket.stl",
  "triangle_count": 12,
  "vertex_count": 8,
  "watertight": true,
  "non_manifold_edge_count": 0,
  "shell_count": 1,
  "bounding_box_mm": {
    "width_x": 20.0, "depth_y": 20.0, "height_z": 10.0,
    "min_z": 0.0, "max_z": 10.0
  },
  "volume_mm3": 4000.0,
  "degenerate_triangle_count": 0,
  "critic_score": 10.0,
  "approved": true,
  "critiques": [],
  "errors": []
}

isError is set on the MCP result whenever approved is false, so a tool-using agent sees the failure as a tool error rather than a success.

generate_reference_box

Writes a known-good watertight box and audits it. Use it as a control experiment when an agent claims its exporter is broken.

{ "out_path": "reference_box.stl", "width": 20, "depth": 20, "height": 10 }

list_presets

Lists the parametric presets bundled in presets/:

Preset

What it demonstrates

esp32_mount.scad

PCB standoff walls, snap lips, USB-C cutout, vent slots

m3_heatset_boss.scad

M3 brass insert seat with strengthening ribs

pagoda_roof_tier.py

Generator that splits >45° eaves into printable steps

python presets/pagoda_roof_tier.py > pagoda.scad
openscad -o pagoda.stl pagoda.scad

Talk to it without an agent

The transport is line-delimited JSON-RPC 2.0 on stdio, so you can drive it by hand:

printf '%s\n' \
  '{"jsonrpc":"2.0","id":1,"method":"initialize"}' \
  '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"generate_reference_box","arguments":{"out_path":"box.stl"}}}' \
  | mcp-spatial-critic

Library use

from mcp_spatial_critic import audit_stl_file, audit_stl_mesh, generate_watertight_box_stl

report = audit_stl_file("build/bracket.stl", min_wall_mm=1.6)
if not report["approved"]:
    for critique in report["critiques"]:
        print("FAIL:", critique)

# or audit an in-memory mesh
report = audit_stl_mesh(generate_watertight_box_stl(height=1.0))
assert "THIN_FEATURE" in report["errors"]

How it works

  • ASCII + binary STL parsers (core.py) — binary uses struct with a 50-byte stride; ASCII tolerates CRLF, tabs and malformed vertex lines.

  • Manifold test — canonicalised undirected edge keys (vertices rounded to 1e-5 mm) counted in a dict; any count ≠ 2 is a non-manifold edge.

  • Shell count — union-find with path compression over shared vertices.

  • Orientation — signed volume via the divergence theorem, Σ dot(v1, cross(v2, v3)) / 6.

  • Deterministic output — no timestamps, no random ordering; the same STL always produces the same bytes on the wire.

  • Server — handle_request() is a pure function (dict → dict | None), so the whole protocol is unit-testable without starting a process.

python -m unittest discover -s tests -v   # 40 tests

Known limitations (deliberate, tracked as issues)

  1. min_wall_mm is compared against the bounding box, not per-wall ray casts. A 40 × 40 × 20 mm hollow box with 0.4 mm walls passes this check. Real thin-wall detection needs slice-plane sampling — see GOOD_FIRST_ISSUES.md #3.

  2. Only STL is supported. 3MF and OBJ are open tasks.

  3. The critic diagnoses, it never repairs. Auto-repair belongs in the agent loop.

  4. Binary STL headers that begin with solid are disambiguated by looking for the facet keyword in the first 512 bytes. A pathological header could fool it.

  5. No build-volume check yet (the bbox is reported, the comparison is not).

Contributing

See GOOD_FIRST_ISSUES.md for scoped, test-covered starter tasks. Every PR needs a test that fails before the fix. Run the suite with python -m unittest discover -s tests -v.

License

MIT — see LICENSE.

Related MCP Connectors

Related MCP Servers

  • F
    license
    A
    quality
    D
    maintenance
    An intelligent 3D printing assistant that enables users to analyze meshes, generate optimal print profiles, and automate PrusaSlicer operations through an MCP client. It provides a comprehensive suite of tools for geometric analysis, printability checks, filament recommendations, and post-print diagnostics.
    17
    9
    -
  • F
    license
    Not graded
    quality
    B
    maintenance
    Enables AI agents to convert raw STL mesh files into production-grade STEP AP242 solid B-Rep models and compact semantic CAD Features JSON, with sub-second deterministic processing and high mathematical fidelity.
    27
    -
  • A
    license
    A
    quality
    A
    maintenance
    Enables an LLM to design parametric 3D parts in pure NumPy and verify they are actually printable before exporting them. Provides topology checks (watertightness, genus, shell count, wall thickness), self-rendered views, and gated STL/OBJ export that refuses broken solids.
    7
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Experimental load-path analysis for 3D-printed STL parts, with stress and movement comparisons across CAD revisions, PNG captures, visual reviews, and persistent human-guided design sessions. Runs locally through a stdio MCP server.
    MIT