About BrickBuilderMCP
BrickBuilderMCP is an MCP server published by jonx in the Files category: an MCP server that lets an LLM design buildable LEGO models by emitting validated LDraw files — not by clicking around in a CAD app.
The repository has 14 stars and 0 forks, with the last commit 3 months ago. Recent commit activity is the strongest signal that reported issues will get attention.
Install
git clone https://github.com/jonx/BrickBuilderMCPThis server has no ConduID identity, so agent calls to it are not receipted. Pin the version you install and review the source before granting it credentials.
Ask AI
Ask AI about BrickBuilderMCP
Powered by Claude · Grounded in docs
Security checks
Static checks derived from the repository 8 hours ago. Heuristics on the source, not an audit.
- ✓README presentThe repository ships documentation.
- ✓License declaredUsable under a stated license.
- ✓Tests presentA test suite exists in the repository.
- ✓Dependencies pinnedA lockfile pins dependency versions.
- ✓No dynamic code executionSource scan found no eval, dynamic Function, or shell execution.
1 check has not been run yet.
README
LegoMCP
An MCP server that lets an LLM design buildable LEGO models by emitting validated LDraw files — not by clicking around in a CAD app.

A 1,024-piece fortress city designed entirely through these tools — 1,576 verified stud connections, zero floating parts, zero collisions, validated at every stage. The model is examples/fortress_city.ldr; open it in Studio.
Drop it into Claude Desktop, ask Claude "build me a small red house on a tan baseplate," and the LLM gets 57 semantic tools, 6 prompts, and 5 reference resources covering: real LDraw catalog (24,009 parts), buildability checks (no floating / no collisions / no overlaps), connection-aware bonding, builder mode for piece-by-piece assembly, persistent projects, autosave, inline render previews in the chat, an interactive in-chat 3D viewer (MCP Apps), and a debug toolkit (render_validation, inspect_part, collision_detail, describe_errors). The output is a real .ldr / .mpd file you can open in BrickLink Studio or LeoCAD.
Status: early days, but the foundation is now load-bearing. The pipeline runs end-to-end — install, talk to Claude, get an exportable
.ldr— and the connection model is enforced for the entire 24k-part catalog: every placement reports what it actually clutches, strict mode rejects physically impossible positions, and the validator's ground truth is real stud↔receiver mating (in both directions — hanging parts under overhangs is a first-class connection). Structured architecture (walls, towers, gates, roofs — the fortress above) builds reliably through the helpers.Designing LEGO models with an LLM is still hard. The model has to reason about 3D geometry, brick connectivity, stagger / bond patterns, color, and proportion, and keep all that consistent across dozens of tool calls. Organic shapes, curves, and studs-not-on-top techniques are not yet within reach of the shipped tools (the connection model is stud-only — pins, clips, and brackets are Phase 2). The semantic toolkit is what makes any of this possible; getting it to be fluent is the actual research problem.
The renderer, the catalog, the validation/feedback loop, the import/export path, and the CLI are the parts that are working well today.
Table of contents
- Why this exists
- The non-negotiable rules
- Requirements
- Install & configure Claude Desktop
- Inline preview modes
- Coordinate convention
- The tools, grouped
- Prompts (slash menu)
- Resources
- Run as a standalone agent
- Standalone CLI:
render/bom/instructions - Development
- Known limitations
- License
Why this exists
Letting an LLM drive a CAD app via screen automation fails — too many micro-actions, no spatial ground truth, errors compound. LegoMCP gives the LLM a semantic API: every action is a typed function call, every change is validated against real LEGO connection rules, every state is reversible. The CAD app becomes the viewer; the MCP server is the source of truth.
The non-negotiable rules
The toolchain enforces these so what the LLM exports is actually buildable:
- Connection rule — every part either sits on the ground (y ≈ 0) or has at least one stud-receptor mating with a part directly above or below. Both directions are real connections: sitting on studs below or hanging from receivers above (building downward is fine). Merely resting on another brick without stud alignment — e.g. half a stud off, or a 1-wide brick centered on a 2-wide one — is NOT a connection and is reported as
floating. Enforced for every part in the catalog, not a hardcoded subset. - Anchored rule — connected islands must reach the ground via a chain of stud connections (in any direction). A "tower" whose bricks touch each other but the whole thing levitates is reported as
unanchored. - No overlap — bricks must not occupy the same AABB.
add_part(strict=True)rejects overlapping and unconnected placements at insertion time, and everyadd_part/move_part/rotate_partresult carries aconnectivityreport +warningsso problems surface immediately, not at the nextvalidate_model. - Real geometry — AABBs, stud positions, and dimensions come from actually-parsed LDraw
.datfiles (recursively through subparts andstug-NxNstud-group primitives). A "Brick 2x4" really is 80 LDU × 40 LDU × 24 LDU with 8 studs at the correct positions. - Cohesion rule — connected-to-ground is not the same as holding together: a forest of 1x1 columns passes rules 1–2 while being hundreds of separate objects.
validate_modeltherefore also reportsstructure(rigid bodies, single-stud joints, articulation points, spanning ratio) and a top-levelstructurally_soundthat requires the model to be ONE rigid body.consolidate_bricksconverts 1x1 voxel designs into bonded brickwork (running bond in both axes) automatically.
Requirements
- macOS (tested) or Linux. Windows should work via WSL; not tested.
uv— Python toolchain manager.brew install uv(macOS) orcurl -LsSf https://astral.sh/uv/install.sh | sh.- ~150 MB disk for the LDraw parts library (one-time download).
- Claude Desktop for the inline-preview workflow. The standalone agent works without it but needs an
ANTHROPIC_API_KEY.
Install & configure Claude Desktop
# 1. Clone & install
git clone <this repo> LegoMCP && cd LegoMCP
uv tool install --from . lego-mcp
# 2. Download the full LDraw parts library (~135 MB, one-time, a few minutes)
lego-mcp install-library
# 3. Wire into Claude Desktop. Edit:
# ~/Library/Application Support/Claude/claude_desktop_config.json
{
"mcpServers": {
"lego": {
"command": "/Users/<you>/.local/bin/lego-mcp",
"env": {
"LEGO_MCP_INLINE_MODE": "file_url"
}
}
}
}
Replace <you> with your username. Quit Claude Desktop entirely (⌘Q) and reopen so it spawns the new server.
Verify in any chat: type / — you should see a lego group with the start, build, from_plans, from_image, rescue, and techniques prompts.
Renders live here
~/Library/Application Support/lego_mcp/renders/
Every render_model / render_progress / render_validation call writes <timestamp>_<model>.png (history preserved) and overwrites latest.png. Override with LEGO_MCP_RENDERS_DIR env var.
Open the latest at any time:
open "$HOME/Library/Application Support/lego_mcp/renders/latest.png"
Preview.app auto-refreshes on file change, so leave it open for live updates.
Inline preview modes (env var)
Set via LEGO_MCP_INLINE_MODE in the Claude Desktop config's env block.
| Mode | What lands in the chat text | Bytes per render | Use when |
|---|---|---|---|
data_uri (default) |
 |
~16 KB | You want inline previews to work in any MCP client (web, Desktop, future clients). |
file_url (recommended for Claude Desktop) |
 |
~150 bytes | You're on Claude Desktop, which renders local file URLs. ~200× cheaper. |
none |
nothing | 0 | You only want the LLM-vision MCPImage block; no human preview in chat. |
The MCPImage block (Claude's vision input) is always present regardless of mode — only the human-facing preview text changes.
Coordinate convention (LDraw)
- Right-handed, -Y is up.
- 1 stud = 20 LDU wide. 1 plate = 8 LDU tall. 1 brick = 24 LDU tall (= 3 plates).
- Part origins are at the center of the bottom face.
- A baseplate (3811) at y=0 has its top face at y=-4. A brick sitting on it has its bottom face at y=-4.
- Stack a brick on top of another by subtracting 24 from y.
- A "Brick A x B" has its longer axis along +X. So a 2x4 brick is 80 LDU (X) × 40 LDU (Z) × 24 LDU tall.
Rotations are named, not matrices: identity, rot90y, rot180y, rot270y, rot90x, rot90z.
The tools, grouped
57 tools total. The recommended order of preference when the LLM is building: high-level helpers → connection-guaranteed placement (find_valid_placements + add_part_at_placement, place_on_top) → raw add_part only as a debug fallback. Each call shows the most useful arguments; full signatures are in the tool docstrings.
Model state
| Tool | What it does |
|---|---|
create_model(name, include_manual=True) |
Reset to a fresh empty model. The result carries getting_started — the building manual (coordinates, connection rules, workflow, tool order) — so the LLM gets the rules at the exact moment a build begins. The same manual ships as MCP server instructions at connection time. |
list_parts(limit, subassembly) |
List current parts (optionally filtered to one subassembly). |
consolidate_bricks() |
Re-tile 1x1 voxel constructions into bonded standard brickwork: larger bricks that CROSS the seams of the layer below (running bond in both axes). Same shape/colors, ~3x fewer parts, one rigid body. Saves checkpoint before_consolidate. |
bill_of_materials(subassembly, bricklink=False) |
The orderable parts list: quantities per (part_id, color) with part + color names, sorted most-needed first. bricklink=True also returns a BrickLink Wanted List XML (bricklink_xml) you can upload to order; colors without a verified BrickLink mapping are flagged in bricklink_warnings rather than guessed. |
Placement (raw)
| Tool | What it does |
|---|---|
add_part(part_id, color, x, y, z, rotation, strict=False) |
Explicit placement. strict=True rejects overlap and unconnected placements at insertion. Result includes connectivity (mated parts, studs engaged, grounded) + warnings. |
remove_part(instance_id) |
Delete by id. |
move_part(instance_id, x, y, z) |
Reposition. Reports connectivity at the new position. |
rotate_part(instance_id, rotation) |
Change orientation. Reports connectivity in the new orientation. |
High-level helpers (preferred)
These encode real LEGO masonry/architecture techniques so the LLM doesn't have to compute stagger/corners/seams by hand. Strict-grid by default; reject off-grid coords.

build_wall_segment(..., bond="running") — each row's seams shift by half a brick from the row below.
| Tool | What it does |
|---|---|
build_wall_segment(x0, z0, x1, z1, height_rows, color, bond, base_y, palette) |
Straight wall with running / stretcher / stack bond. End-fills with shorter bricks; tracks seams so adjacent rows don't share them. |
build_room(x_min, z_min, x_max, z_max, height_rows, color, bond, base_y) |
Rectangular hollow room. 4 staggered walls + 2x2 corner columns. |
build_perimeter(points, height_rows, color, bond, base_y) |
Closed orthogonal outline (any rectilinear footprint from a plan/image). Alternates corner ownership by row. |
build_wall_with_openings(x0, z0, x1, z1, height_rows, color, openings=[...]) |
Straight wall with rectangular / round-arch / lancet openings. |
build_stepped_gable_roof(...) / build_stepped_pyramid_roof(...) |
Connector-aware roofs. |
build_volume(regions, base_y) |
The generic builder: describe any shape as ordered cell regions (box/ring/pyramid/gable/slab (plate floors/roofs over hollow rooms)/outline (arbitrary footprints), subtract regions for doors/tunnels, fill_color for glass) and the bonding engine tiles it as one interlocked body — woven corners and bridged lintels emerge from scoring, not from per-shape code. |
build_floor(x_min, z_min, x_max, z_max, y, color, part_id) |
Tile a rectangle with plates. |
build_corner(x, z, height_rows, color, brick_part) |
One-corner column (for hand-built rooms). |
place_on_top(base_id, new_part_id, color, stud_offset_x, stud_offset_z, rotation) |
Stud-grid stacking from a known anchor. |
place_next_to(reference_id, new_part_id, color, side, stud_offset, rotation) |
Flush placement (north/south/east/west of an existing part). |
repeat_pattern(part_id, count, dx, dy, dz, ...) |
Array of identical parts along a line. |
Catalog & search
| Tool | What it does |
|---|---|
search_parts(query, limit=20) |
Keyword search over the 24k catalog. AND-of-tokens; use single concrete words ("arch", "wheel", "window"). |
get_part_info(part_id) |
Dimensions, stud count, bbox_local, AND for slopes an orientation block (high_edge, low_edge, plain-text summary) so you don't have to guess after rotation. |
list_colors() |
The 22 named colors + LDraw IDs. |
parts_that_mount_on(part_id, limit=20) |
Reverse mount search. Footprint-bucketed index over the full catalog; first call builds index (~1s), then queries are ~ms. |
Connections
| Tool | What it does |
|---|---|
find_connections(a, b, full_nesting_only=False, min_studs_matched=1) |
Every valid placement of B-on-A and A-on-B. |
find_valid_placements(part_id, near_part_id, limit=20) |
World-coordinate placements that REALLY connect to an existing instance — on top and hanging underneath, pre-filtered against collisions with the rest of the model, sorted by studs engaged. Each entry carries a token. |
add_part_at_placement(token, color) |
Place a part at a find_valid_placements result — strict-checked, zero coordinate math. The preferred way to attach a single part. |
suggest_next_brick_for_wall(subassembly) |
Heuristic next-brick suggester (Phase 1 stub). |
Subassemblies
| Tool | What it does |
|---|---|
set_current_subassembly(name) |
Tag subsequent additions with a group name. |
list_subassemblies() |
Names + counts. |
remove_subassembly(name) |
Delete every part with the tag. |
clone_subassembly(src, dst, x_offset, y_offset, z_offset) |
Duplicate a built component. |
mirror_subassembly(src, dst, axis, plane_offset) |
Bilateral symmetry — build half a cathedral, mirror it. |
analyze_assembly_ports(subassembly) |
Exposed studs/receivers on a built module. |
find_subassembly_connections(movable, target) |
Where one module can connect to another. |
move_subassembly(name, dx, dy, dz) |
Translate a whole group. |
Validation & debug
| Tool | What it does |
|---|---|
validate_model(max_errors=200, check_support=True) |
The 9-field report: parts, connections, collisions, unknown_parts, floating, unanchored, grid_alignment_errors, vertical_seam_score, wall_bond_quality. Errors include suggestion strings. |
inspect_part(instance_id, neighbor_studs=2) |
Focused diagnostic for ONE part: position, AABB, rotation, subassembly, validation flags, who it collides with (with overlap region + smallest separation), who it's connected to, who's nearby in XZ. |
collision_detail(part_a, part_b) |
For a flagged pair: overlap AABB, volume, smallest separating translation per axis, one-line "Move part X by Y LDU on Z to clear" suggestion. |
describe_errors(max_errors=10) |
Bulk-enriches every validate_model error with inspect_part / collision_detail context in one round-trip. |
Rendering
All four return [markdown_preview, summary_dict, MCPImage] — the human sees the inline image, the LLM sees the image via its vision channel.
| Tool | What it does |
|---|---|
render_model(width=800, height=600, color_mode="model", hidden_edges=True) |
Isometric PNG. color_mode: "model" (actual colors), "instance" (one color per piece, for debugging), "row" (color by brick course), "rotation" (color by orientation). |
render_progress(width, height, color_mode, hidden_edges) |
Builder-mode render: built parts solid, unbuilt as ghosts. |
render_validation(width=900, height=700) |
Color-codes each part by validation status: green = ok, red = collision, orange = floating, purple = unanchored, yellow = off-grid, gray = unknown part_id. The one-shot "where are my problems?" view. |
view_latest_render() |
Re-show the most recent render without re-rendering. |
open_viewer(limit=20000) |
Interactive 3D viewer (MCP Apps) — on hosts that support MCP Apps (Claude / Claude Desktop, VS Code Copilot, Goose, …) this renders a live widget in the chat: drag to orbit, wheel to zoom, click the HUD to re-fetch after the model changes. On other hosts it returns the model as box-geometry JSON, and the PNG renders above remain the visual fallback. |
Interactive 3D viewer (MCP Apps)

Claude Desktop building an Eiffel Tower, then opening it in the inline viewer — the widget is a 3 KB self-contained canvas app served as a ui:// resource. It speaks the MCP Apps postMessage protocol (ui/initialize handshake, tool-result notifications) and can call server tools itself: clicking the HUD re-runs open_viewer to refresh the geometry after Claude places more bricks.

render_validation() on a clean build_room: every part is green — no collisions, nothing floating, nothing off-grid. When something is wrong, the offending parts switch color (red = collision, orange = floating, purple = unanchored, yellow = off-grid, gray = unknown part_id) so you can spot problems at a glance.
Stress test: importing a 9,361-part Star Destroyer
import_ldr("10030-1.mpd") followed by render_model(...) on the Imperial Star Destroyer (set 10030) — 9,361 parts across 135 sub-modules, ~7,290 of them carrying non-canonical rotation matrices (the angled hull plating). Import: 0.09 s.

✨ Current renderer. Reads the real triangle/quad geometry inside each .dat file and projects it directly. Bridge superstructure, three engine exhaust rings at the rear, antenna dishes, angled hull plating — all in true shape. Same parsing/transform pipeline that import_ldr uses, just hooked up to the catalog's actual mesh data.

🪨 The first cut of the renderer drew each part as its world-space bounding cuboid. The silhouette and scale are correct — every part is positioned and rotated faithfully, you can clearly see the loader walked the 135-sub-module hierarchy correctly — but slopes, curves, and engine details all became chunky stacks of grey blocks. Useful while debugging the loader / transform pipeline. It looks like a Minecraft Star Destroyer.
Builder mode isn't trained to design anything this complex yet — semantic tools for greebling, wing-plate panel boundaries, and curved-hull techniques are future work. But the import + render path holds up at this scale today: you can load any OMR model, inspect it, run validation on it, and start chipping away.
LDraw model © Roland Dahl (rolandd on the LDraw OMR), redistributable under CCAL 2.0. Downloaded from library.ldraw.org/library/omr/10030-1.mpd. The Imperial Star Destroyer is a trademark of Lucasfilm / The Walt Disney Company.
Persistence: checkpoints / projects / autosave
Three tiers — pick the right one:
| Tier | Tool | Use when |
|---|---|---|
| Checkpoint (in-memory, instant) | save_checkpoint(name) / restore_checkpoint(name) / list_checkpoints() |
Before a risky change. Lost on restart. |
| Project (on-disk, named) | save_project(name) / load_project(name) / list_projects() |
A meaningful intermediate you want to keep. Multi-block MPD + JSON notes. Survives restart. |
| Autosave (automatic) | autosave_status() / restore_autosave() |
Every 25 mutations, the model is dumped to _autosave.mpd. After a crash, call restore_autosave() to pick up where you left off. |
Builder mode
Walk a finished target model piece by piece (for a human or robot to physically assemble).

render_progress() mid-build: the bottom row of the target room is placed (solid red), the rest of the target shows as faded ghosts so you know what's still to come.
| Tool | What it does |
|---|---|
start_builder_session() |
Snapshot becomes the target; built set clears. After this, new add_part calls add to target, not built. |
end_builder_session(mark_all_built=True) |
Exit builder mode. |
builder_status() |
Quick snapshot: total / built / remaining / next_up part / blocked count / complete? |
next_unbuilt_step(limit=1) |
The next placeable unbuilt parts (only treats already-built parts as supporters). |
mark_built(id) / mark_built_batch(ids) / unmark_built(id) / reset_build_progress() |
Walk forward / back / start over. |
plan_build_sequence(subassembly, max_steps=50, start_after=0) |
Compile the whole target into a paged step list. |
next_build_step(subassembly, built_count=0) |
Same as above but per-call (compatible with plan_build_sequence). |
render_progress() |
See built-solid + unbuilt-ghost in one image. |
Notes (multimodal context)
For workflows where Claude is reading a plan / photo and needs to remember dimensions across many turns.
| Tool | What it does |
|---|---|
add_note(key, text) / get_note(key) / list_notes() / remove_note(key) |
Sticky observations keyed by short string. Notes travel with the project (saved by save_project). |
Import / export
| Tool | What it does |
|---|---|
export_ldr(path) / export_mpd(path) |
Write to disk. Relative paths land in the renders dir (so it works under Claude Desktop's read-only cwd). |
import_ldr(path) |
Load an .ldr or .mpd. Multi-block MPDs are flattened with subassembly tags preserved. |
Undo / redo
| Tool | What it does |
|---|---|
undo() / redo() |
O(1) operation-based. Last 500 mutations. |
Prompts (slash menu)
Invoke from Claude Desktop's / menu. Each one seeds the conversation with the right context.
| Prompt | What it does |
|---|---|
start |
Open this first in a new session. Full one-page reference: every tool, the build/validate/render loop, the three save tiers, builder mode. |
build (goal, scale) |
Generic "build X" prompt with the workflow + techniques. |
from_plans (subject) |
Multimodal cathedral-style build: upload plans, decompose into subassemblies, build half + mirror. |
from_image |
Build from an uploaded reference photo. |
rescue (path) |
Load an existing model, clean it up, fix collisions. |
techniques |
Print the LEGO techniques cheat sheet (running/English bond, SNOT, MILS, etc). |
Resources
Read-only references the LLM can fetch with resources/read.
| URI | Content |
|---|---|
lego://techniques |
LEGO building techniques cheat sheet |
lego://coords |
LDraw coordinate convention reference |
lego://workflow |
The build/validate/render loop |
lego://connections |
How stud connections work — the rules behind floating/unanchored |
lego://model/current |
Live state of the current model (JSON) |
Run as a standalone agent (no Claude Desktop)
uv sync --extra agent
export ANTHROPIC_API_KEY=sk-ant-...
uv run python -m lego_mcp.agent "build me a 16x12 stud room, 5 bricks high"
The agent spawns lego-mcp as a subprocess, fetches the tool list over MCP JSON-RPC, hands the tools to Claude via the Anthropic SDK, and loops until Claude stops calling tools. Final model exported to ./agent_build.mpd and rendered.
Standalone CLI
Three one-shot commands that work on an .ldr / .mpd file from the shell — no MCP server, no Claude Desktop:
lego-mcp render <file>— render to PNG (below).lego-mcp bom <file>— the orderable parts list (below).lego-mcp instructions <file>— step-by-step build order (below).lego-mcp rebrick <file>— consolidate 1x1 voxels into bonded brickwork (below).
lego-mcp render
For when you want to render an .ldr / .mpd file from the shell — previewing an OMR download, batching renders for a website, generating a turntable, or just sanity-checking a model someone sent you — without spinning up the MCP server or opening Claude Desktop.
lego-mcp render path/to/model.mpd # writes model.png next to the input
lego-mcp render model.mpd -o /tmp/out.png -w 1600 -H 1100 # explicit output + dimensions
lego-mcp render model.mpd --color-mode instance # one color per piece (debug)
lego-mcp render model.mpd --hidden-edges # show internal contact faces as dotted lines
lego-mcp render model.mpd --view-angle 90 # camera 90° around the Y-axis
lego-mcp render model.mpd --turntable 24 # 24 frames sweeping 360° → model_000.png … model_023.png
lego-mcp render model.mpd --background "#0a0a0a" # dark background
lego-mcp render model.mpd \
--watermark "ACME © 2026" \
--watermark-position bottom-right \
--watermark-color "#ffffff" \
--watermark-opacity 200
lego-mcp render --help # full help
All flags:
| Flag | Default | What it does |
|---|---|---|
<input> (positional) |
— | Path to an .ldr or .mpd file. |
-o, --output PATH |
<input-stem>.png next to input |
Output PNG path. |
-w, --width N |
1200 |
Image width in pixels. |
-H, --height N |
900 |
Image height in pixels. (-h is reserved for help.) |
--color-mode {model,instance,row,rotation} |
model |
model = actual colors; instance = one color per piece; row = color by brick course; rotation = color by orientation. |
--hidden-edges |
off | Draw fully-covered contact faces as dotted guide lines. |
--view-angle DEG |
0 |
Camera azimuth (yaw around Y), pitch constant. Use multiples of 90 for presentation renders — they show the other corners with the same classic iso look. Angles near 45/135/225/315 face the model almost head-on, and orthographic face-on views have no horizontal depth offset, so they read as flat/"sheared" (correct, but hard on the eyes). |
--turntable N |
— | Instead of one image, write N frames sweeping 360°. Each frame goes to <stem>_NNN.png. Overrides --view-angle. |
--background #RRGGBB |
#f5f5f8 |
Image background color. Accepts #fff shorthand. |
--watermark TEXT |
— | Overlay text on the rendered image. |
--watermark-position {top-left,top-right,bottom-left,bottom-right} |
bottom-right |
Where to draw the watermark. |
--watermark-color #RRGGBB |
#282828 |
Watermark text color. |
--watermark-opacity N |
140 |
Watermark alpha (0 transparent .. 255 opaque). |
--watermark-size N |
18 |
Watermark font size in px. |
--watermark-margin N |
14 |
Watermark distance from image edges, in px. |
-q, --quiet |
off | Suppress progress lines on stdout. |
The watermark draws with a TTF font when one is available (Helvetica / Arial / DejaVu probed in order), falling back to PIL's bitmap default on minimal systems so it never hard-fails.
lego-mcp rebrick — make voxel models hold together
lego-mcp rebrick mosaic.ldr -o mosaic_bonded.ldr
# parts: 10000 -> 2133
# rigid bodies: 100 -> 1
# spanning: 53% of elevated bricks rest on 2+ supports
Voxel-style models (mosaics, sculptures, heightmaps) built from 1x1 bricks are really hundreds of separate columns — a 1x1 can only chain, never bond. This re-tiles every layer with larger same-color bricks that cross the seams of the layer below, turning the pile into one rigid body. Put the model on a shared baseplate first so ground-level pieces bond too.
lego-mcp bom — the parts you need to order
Aggregates a model into an orderable parts list: one row per (part_id, color), sorted most-needed first, with a piece total. The same data the bill_of_materials MCP tool returns in-chat.
lego-mcp bom model.ldr # human-readable table (default)
lego-mcp bom model.ldr -f csv -o parts.csv # spreadsheet
lego-mcp bom model.ldr -f json # structured (scripting)
lego-mcp bom model.ldr -f bricklink -o wanted.xml # BrickLink Wanted List to upload & order
| Flag | Default | What it does |
|---|---|---|
<input> (positional) |
— | Path to an .ldr / .mpd file. |
-f, --format {table,csv,json,bricklink} |
table |
Output format. bricklink writes a Wanted List XML you upload at bricklink.com. |
-o, --output FILE |
stdout | Write to a file instead of printing. |
The BrickLink export maps the colors it can verify and flags the rest as warnings (printed to stderr, and left as a comment in the XML) rather than guessing a wrong color id — so double-check any flagged lots before ordering.
lego-mcp instructions — step-by-step build order
Compiles a finished model into a support-respecting assembly order: baseplates and ground parts first, then every part once something it rests on is already placed. (In-chat, the plan_build_sequence / next_build_step tools and the interactive builder mode — start_builder_session → next_unbuilt_step → mark_built → render_progress — do the same.)
lego-mcp instructions model.ldr # full numbered build order
lego-mcp instructions model.ldr --max 20 # first 20 steps
lego-mcp instructions model.ldr --start 20 --max 20 # next page
lego-mcp instructions model.ldr --json # full step payloads (positions, supports)
One-liner recipes
# Generate a hero image for a model with a tagline
lego-mcp render hero.mpd -o hero.png -w 1920 -H 1080 \
--watermark "Built with LegoMCP" --watermark-opacity 90
# Compare two color modes side-by-side (re-run with different --color-mode)
lego-mcp render m.mpd -o m_model.png --color-mode model
lego-mcp render m.mpd -o m_instance.png --color-mode instance
# Spin a 36-frame turntable for an animation
lego-mcp render m.mpd --turntable 36 -o /tmp/spin.png
# → /tmp/spin_000.png … /tmp/spin_035.png — feed into ffmpeg / a GIF tool
Development
uv sync --extra dev --extra agent
uv run pytest # 128 tests including a real MCP stdio handshake
Architecture and decisions live in NOTES.md.
Project layout
src/lego_mcp/
├── server.py # FastMCP tools + state + tool dispatch
├── parts.py # LDraw .dat parser + catalog + index cache
├── render.py # Built-in isometric renderer w/ studs + sub-faces
├── helpers.py # build_wall / build_room / build_floor / place_on_top / ...
├── connections.py # Stud-receptor mating combinatorics
├── connection_graph.py # Floating + unanchored detection via BFS
├── mount_index.py # Reverse mount search (footprint-bucketed)
├── connectors.py # Typed connector model (Phase 1: stud-only)
├── assembly_ports.py # Module-level exposed-port analysis
├── build_steps.py # Build sequencing (whole + partial-vs-target)
├── prompts.py # 6 prompts + 4 resources
├── agent.py # Standalone Claude-driven agent
└── __main__.py # `lego-mcp` CLI: server / install-library / --help
tests/ # 128 tests, pytest
Known limitations
- Slope renderer is AABB. Slopes draw as cuboids. Use
get_part_info(slope_id)'sorientationblock to reason about slope direction — that's the bigger reasoning win. True wedge geometry is a planned follow-up. - Non-canonical-rotation parts are drawn as oriented cuboids — the matrix is preserved, the box rotates correctly, but those parts don't participate in stud-mating connection analysis (the grid only makes sense axis-aligned). Fine for imported display, intentionally limited for new construction.
- Search is keyword-AND. "vehicle base car chassis" returns 0; "chassis" alone returns lots. Workaround: use single concrete words. Semantic search is on the roadmap.
- Connection model is stud-only. Pins / hinges / axles / clips need
!LDCAD CONNparsing (LDCad-extended LDraw metadata). Phase 2. mount_indexranking is footprint-naive. Often surfaces minifig-leg variants for small targets because they happen to share the footprint. A "kind=" filter (brick/plate/tile vs accessory) would help.- No physics. A structurally-unsound model that's geometrically connected still passes validation.
See NOTES.md for the full decision log + deferred items.
License
MIT. See LICENSE.
README fetched from the repository 8 hours ago. The original is authoritative.