Skip to content

AI assistants (MCP)

A design program can expose itself to an AI assistant over the Model Context Protocol — headlessly, with no window. The assistant can then list the model’s parts, measure them, read how they were built, export them, and see them: the screenshot tool renders the scene and returns the PNG as an image, so questions about shape and proportion get answered by looking rather than guessing.

Swap the entry point from EngrCad.Run to EngrCadMcp.Run (package EngrCAD.Mcp):

using EngrCAD.Mcp;
using EngrCAD.Modeling;
return EngrCadMcp.Run(args, BuildScene, "bracket");
static Scene BuildScene()
{
var scene = new Scene();
scene.Add(new Part("bracket",
Shape.Extrude(Sketch.RoundedRectangle(40, 24, 5), 8)
.Drill(StandardHoles.Clearance(5), [new(-14, 0), new(14, 0)], depth: 10,
SketchPlane.At((0, 0, 8), Vector3d.UnitX, Vector3d.UnitY))));
return scene;
}

That adds one switch, --mcp. Everything else is untouched: no arguments still opens the live dotnet watch loop, and --view, --export, --render, --section, --render-style behave exactly as before.

dotnet run --project MyModel -- --mcp # serve this model over MCP on stdio
dotnet run --project MyModel # unchanged: the live viewer loop

Configuring a client

MCP clients launch the server as a child process and talk to it over stdin/stdout:

{
"mcpServers": {
"bracket": {
"command": "dotnet",
"args": ["run", "--project", "samples/MyModel", "--", "--mcp"]
}
}
}

For a faster start-up (no build check per launch), point it at a published binary instead:

{
"mcpServers": {
"bracket": {
"command": "C:/models/bracket/bracket.exe",
"args": ["--mcp"]
}
}
}

What the assistant gets

ToolAnswers
list_tabsWhat tabs exist, and how much is in each.
list_partsEvery part: name, tab, geometry kind (Shape / B-Rep / mesh / SDF), how many times it is placed and where, colour, display mode, whether it has an exact B-Rep route.
describe_partFaces, vertices, closed, volume, surface area, local and world bounding boxes, placement, annotations — and the construction tree, the ordered record of how the part was built (booleans, drills, fillets, sketches; or the parametric feature list with its [Param] values).
screenshotA rendered PNG. Standard views (iso, front, back, left, right, top, bottom), display styles (shaded-edges, shaded, wireframe, points), a section plane (sectionAxis + sectionOffset) that cuts the model open to show bores and wall thickness, image size, an optional tab/part filter, and t — a position on the program’s animation timeline, so an assistant can ask for the mechanism at half stroke.
exportWrites .step (exact B-Rep, one file per part), .stl/.obj/.off (meshes merged with instance transforms), .3mf/.amf (one named object per part), .glb/.gltf (the assembly hierarchy kept), .vtu (geometry plus every part’s simulation results, the array count reported), or .png.
set_param, suppress_feature, unsuppress_featureEdit a [Param] value or toggle a feature’s suppression on a history-backed part and regenerate. The result is the regeneration report; a failure keeps the previous geometry and names the feature.
save_document, load_documentWrite the whole model — tabs, parts with their histories, assemblies, mates, annotations, results — as one document file, and read one back.
reloadRe-runs the scene factory after the model’s source changed — the headless equivalent of hot reload. A model that throws leaves the previous scene in place and reports the error.

There is also a resource, engrcad://scene: the whole document as JSON, cheap enough to read on every turn.

Editing, and getting the edits back out

set_param changes the running model; the program’s source stays the truth, so reload discards session edits by design. That is the right default and it leaves one obvious gap — how does the tuning an assistant just found reach the user? Through save_document:

set_param(part: "bracket", feature: "Base", param: "Height", value: 14)
save_document(path: "bracket-tuned.json")

The file is the same envelope documents describes, so it reopens parametric: load_document regenerates the histories and the write tools keep working on the result. The corollary is reported rather than hidden — a part with no construction recipe (a raw mesh, an imported STL, an Sdf) travels as a mesh snapshot, and both tools list those parts by name. A loaded document is a session-lifetime overlay: reload still re-runs the program’s own source and discards it, so there is one answer to where the model comes from rather than two.

Two things worth knowing

stdout is the protocol. The stdio transport is standard output, so a single Console.WriteLine in model code would corrupt the stream and break the client. --mcp mode redirects Console.Out to stderr before the scene factory ever runs and keeps the raw stdout handle for protocol frames, so printing from a model is safe — it shows up in the client’s server log instead.

Nothing is meshed up front. Tessellating a busy scene costs tens of seconds, and most questions need no geometry: list_tabs, list_parts and reload evaluate none at all, describe_part meshes only the part it was asked about, and screenshot and export mesh only what they are about to draw or write.

Hosting it yourself

The pieces are separable, so the tools can be embedded in a larger server or driven over another transport:

var session = new SceneSession(BuildScene); // the live scene, plus reload
var tools = new SceneTools(session); // one method per tool
var options = EngrCadMcpServer.BuildOptions(tools, "bracket");
await EngrCadMcpServer.RunAsync(input, output, tools, "bracket", cancellationToken);