Agent Skills

Analytic Interaction with CAD Models built on the OCCT CAD Kernel

Install

Install and configure the MCP from https://github.com/SecondMouseAU/OCCTMCP now. Follow the repository's installation instructions, ask me for anything you can't complete yourself, and verify its tools load.
README

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 79 typed MCP tools that cover authoring, scene reads, mutation, introspection, construction, analysis, I/O, mesh, drawing, selection / remap, mesh-zone analysis, mesh inspection, alignment, dimension overlays, and the agent-to-viewport-host selection bridge.

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

79 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

ToolPurpose
execute_scriptWrite & execute arbitrary Swift CAD code (full OCCTSwift API)
get_scriptRead the most recent script's source
get_api_referenceBrowse OCCTSwift API by category

Scene reads

ToolPurpose
get_sceneRead current scene manifest (bodies, colors, materials)
export_modelList exported BREP / STEP / STL / OBJ file paths
compare_versionsDiff current scene vs N runs ago (added / removed / appearance / file changed)

Scene mutation

ToolPurpose
remove_bodyDelete a body from the scene (manifest + BREP file)
clear_sceneWipe all bodies, optionally keep diff history
rename_bodyChange a body's id
set_appearanceUpdate color / opacity / roughness / metallic / display name

Introspection

ToolPurpose
validate_geometryPer-body topology validation (isValid, error counts)
compute_metricsVolume, area, centroid, bounding box, principal axes
query_topologyFind 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. Face-only includeNeighbors (neighbours with convexity, capped by neighborLimit) and oppositeFaces (advisory opposite planar face, oppositeMethod exact or bbox) (#199)
measure_distanceMin distance + contacts between two bodies
measure_deviationSigned, 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_featuresPockets and holes via AAG heuristics
inspect_assemblyWalk an XCAF assembly tree (STEP / IGES / XBF)

Construction

ToolPurpose
apply_featureDrill / fillet / chamfer / extrude / revolve / thread / boolean (FeatureSpec)
transform_bodyTranslate / rotate / uniform-scale (records identity history for remap)
boolean_opUnion / subtract / intersect / split (records per-input history for remap)
mirror_or_patternMirror / linear / circular pattern → N new bodies

Engineering analysis

ToolPurpose
check_thicknessWall-thickness analysis with thin-region flags
analyze_clearancePairwise interference / minimum clearance
heal_shapeHeal 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:

FamilyMeasures toMoved by signMode?
Unsigned — max / rms / mean / p95 / worstPoint / symmetricHausdorff / maxAbs / withinTolerancethe nearest reference surface, whatever it isNo — same meaning as pre-v1.17
Signed — signedMean / signedMin / signedMax / sections / histogram buckets / heatmap coloursthe surface the sample corresponds toYes

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.

ToolPurpose
deviation_histogramSigned 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_compareSlice 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_heatmapRender 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_renderRender 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.

ToolPurpose
segment_mesh_zonesSplit 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_sweepSweep 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_zonesInspect the zone registry (<output_dir>/zones.json)
clear_zonesWipe 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.

ToolPurpose
mesh_diagnosePrintability-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_thicknessMesh-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_symmetryDetect 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_curvaturePer-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

ToolPurpose
select_topologyPick 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_selectionCarry selectionIds across mutations of the same body (history-based for transform / heal / boolean / apply_feature; centroid heuristic fallback otherwise)
find_correspondencesMap 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_featureBulk pick by feature kind (e.g. all hole edges)
list_selectionsInspect the in-memory selection registry
clear_selectionsWipe the registry
get_selection (#189)Read a live viewport host's current selection (<output_dir>/selection.json + host.lock, per SecondMouseAU/OCCTSwiftInteraction#17). Three-state result: noHost vs hostRunning with an empty or populated selection list; each entry resolves against this server's own scene the same way select_topology does and mints a composable selectionId
highlight_selection (#190)Ask a live viewport host to highlight one sub-shape (replace/add/remove/xor, mirroring OCCTSwiftAIS.SelectionScheme; target attention by default marks the agent's own marker, selection changes the human's). Writes highlight_requests/<id>.json, polls highlight_requests/handled/<id>.json for the host's real applied/rejected/superseded outcome, or an explicit timeout/noHost

Annotations & overlays

ToolPurpose
add_dimensionAdd a linear / angular / radial dimension; renders in render_preview
add_scene_primitiveAdd trihedron / workPlane / axis / pointCloud / boundingBox / diffMarker
auto_dimensionHeuristic dimension drop for the principal extents
show_bounding_boxAdd a body's AABB as an overlay
diff_overlayVisualize the diff between two snapshots
remove_scene_annotationRemove a dimension or primitive by id
list_annotationsInspect the annotations sidecar

I/O

ToolPurpose
read_brepLoad a .brep from disk into the scene (allowInvalid loads a loose-face / invalid shape for measurement)
import_fileMulti-format import (STEP / IGES / STL / OBJ); optional XCAF assembly; allowInvalid for in-progress reconstructions
export_sceneExport to STEP / IGES / BREP / STL / OBJ / glTF / GLB
set_assembly_metadataModify XCAF document or per-component metadata

Mesh & visualisation

ToolPurpose
generate_meshTessellate to triangles + quality metrics
simplify_meshQEM mesh decimation to .stl/.obj — wraps OCCTSwiftMesh's Mesh.simplified (vendored meshoptimizer)
render_previewOne-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_pointCast a render_preview-framed ray through a pixel → world surface point + selectionId (usable as an add_dimension anchor)
generate_drawingMulti-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)

ToolPurpose
graph_validateValidate a BREP's topology graph (raw path)
graph_compactDrop unreferenced graph nodes; write rebuilt BREP
graph_dedupDeduplicate shared surface / curve geometry
graph_mlExport topology + UV/edge samples as ML-friendly JSON
graph_selectLocal graph adjacency / selection: face neighbours (+ convexity), edge faces, vertex edges, face-adjacency (gAAG), edge classes
feature_recognizePockets + 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.

ToolPurpose
reconstruct_get_graphExport 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_decisionAnnotate a node's decidedBy (geometric / ml / human) and/or accept-reject a proposed fit
reconstruct_force_fitOverride a node's fitted surface type (e.g. force cylinder)
reconstruct_confirm_instancesConfirm / reject a congruence cluster ("these N nodes are one part definition")
reconstruct_export_sessionWrite the session snapshot to disk (byte-stable JSON)
reconstruct_import_sessionReload 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. 79 tools. macOS 15+ (the OCCT.xcframework arm64 platform).
  • Node / TypeScript (src/, dist/) — the original implementation. Shells out to the occtkit CLI 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 occtkit is on $PATH (or make install it)

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.

Search skills and MCP servers

Search across 31,816 skills and MCPs