Loon is a Lisp-inspired language for describing vcad geometry. Where create_cad_document takes a verbose JSON IR structure, the create_cad_loon tool accepts Loon source code that expresses the same geometry in a fraction of the tokens. Less tokens means faster inference, lower cost, and more room for the AI to reason about the design rather than fighting JSON syntax.
vcad has two compact formats. Loon is the S-expression language documented here — it uses square brackets, let bindings, and pipe. VCode is a separate line-based opcode format (C 50 30 5, Y 5 10) used for IR serialization. The create_cad_loon MCP tool accepts Loon, not VCode.
Syntax at a Glance
Loon uses square brackets instead of parentheses. The first element is the operator, the rest are arguments.
[cube 50 30 5]
Nesting works as expected:
[difference [cube 50 30 5] [translate 25 15 0 [cylinder 3 10]]]
Comments start with ; and extend to the end of the line:
; A simple plate with a hole
[difference [cube 50 30 5] [translate 25 15 0 [cylinder 3 10]]]
let Bindings
The let form names an expression so you can reference it later. This avoids deep nesting and makes the model readable.
[let plate [cube 50 30 5]]
[let hole [translate 25 15 0 [cylinder 3 10]]]
[let result [difference plate hole]]
[root result "aluminum"]
Variables are resolved by name. A let binding does not create a visible part on its own — you must use root to add it to the scene.
pipe
The pipe form chains operations, passing the result of each step as the last argument to the next. This replaces deeply nested brackets with a flat, readable sequence.
[pipe
[cube 50 50 30]
[fillet 3]
[shell 2]
[translate -25 -25 0]]
[root _ "abs-white"]
This is equivalent to:
[shell 2 [translate -25 -25 -2.5 [fillet 1 [cube 50 30 5]]]]
Each step receives the previous result as its last argument. This works because all Loon operations accept the child/operand as the last argument.
Primitives
Four operations create the basic solids. All dimensions are in millimeters, using vcad's Z-up coordinate system.
[cube 100 60 5] ; box: corner at origin, extends to (sx, sy, sz)
[cylinder 10 25] ; axis along Z, base at origin
[sphere 15] ; centered at origin
[cone 10 0 20] ; axis along Z, r_bottom=10, r_top=0
Booleans, Transforms, and Features
[union a b] ; merge two solids
[difference a b] ; subtract b from a
[intersection a b] ; keep only overlap
[translate 10 0 0 child] ; move by (dx, dy, dz)
[rotate 0 0 45 child] ; Euler angles in degrees
[scale 2 1 1 child] ; scale factors
[fillet 2 child] ; round all edges
[chamfer 1 child] ; bevel all edges
[shell 2 child] ; hollow with wall thickness
Note that the child comes last in transforms and features — this is what makes pipe work cleanly.
JSON vs Loon: A Comparison
Here is a mounting plate expressed as JSON IR passed to create_cad_document:
{
"nodes": {
"0": { "id": 0, "op": { "type": "Cube", "size": [50, 30, 5] }, "name": "Base Plate" },
"1": { "id": 1, "op": { "type": "Cylinder", "radius": 2.75, "height": 10 }, "name": "Hole Tool" },
"2": { "id": 2, "op": { "type": "Translate", "node": 1, "delta": [10, 15, 0] } },
"3": { "id": 3, "op": { "type": "Difference", "left": 0, "right": 2 }, "name": "First Hole" },
"4": { "id": 4, "op": { "type": "Cylinder", "radius": 2.75, "height": 10 } },
"5": { "id": 5, "op": { "type": "Translate", "node": 4, "delta": [40, 15, 0] } },
"6": { "id": 6, "op": { "type": "Difference", "left": 3, "right": 5 }, "name": "Second Hole" }
},
"roots": [{ "root": 6, "material": "aluminum" }],
"materials": {
"aluminum": { "name": "aluminum", "color": [0.9, 0.9, 0.92], "metallic": 0.05, "roughness": 0.3 }
}
}
And here is the same thing in Loon:
[let plate [cube 50 30 5]]
[let hole1 [translate 10 15 0 [cylinder 2.75 10]]]
[let hole2 [translate 40 15 0 [cylinder 2.75 10]]]
[let result [difference [difference plate hole1] hole2]]
[root result "aluminum"]
Five lines vs thirty. For a model with dozens of operations, this difference compounds dramatically. AI models have finite context windows and per-token costs, so Loon lets them build more complex geometry within the same budget.
The AI automatically picks the right tool. For simple single-part models described in conversation, create_cad_document with its higher-level parts interface is often easier. For multi-step constructions where the AI needs precise control over every node, create_cad_loon is more efficient and readable. You can also pass format: "json" to create_cad_loon if you want the output as JSON IR instead of VCode.
A Complete Example: Flanged Hub
A flanged hub with bolt holes using let bindings:
[let flange [cylinder 30.0 10.0]]
[let hub [cylinder 15.0 25.0]]
[let body [union flange hub]]
[let bore [translate 0 0 -1 [cylinder 5.0 27.0]]]
[let bored [difference body bore]]
[let bolt-hole [translate 22 0 -1 [cylinder 3.0 12.0]]]
[let bolt-pattern [circular-pattern 0 0 0 0 0 1 6 360 bolt-hole]]
[let drilled [difference bored bolt-pattern]]
[let result [fillet 1.0 drilled]]
[root result "steel"]
Named bindings make the construction sequence clear: build the body, bore the center, punch bolt holes, fillet. Each line reads like a step in a machining plan.
Calling create_cad_loon
The MCP tool accepts a single source parameter containing the Loon program as a string. An optional format parameter controls the output: "vcode" (the default) returns the evaluated document in VCode format, while "json" returns the full JSON IR document.
{
"source": "[let plate [cube 50 30 5]]\n[root plate \"aluminum\"]",
"format": "vcode"
}
The returned document can then be passed to inspect_cad for analysis or export_cad for file output, just like any other vcad document.
Continue to Inspect and Iterate to learn how the AI verifies its geometry after creating it, or jump to the Loon Language Docs for the complete language specification.