OCCTMCP
Analytic Interaction with CAD Models built on the OCCT CAD Kernel
Documentation
OCCTMCP
MCP server that gives LLMs the ability to author, inspect, and iterate on 3D CAD models with OpenCASCADE via the OCCTSwift family.
Part of the OCCTSwift ecosystem — see the ecosystem map for how this package sits on top of the kernel, viewport, bridge, and AIS layers. SemVer-stable from v1.0.0.
The Swift implementation calls OCCT directly in-process (no subprocess, no JSONL marshalling) and exposes 77 typed MCP tools that cover authoring, scene reads, mutation, introspection, construction, analysis, I/O, mesh, drawing, selection / remap, mesh-zone analysis, mesh inspection, alignment, and dimension overlays.
How It Works
LLM picks a typed tool (boolean_op, transform_body, render_preview, …)
→ OCCTMCP runs the OCCT operation directly via OCCTSwift / Tools / AIS / Mesh
→ Writes BREP/STEP/PNG + manifest.json + annotations.json
→ OCCTSwiftViewport (optional) auto-reloads the 3D model
For novel geometry the typed tools don't cover, the LLM falls back to execute_script: arbitrary Swift code with the full OCCTSwift API, compiled and run in-process.
Tools
77 tools, organized below. Call get_api_reference({ category: "mcp_tools" }) to dump every tool's JSON Schema in one shot, useful for LLM auto-discovery. Most flows can answer "what's the volume?", "make it red", "boolean-subtract these", "render a preview", "add a dimension between these two faces", "export to STEP", and "draw this" without ever touching execute_script.
Authoring
| Tool | Purpose |
|---|---|
execute_script | Write & execute arbitrary Swift CAD code (full OCCTSwift API) |
get_script | Read the most recent script's source |
get_api_reference | Browse OCCTSwift API by category |
Scene reads
| Tool | Purpose |
|---|---|
get_scene | Read current scene manifest (bodies, colors, materials) |
export_model | List exported BREP / STEP / STL / OBJ file paths |
compare_versions | Diff current scene vs N runs ago (added / removed / appearance / file changed) |
Scene mutation
| Tool | Purpose |
|---|---|
remove_body | Delete a body from the scene (manifest + BREP file) |
clear_scene | Wipe all bodies, optionally keep diff history |
rename_body | Change a body's id |
set_appearance | Update color / opacity / roughness / metallic / display name |
Introspection
| Tool | Purpose |
|---|---|
validate_geometry | Per-body topology validation (isValid, error counts) |
compute_metrics | Volume, area, centroid, bounding box, principal axes |
query_topology | Find faces / edges / vertices matching criteria, return stable IDs. Edge results (#119) carry endpoints (every kind) plus a unit direction for LINE edges, and circleCenter/radius/axis/startAngle/endAngle for CIRCULAR edges |
measure_distance | Min distance + contacts between two bodies |
measure_deviation | Signed, spatially-resolved surface deviation between two bodies — max / rms / mean / p95 / signedMean (systematic proud(+)/shy(−) bias) each way + worstPoint, plus an optional per-section signedMean sweep along an axis. The certify-a-reconstruction metric (measure_distance is min-only). See signMode under Deviation & reconstruction QA for what the sign is worth against an open thin-walled reference |
measure_vertex_fit (#118) | Exact per-vertex distance table from a mesh body's own vertices to a target body's real BRep geometry (Shape.vertex(at:).distance(to:), nearest entity kind via distanceSolutionDetail): the vertex-fit instrument neither measure_distance (body-to-body, capped) nor measure_deviation (mesh-to-mesh, approximate) provides. Worst-N table by default; includeAllVertices: true for the full per-vertex table |
recognize_features | Pockets and holes via AAG heuristics |
inspect_assembly | Walk an XCAF assembly tree (STEP / IGES / XBF) |
Construction
| Tool | Purpose |
|---|---|
apply_feature | Drill / fillet / chamfer / extrude / revolve / thread / boolean (FeatureSpec) |
transform_body | Translate / rotate / uniform-scale (records identity history for remap) |
boolean_op | Union / subtract / intersect / split (records per-input history for remap) |
mirror_or_pattern | Mirror / linear / circular pattern → N new bodies |
Engineering analysis
| Tool | Purpose |
|---|---|
check_thickness | Wall-thickness analysis with thin-region flags |
analyze_clearance | Pairwise interference / minimum clearance |
heal_shape | Heal imported / non-watertight geometry; before/after stats |
Deviation & reconstruction QA
Signed, spatially-resolved comparison of a reconstruction against its source mesh. Where measure_deviation's scalars can hide a systematic shape error (a wrong cross-section that averages out), these expose where and which way the candidate departs. Pure-Swift rendering — no Python/matplotlib.
Which way is out? measure_deviation, deviation_histogram and signed_deviation_heatmap share one signed-distance engine, so they share a signMode knob. The sign of a deviation depends on which reference triangle a sample is judged against, and against an open, thin-walled reference (a raw scan / STL skin) the nearest one is often the wrong one: a candidate flank sitting 4.5 mm inside a 2 mm wall is only 2.5 mm from the wall's inner surface, so that surface wins on proximity and — facing the cavity — reports +2.5 proud for a part that is 4.5 shy. Wrong side, wrong magnitude, nothing tying to flag it. signMode: "robust" (the default since v1.17.0) rejects reference triangles whose outward normal opposes the sample's own before the nearest survivor wins, recovering both figures; samples with no compatible surface in reach are reported ambiguous and withheld from the signed statistics rather than guessed. signMode: "nearest" restores the pre-v1.17 raw nearest-triangle sign, which is correct against a watertight / single-surface reference. An ambiguousFraction near 1.0 means the reference's winding is likely inverted relative to the sampled body; where nothing has a trustworthy sign the signed figures come back null rather than a zero that would read as "perfectly centred".
The two families of number answer different questions, and signMode moves only the second:
| Family | Measures to | Moved by signMode? |
|---|---|---|
Unsigned — max / rms / mean / p95 / worstPoint / symmetricHausdorff / maxAbs / withinTolerance | the nearest reference surface, whatever it is | No — same meaning as pre-v1.17 |
Signed — signedMean / signedMin / signedMax / sections / histogram buckets / heatmap colours | the surface the sample corresponds to | Yes |
Against a watertight reference these are the same surface and the families agree. Against an open thin-walled one they diverge on purpose: max: 2.5 next to signedMin: -4.5 says the nearest reference geometry is an inner wall 2.5 away while the skin that flank belongs to is 4.5 above it. Both true. A gap between them is itself the tell that the reference is thin-walled.
| Tool | Purpose |
|---|---|
deviation_histogram | Signed point-to-surface deviation distribution: μ / σ / median / p95 / proud-shy extremes, percent within ±tolerance, bucket histogram + optional PNG. A non-zero mean or bimodal shape ⇒ systematic error |
cross_section_compare | Slice both bodies at N stations across their shared axis-extent overlap; per-section signed-mean / RMS / area-ratio / centroid-offset + a pose-robust radial shape scalar, with overlay PNGs. Default outerEnvelope mode compares against the reference's outer boundary per angular direction so inner window-return / frame paths of a thin-wall or scanned part don't pollute the aggregate; each station reports axisCoord (world position along the axis). Handles open-shell references (raw scan / STL skin) whose sections are open arcs, reports the overlap range, and warns on stations that sliced only one body. The highest-leverage detector of a wrong-shape section |
symmetric_difference_volume (#122) | The direct geometric fidelity figure a mean/RMS surface deviation can hide via cancellation: the two one-sided volumes between a candidate and a reference (excess-only, missing-only) and their sum, via OCCTSwiftMesh's generalized winding number (robust against an open/non-watertight/self-intersecting reference, unlike boolean_op's subtract, which fails outright against one). Deterministic Halton-sequence Monte Carlo sampling; reports a standard error and an exact-BREP-volume cross-check where available |
signed_deviation_heatmap | Render the candidate surface coloured by signed distance (proud = red, shy = blue) through a diverging colormap with a colorbar legend. Triangles whose sign can't be established against an open/thin-walled reference render grey (ambiguousTriangles/ambiguousFraction, excluded from signedMin/Max/Mean) rather than a coin-flip red/blue — see signMode above |
overlay_render | Render the reference mesh semi-transparent over the opaque candidate solid — see the departure in 3D |
Mesh analysis (zones)
The mesh-inspection surface for raw scans / STL skins: split a body's mesh into surface zones (plane / cylinder / sphere / cone, via OCCTSwiftMesh's dihedral region-growing + primitive-fit merge), then measure how far each zone's own cross-section stays constant along an axis (a loftable-extent map). Both are pure mesh-domain composition — the aggregation/verdict logic here is independent of OCCTReconstruct's own engine, per the mandatory-analytic-verification policy.
| Tool | Purpose |
|---|---|
segment_mesh_zones | Split a body's mesh into surface zones; each zone gets a stable zone:<bodyId>#<n> id, a fitted primitive (kind/params/residual), and (optionally) a categorical PNG render and/or its own registered scene body |
zone_continuity_sweep | Sweep a zone (or whole body) along an axis; report maximal within-tolerance runs (loftable extents) and deviation intervals between them, each with world axisCoord spans and magnitudes |
list_zones | Inspect the zone registry (<output_dir>/zones.json) |
clear_zones | Wipe the zone registry, optionally for one body |
fit_primitives (#107) | Schnabel-style RANSAC primitive report (plane/cylinder/sphere/cone), claiming GLOBAL inliers rather than segment_mesh_zones' edge-adjacent-only region growing — so it can unify a primitive (e.g. a cylinder interrupted by a boss) the zone table keeps split across regions. Optional zoneId scopes the fit to one zone; strategy: "auto" runs a dihedral-vs-RANSAC bake-off and reports which won. uncoveredFraction (triangles no primitive claimed) and a maxPrimitives cap are reported as strictly separate warnings |
Mesh inspection
The mesh-domain check-list / measurement surface (Phase 2 of the mesh-analysis expansion): integrity diagnosis, wall thickness, reflective-symmetry detection, and two-body alignment, all working directly on a body's tessellated surface rather than BREP topology, so they don't degrade on facet shells (a raw STL import) the way check_thickness does.
| Tool | Purpose |
|---|---|
mesh_diagnose | Printability-check-list integrity report: watertight, edge/vertex-manifold, orientable, connected components, boundary loops, Euler characteristic / genus, duplicate/degenerate triangle counts, sliver signals, plus derived pass/warn/fail checks[]. Self-intersection is NOT checked (an upstream OCCTSwiftMesh limitation) |
mesh_thickness | Mesh-domain wall thickness via the ray method (normal-opposite, first-hit, optional cone-averaged median): the complement to check_thickness for raw meshes. Reports the thickness distribution, an optional below-threshold section, and an optional histogram PNG |
detect_symmetry | Detect reflective (mirror-plane) symmetry: 3 PCA candidate planes through the area-weighted centroid, each verified by reflecting sampled points and measuring their residual distance back to the surface. Rotational/axis symmetry detection is deferred to a later phase |
align_bodies (#104) | GOM-style alignment: register a source body onto a reference body via point-to-plane ICP (PCA pre-align + normal-space sampling + trimmed correspondence). mode: "bestFit" (default, full pipeline) or "preAlign" (coarse PCA/bbox pose only). Returns the recovered transform (row-major, translation + axis-angle rotation) and residual stats; apply: true writes it onto the source body in place with the same history semantics as transform_body. The step scan-vs-CAD deviation tools need before their numbers mean anything |
mesh_curvature | Per-vertex discrete curvature (Rusinkiewicz per-face tensor) over a body's own welded mesh: principal curvatures k1/k2, mean, gaussian, plus a colored render (colorBy) and bounded stats (medians, flatFraction, highCurvatureFraction). No reference body needed |
detect_mesh_features (#108) | Crease-ring feature outlines (doors, panels, window returns, recesses) on a raw scan mesh via dihedral-fold-edge detection: welds the mesh, chains fold edges exceeding minAngleDegrees into closed rings and open paths (largest-first), for meshes where recognize_features (BREP/AAG) has no B-rep structure to work against. Junction-aware (Y/T intersections split cleanly). Reports each ring's containingZones when segment_mesh_zones has already run for the body. includePoints: true (#120) also returns each ring's ordered world-coordinate vertex polyline. Optional render: the surface plus each ring as its own categorically-colored wireframe overlay |
fit_edge_chain (#121) | Segments an ordered 3D point chain (typically a detect_mesh_features ring's polyline) into line and circular-arc runs: per-segment kind, endpoints, unit direction (line) or center/radius/axis/startAngle/endAngle (arc), and fit residuals. A raw STL has no curved edges by construction; an arc exists on the mesh only as a fit over a chain of straight facet edges, which neither fit_primitives nor segment_mesh_zones (both fit SURFACES, not edge chains) provide. Multi-radius chains split into separate segments rather than collapsing to one averaged circle |
Selection & remap
| Tool | Purpose |
|---|---|
select_topology | Pick faces / edges / vertices, get a stable selectionId. Edge anchors (#119) carry endpoints (every kind) plus a unit direction for LINE edges, and circleCenter/radius/axis/startAngle/endAngle for CIRCULAR edges |
remap_selection | Carry selectionIds across mutations of the same body (history-based for transform / heal / boolean / apply_feature; centroid heuristic fallback otherwise) |
find_correspondences | Map selectionIds from a source body onto a target body that's a known transform of the source — mirror_or_pattern outputs are the typical case |
select_by_feature | Bulk pick by feature kind (e.g. all hole edges) |
list_selections | Inspect the in-memory selection registry |
clear_selections | Wipe the registry |
Annotations & overlays
| Tool | Purpose |
|---|---|
add_dimension | Add a linear / angular / radial dimension; renders in render_preview |
add_scene_primitive | Add trihedron / workPlane / axis / pointCloud / boundingBox / diffMarker |
auto_dimension | Heuristic dimension drop for the principal extents |
show_bounding_box | Add a body's AABB as an overlay |
diff_overlay | Visualize the diff between two snapshots |
remove_scene_annotation | Remove a dimension or primitive by id |
list_annotations | Inspect the annotations sidecar |
I/O
| Tool | Purpose |
|---|---|
read_brep | Load a .brep from disk into the scene (allowInvalid loads a loose-face / invalid shape for measurement) |
import_file | Multi-format import (STEP / IGES / STL / OBJ); optional XCAF assembly; allowInvalid for in-progress reconstructions |
export_scene | Export to STEP / IGES / BREP / STL / OBJ / glTF / GLB |
set_assembly_metadata | Modify XCAF document or per-component metadata |
Mesh & visualisation
| Tool | Purpose |
|---|---|
generate_mesh | Tessellate to triangles + quality metrics |
simplify_mesh | QEM mesh decimation to .stl/.obj — wraps OCCTSwiftMesh's Mesh.simplified (vendored meshoptimizer) |
render_preview | One-shot PNG render with measurement labels and primitive overlays. Mesh-scale bodies (imported scans, >10k edges) render via a linear path in seconds — edge overlays kept up to 100k edges, surface-only beyond |
pick_surface_point | Cast a render_preview-framed ray through a pixel → world surface point + selectionId (usable as an add_dimension anchor) |
generate_drawing | Multi-view ISO 128-30 DXF technical drawing — bodyId for a single part, or bodyIds (2+) for a general-arrangement assembly sheet with a parts list + balloons |
Topology graph (low-level)
| Tool | Purpose |
|---|---|
graph_validate | Validate a BREP's topology graph (raw path) |
graph_compact | Drop unreferenced graph nodes; write rebuilt BREP |
graph_dedup | Deduplicate shared surface / curve geometry |
graph_ml | Export topology + UV/edge samples as ML-friendly JSON |
graph_select | Local graph adjacency / selection: face neighbours (+ convexity), edge faces, vertex edges, face-adjacency (gAAG), edge classes |
feature_recognize | Pockets + holes (raw BREP path; recognize_features is the scene-aware wrapper) |
Reconstruction graph (read/write)
LLM read/write over an attributed reconstruction graph — annotate per-node decisions and persist them. Backed by OCCTSwift 1.2.0's NodeAttributeStore + Codable GraphSnapshot. Nodes are addressed as <kind>:<index> (e.g. face:3). The reconstruction engine (surface fitting, congruence detection) lives in OCCTReconstruct; these tools are the annotate-and-persist layer — reconstruct_force_fit records an override for the engine to honour, it does not re-fit here.
| Tool | Purpose |
|---|---|
reconstruct_get_graph | Export the attributed graph as JSON — topology counts, annotated nodes (with reconstruct.* attributes), instance clusters. Starts a session from a bodyId or reads an existing one by sessionId |
reconstruct_set_decision | Annotate a node's decidedBy (geometric / ml / human) and/or accept-reject a proposed fit |
reconstruct_force_fit | Override a node's fitted surface type (e.g. force cylinder) |
reconstruct_confirm_instances | Confirm / reject a congruence cluster ("these N nodes are one part definition") |
reconstruct_export_session | Write the session snapshot to disk (byte-stable JSON) |
reconstruct_import_session | Reload a snapshot file into a session |
Implementations
This repo ships two implementations side-by-side:
- Swift (
Sources/,Package.swift): the primary server. In-process against OCCTSwift / OCCTSwiftMesh / OCCTSwiftTools / OCCTSwiftAIS / DrawingComposer using the official Swift MCP SDK. 77 tools. macOS 15+ (the OCCT.xcframework arm64 platform). - Node / TypeScript (
src/,dist/) — the original implementation. Shells out to theocctkitCLI for everything Swift-side. 37 tools (the pre-v0.4 surface; selection / remap / annotations are Swift-only). Useful if you can't run a macOS binary.
Both speak stdio MCP and read/write the same manifest format.
Prerequisites
- macOS 15+ (for the Swift implementation)
- Swift 6.1+ / Xcode 16+
- For the Node implementation only: Node.js 18+, plus a sibling clone of OCCTSwiftScripts so
occtkitis on$PATH(ormake installit)
Setup
Swift implementation (recommended)
git clone https://github.com/SecondMouseAU/OCCTMCP.git
cd OCCTMCP
swift build -c release
In Claude Code's .mcp.json:
{
"mcpServers": {
"occtmcp": {
"command": "/path/to/OCCTMCP/.build/release/occtmcp-server"
}
}
}
The Swift package is published on the Swift Package Index.
Node implementation
git clone https://github.com/SecondMouseAU/OCCTMCP.git
cd OCCTMCP
npm install
npm run build
In .mcp.json:
{
"mcpServers": {
"occtmcp": {
"command": "node",
"args": ["/path/to/OCCTMCP/dist/index.js"]
}
}
}
Example
The LLM can author CAD models by composing typed tools — most everyday flows never touch execute_script:
boolean_op(op: "subtract", aBodyId: "block", bBodyId: "hole", outputBodyId: "drilled")
→ "drilled" body added to the scene
select_topology(bodyId: "drilled", kind: "face", limit: 1)
→ returns selectionId "sel:drilled#face[12]"
add_dimension(kind: "linear", anchors: [...]) ; render_preview()
For novel geometry, drop into execute_script with the full OCCTSwift API:
import OCCTSwift
import ScriptHarness
let ctx = ScriptContext()
let C = ScriptContext.Colors.self
let box = Shape.box(width: 40, height: 30, depth: 20)!
let hole = Shape.cylinder(radius: 5, height: 30)!
.translated(by: SIMD3(20, -1, 10))!
let result = box.subtracting(hole)!
let filleted = result.filleted(radius: 2.0)!
try ctx.add(filleted, id: "part", color: C.steel, name: "Bracket")
try ctx.emit(description: "Filleted bracket with mounting hole")
API Categories
The get_api_reference tool provides documentation for:
- primitives — box, cylinder, sphere, cone, torus, wedge
- sweeps — extrude, revolve, pipe sweep, loft, ruled
- booleans — union, subtract, intersect, section
- modifications — fillet, chamfer, shell, offset, draft, defeature
- transforms — translate, rotate, scale, mirror
- wires — rectangle, circle, polygon, spline, helix, offset
- curves2d/3d — line, arc, ellipse, bspline, bezier, interpolate
- surfaces — plane, cylinder, cone, sphere, extrusion, revolution, plate
- analysis — volume, area, distance, bounds, validation
- import_export — STL, STEP, IGES, BREP, OBJ, PLY
- mcp_tools — every MCP tool's JSON Schema (handy for LLM auto-discovery)
Versioning
OCCTMCP follows Semantic Versioning. The Swift port reached v1.0.0 on 2026-05-09 — feature-complete against the original Node implementation, plus a layer of selection / remap / annotation tools that are Swift-only.
Releases are tagged on GitHub. The main branch is what SPI tracks.
License
LGPL-2.1-or-later — same as OCCTSwift.