b123d-print
Design 3D-printable parts with an AI assistant — any assistant. You describe a part (photos, caliper readings); the model writes a build123d Python script; b123d-print builds it in a sandbox, renders it, cuts cross-sections, runs print checks, and exports the formats you pick: STEP, STL, 3MF, PNG.
| Two-part box for an IR transceiver board, with the board stand-ins | Section through the LED: lid cone, board, clearances |
| Outdoor sensor case: dovetail back plate + support-free cover | Wall plate: section just past the face, where the wall check looks |
Scope: parts for FDM 3D printing, nothing else. Everything is built around a printed part: print orientation with the bed at Z=0, hole compensation, mating clearance, and overhang, wall-thickness and overlap checks. It is not a general CAD framework.
What's in it
b123d-printpackage + CLI — export, 6-view renders, colored multi-part cross-sections, and print checks: overlap between mating parts, overhang in print orientation, wall thickness around cuts, probes ("is the roof closed?").b123d-print-mcp— an MCP server, so it works from Claude Desktop, Claude Code, Gemini CLI, Cursor, VS Code, Codex and other MCP clients. Part scripts are model-written code: they run in a sandbox with a timeout and no network.- The workflow, model-neutral — MCP prompts plus an
AGENTS.mdany agent can read. The rules come from real prints; one exists because a part broke through a 0.05 mm skin that no render showed. - Examples — four parts designed this way and printed, plus a hole calibration coupon, in
src/b123d_print/examples/.
Quick start
-
Install uv. It fetches Python 3.12 and every pinned dependency for you.
-
Add the server to your MCP client — Claude Desktop shown, others below:
{ "mcpServers": { "b123d-print": { "command": "uvx", "args": ["--from", "git+https://github.com/KD5RYN/b123d-print", "b123d-print-mcp", "--workspace", "/Users/you/parts"] } } }
-
Restart the client and ask: "Design a 40 mm round spacer, 10 mm tall, with an M3 hole." Files land in
/Users/you/parts/parts/<name>/out/.
The first start downloads a few hundred MB (mostly OpenCascade, the geometry kernel) and can take a few minutes; later starts are fast. See Troubleshooting if your client gives up waiting.
Images:
render_previewandsectionreturn PNG images. The model can only check its own work if your client passes tool-result images to the model. Claude Desktop and Claude Code do; check your client's docs for others. Without it the checks still run, but the model designs blind — look at the PNGs inout/yourself.
Ways to use it
| Method | When | Sandbox |
|---|---|---|
MCP server via uvx |
any MCP client; the normal way | yes (macOS; Linux with bubblewrap) |
| MCP server in Docker | strongest isolation, or Windows | the container |
| Claude Code plugin | Claude Code; adds a skill that points at the workflow | yes |
| CLI / Python | agents that run shell commands, or by hand | no — runs your own scripts |
Connect your MCP client
Every client runs the same command:
uvx --from git+https://github.com/KD5RYN/b123d-print b123d-print-mcp --workspace <folder>.
The workspace is the folder that holds your parts (parts/<name>/<name>.py); it is created
if missing. Without --workspace the server uses $B123D_PRINT_WORKSPACE, else the folder the
client starts it in.
Claude Desktop
Settings → Developer → Edit Config (claude_desktop_config.json): the block in
Quick start. Always pass --workspace. If the server fails with "uvx not
found", use the full path from which uvx as command (desktop apps don't see your shell's
PATH).
Claude Code
claude mcp add b123d-print -- uvx --from git+https://github.com/KD5RYN/b123d-print b123d-print-mcp --workspace ~/parts
Or the plugin (MCP server + skill; uses the project folder as workspace), inside Claude Code:
/plugin marketplace add KD5RYN/b123d-print
/plugin install b123d-print@b123d-print
Gemini CLI
~/.gemini/settings.json (or .gemini/settings.json in a project):
{
"mcpServers": {
"b123d-print": {
"command": "uvx",
"args": ["--from", "git+https://github.com/KD5RYN/b123d-print", "b123d-print-mcp", "--workspace", "/Users/you/parts"]
}
}
}
Cursor
~/.cursor/mcp.json, or .cursor/mcp.json in a project: the same mcpServers block as for
Gemini CLI.
VS Code (Copilot agent mode)
.vscode/mcp.json:
{
"servers": {
"b123d-print": {
"type": "stdio",
"command": "uvx",
"args": ["--from", "git+https://github.com/KD5RYN/b123d-print", "b123d-print-mcp", "--workspace", "${workspaceFolder}"]
}
}
}
Codex CLI
~/.codex/config.toml:
[mcp_servers.b123d-print]
command = "uvx"
args = ["--from", "git+https://github.com/KD5RYN/b123d-print", "b123d-print-mcp", "--workspace", "/Users/you/parts"]
Other clients
Any client that can start a stdio MCP server: command uvx, arguments as above. To pin a
version, use git+https://github.com/KD5RYN/b123d-print@v0.1.0 once releases exist.
Docker
The container is the sandbox: no network, and it sees only the folder you mount.
git clone https://github.com/KD5RYN/b123d-print && cd b123d-print
docker build -t b123d-print-mcp .
Then point your client at Docker instead of uvx (Claude Desktop shown):
{
"mcpServers": {
"b123d-print": {
"command": "docker",
"args": ["run", "-i", "--rm", "--network", "none",
"-v", "/Users/you/parts:/work", "b123d-print-mcp"]
}
}
}
-iis required (MCP talks over stdin/stdout);--rmremoves the container afterwards.- Your parts and exports appear in the mounted folder (
/Users/you/partsabove). Settings (printer, hole compensation) are kept there too, in.config/b123d-print/, since the container has no lasting home folder. - On Linux, add
"--user", "1000:1000"(yourid -u:id -g) so exported files belong to you. - Released images will also be on GitHub's registry as
ghcr.io/kd5ryn/b123d-print-mcp.
CLI and Python (no MCP)
For agents that run shell commands, and for you:
uv tool install git+https://github.com/KD5RYN/b123d-print
b123d-print init ~/parts && cd ~/parts
b123d-print new bracket
Edit parts/bracket/bracket.py (or let your agent do it), then:
b123d-print build bracket --formats 3mf,png
b123d-print check
| Command | Does |
|---|---|
init [dir] |
create a workspace: b123d-print.toml, AGENTS.md, parts/ |
new <name> [--from <example>] |
scaffold a part from the template or a bundled example |
build [parts] [--variant v] [--formats f] |
export + section views, report sizes |
check [parts] [-v] [--json] |
run the checks; exit code 1 on any failure |
examples / list / config |
bundled examples / parts in the workspace / settings in effect |
AGENTS.md (written by init) carries the workflow for agents that read it, such as Codex and
Cursor; for Claude Code put @AGENTS.md in CLAUDE.md. Running a part script directly also
works: uv run parts/bracket/bracket.py. The CLI does not sandbox: it runs your scripts
like any Python.
How a session goes
Start with one of the MCP prompts where your client supports them —
design_part_from_photos, modify_part, verify_part, calibrate_hole_comp —
or just ask; the server's instructions and the b123d-print://guide resource carry the same
rules. An illustrative, shortened exchange:
You: A case for this PIR sensor, mounted on a wall outside. (photos with calipers)
Model: Dimensions I read: body 69.6 × 24.5 × 19.1 (photo 2, calipers), dome Ø18.6 (photo 3), dome center about 35.5 from the bottom (estimate). Nothing shows how far the dome sticks out. Please confirm or correct before I model.
You: Dome sticks out 8. The rest is right.
Model: (new_part, write_part, build_part, render_preview, three sections, check; fixes a thin wall the check found, checks again) Done: 35.1 × 38.8 × 76.6 mm, valid. The sensor gets 0.6 mm per side, the dovetail 0.2 per face. Prints on its floor without supports (one flat bridge over the groove). Exported 3MF and PNG.
| Tool | What it does |
|---|---|
list_parts |
parts, bundled examples, settings in effect, sandbox in use |
new_part(name, description?, example?) |
scaffold from the template, or copy a bundled example |
read_part / write_part |
read / replace the script (syntax-checked before saving) |
build_part(name, variant?) |
run it: bounding box, volume, validity, dimensions, errors |
render_preview(name, variant?, parts?) |
6-view PNG; parts="assembly" shows the fit |
section(name, plane, at, ...) |
colored cross-section PNG on XY, XZ or YZ |
check(name, variant?) |
validity, overlap, overhang, wall and probe checks |
export(name, variant?, formats?) |
files to parts/<name>/out/ |
Output formats
| File | For |
|---|---|
<name>.py (always) |
the parametric source: change a Dims value, rebuild everything |
.step |
editing in other CAD (Fusion, Onshape, FreeCAD, SolidWorks): an exact solid you can push/pull, fillet and cut, without feature history; changes there don't flow back to the script |
.stl |
any slicer |
.3mf |
slicers that read it (Bambu Studio, OrcaSlicer, PrusaSlicer); keeps units |
.png |
6-view preview, assembly render, section views |
Pick them in your config (formats = ["3mf", "png"]), per run (--formats step,3mf), or with
formats= on the MCP export tool. Default: all four.
A part script
from dataclasses import dataclass
from build123d import Align, Box, Cylinder, Mode, Part
from b123d_print import View, Wall, hole_comp
BED = (Align.CENTER, Align.CENTER, Align.MIN)
@dataclass(frozen=True)
class Dims: # every dimension named, in mm, with its source
W: float = 30.0 # typed measurement
H: float = 8.0
HOLE_D: float = 3.4 # M3 clearance, target printed size
CBORE_D: float = 6.5
CBORE_DEPTH: float = 3.0
HOLE_COMP: float = hole_comp(__file__) # from YOUR config, default 0
VARIANTS = {"": {}, "wide": dict(W=40.0)} # overrides only
def build(d: Dims) -> Part: # print orientation, bed at Z=0
p = Box(d.W, d.W, d.H, align=BED)
p -= Cylinder((d.HOLE_D + d.HOLE_COMP) / 2, 50, mode=Mode.PRIVATE)
p -= Cylinder((d.CBORE_D + d.HOLE_COMP) / 2, d.CBORE_DEPTH, align=BED, mode=Mode.PRIVATE)
return p
def views(d): # sections saved with every export
return [View("XZ", 0.0, "through the hole")]
def checks(d): # how much material is left around the counterbore?
return [Wall("", "XY", d.CBORE_DEPTH / 2, near=(0, 0), radius=d.W, label="around the counterbore")]
if __name__ == "__main__":
from b123d_print import run
run(__file__)
Optional assembly(d) returns the parts in assembled position plus stand-ins for what they
mate with (a PCB, a sensor); the overlap check makes sure nothing printed hits them. See
esp_ir_blaster and
tuya_motion_sensor_outdoor_case.
The modeling API itself is documented in the build123d docs.
Settings and hole compensation
FDM printers print small holes undersize, by an amount that depends on printer, nozzle,
material and slicer. b123d-print never assumes a value: hole_comp defaults to 0.
Print the coupon (new_part("coupon", example="hole_coupon") over MCP, or
b123d-print new coupon --from hole_coupon), measure each hole, and set the average error:
# ~/.config/b123d-print/config.toml (`b123d-print config` shows what is in effect)
[print]
hole_comp = 0.4 # mm added to hole diameters when cutting
clearance = 0.2 # default mating gap
min_wall = 0.8 # Wall checks fail below this unless they set their own limit
max_overhang = 45 # degrees from vertical
formats = ["step", "stl", "3mf", "png"]
A workspace b123d-print.toml overrides the user config; environment variables
(B123D_PRINT_HOLE_COMP, B123D_PRINT_FORMATS, ...) override both.
Printer profile (optional)
By default b123d-print is in generic mode: no printer assumed, 0.8 mm minimum wall, 45° overhang limit. If you set your printer, the model also knows its build volume and nozzle:
b123d-print printer list bambu a1 # search 405 printer models
b123d-print printer use bambu_lab_a1_mini --material petg
- Parts must then fit the build volume in print orientation (
fitcheck; it fails otherwise). - Minimum wall becomes 2 line widths of your nozzle; material notes (PLA vs PETG vs ASA …) reach the model.
b123d-print printer hole-comp 0.4stores your coupon result for that printer + material.b123d-print printer use genericgoes back. Over MCP: theuse_printertool.
Printer dimensions come from OrcaSlicer's printer
library (facts only; see THIRD_PARTY.md). Not listed, or modified? Add your own
~/.config/b123d-print/printers/<name>.toml:
label = "My Ender 3 with 0.6 nozzle"
bed_x = 220
bed_y = 220
height = 250
nozzle = 0.6
# round_bed = true # deltas: bed_x is the diameter
# max_overhang = 50
Materials work the same way in materials/ (label, notes, optional clearance).
The checks
- overlap — intersection volume between every pair of
assembly(d)parts; > 0.01 mm³ fails. - overhang — downward faces in print orientation, angle from vertical (wall 0°, ceiling
90°); > 45° is reported, the bed face is ignored, flat ceilings are marked
bridge.Overhang(part, fail=True)for parts that must print without supports. - wall — thinnest material in a cross-section, optionally only around one cut: across the wall along the surface normal, and between separate outlines (hole to edge). Tips of ordinary corners don't count as thin; knife edges sharper than about 25° do.
- fit — only with a printer profile: the part fits the build volume in print orientation.
- probe — material must be present (or absent) in a small box.
- valid — every printable part is a valid, non-empty solid.
Sandbox
| Platform | Isolation |
|---|---|
| macOS | sandbox-exec: no network, no child processes, writes only to the part's out/ and a scratch folder, home folder unreadable except the workspace |
| Linux with bubblewrap | no network, read-only system, same writable folders, home hidden |
| Linux without bubblewrap, Windows | separate process + audit hook only — install bubblewrap, or use Docker |
| Docker | the container (--network none, only the mounted folder) |
Every build also gets a timeout (default 120 s, --timeout) and a CPU limit. The audit hook
(blocks sockets, subprocesses, native libraries, writes elsewhere) is defense in depth, not a
boundary on its own.
Updating
b123d-print --version (or the version field of the MCP list_parts tool) shows what you run.
Releases and what changed: GitHub releases.
| Installed with | Update |
|---|---|
uvx --from git+https://github.com/KD5RYN/b123d-print ... (MCP clients) |
uvx caches the build: run uvx --refresh --from git+https://github.com/KD5RYN/b123d-print b123d-print-mcp --help once, then restart the client |
a pinned version ...b123d-print@v0.1.0 |
change the tag in your client config, restart the client |
uv tool install git+https://github.com/KD5RYN/b123d-print (CLI) |
uv tool install --force git+https://github.com/KD5RYN/b123d-print |
| Docker | git pull && docker build -t b123d-print-mcp . in your clone, restart the client |
| Claude Code plugin | /plugin marketplace update b123d-print, then restart Claude Code |
After updating:
- Workspaces keep your parts and settings. The workflow text in a workspace's
AGENTS.mdis a copy; refresh it withb123d-print init --update-agents(MCP clients always get the current text from the server). - Printer profiles ship with the package and update with it; your own files in
~/.config/b123d-print/are never touched. - Part scripts: while b123d-print is 0.x the script conventions may change; the release
notes say when, and
b123d-print checktells you what breaks.
Troubleshooting
| Symptom | Fix |
|---|---|
| First start is very slow, or the client gives up | it is downloading the dependencies; run uvx --from git+https://github.com/KD5RYN/b123d-print b123d-print-mcp --help once in a terminal, then restart the client |
Client says uvx not found |
use the full path from which uvx as the command |
| Model never looks at the renders | your client doesn't pass tool images to the model; open out/*.png yourself |
timed out after 120 s |
a slow or endless script; raise --timeout if the part is genuinely heavy |
blocked in part scripts: ... |
the script tried the network, a subprocess or files outside the workspace; part scripts only need build123d |
| Holes print small | calibrate hole_comp with the coupon |
| Linux: sandbox says "audit hook only" | install bubblewrap (apt install bubblewrap); on Ubuntu 24.04 unprivileged user namespaces must also be allowed — or use Docker |
Platforms and status
0.x — early. The part-script convention may still change. CI tests macOS and Ubuntu, the
built package via uvx, and the Docker image on every push. Windows is untested: the MCP
server should run there without the OS sandbox; Docker is the safe route.
Contributing
Issues and pull requests are welcome. To work on it:
git clone https://github.com/KD5RYN/b123d-print && cd b123d-print && uv sync
uv run pytest && uv run b123d-print check && uv run python tests/mcp_smoke.py
The printer table is generated: python tools/sync_orca_printers.py refreshes
src/b123d_print/data/printers_orca.json from OrcaSlicer (records the commit it used).
New checks and example parts should come from something that was actually printed.
Credits
b123d-print is a thin layer over:
- build123d — the Python CAD API every part is written in (Apache-2.0)
- Open CASCADE Technology, via OCP — the geometry kernel (LGPL-2.1 with exception; bindings Apache-2.0)
- lib3mf — 3MF export (BSD)
- OrcaSlicer — printer dimensions for the optional printer profiles (AGPL-3.0; facts only, no files copied)
- matplotlib and NumPy — renders, sections, check math
- Model Context Protocol and its Python SDK (MIT)
- uv — packaging and
uvx
Details and redistribution notes: THIRD_PARTY.md.
License
MIT © KD5RYN and contributors.
Metadata
Release files for b123d-print 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| b123d_print-0.1.0.tar.gz | 71.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| b123d_print-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 154.1 kB
Release files / b123d_print-0.1.0.tar.gz
| Download URL | b123d_print-0.1.0.tar.gz |
|---|---|
| Size | 71.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
6bc1801653d022ef02c0b7876386a28e2832aab19da6c7368866b5388a8ff9b3
|
|
BLAKE2b-256 checksum How to use checksums |
0a3d81b973a3a3fa322a5c54aebc947502742a3200791b1dec9ce88ff4022994
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 17, 2026.
Transparency logRelease files / b123d_print-0.1.0-py3-none-any.whl
| Download URL | b123d_print-0.1.0-py3-none-any.whl |
|---|---|
| Size | 82.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
71a8608e0f90cf51fec72410c007ccd0071afa6a76c17c96cbc3865e3ad2b5fe
|
|
BLAKE2b-256 checksum How to use checksums |
3d7ed4b08d89b7f370dd452a53a477e346a62523781d36465dc1e7d037bde3f1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 17, 2026.
Transparency log