techhand-print-fab
An MCP server for designing, exporting, and inspecting original 3D-printable parts, with optional local slicing and Bambu X1 Carbon print push (per README).
Create local projects and list generated parts.
Generate parametric OpenSCAD and CadQuery scripts from JSON for box, cylinder, tube, plate, L-bracket, mount plate, or custom SCAD.
Export binary STL or geometry-only 3MF meshes to disk.
Run DFM heuristics for walls, holes, overhangs, clearance, and X1C bed size.
Get starting X1C profile notes for materials and BOM/filament/fastener sketches.
Slice STL/3MF to .gcode.3mf with OrcaSlicer/Bambu Studio CLI or produce a Studio handoff when the CLI/presets are missing.
Discover/check Bambu printers and push a sliced job to an X1 Carbon via LAN Developer Mode or Farm Manager, subject to dry-run/confirm/BAMBU_PRINT_ENABLED gates.
Refuses proprietary clones and weapon-part requests; optional TNT ticket attach is available only when enabled.
Provides tools for discovering, monitoring, and pushing print jobs to Bambu Lab X1 Carbon printers, including slicing and status checks.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@techhand-print-fabCreate an L-bracket with holes and export STL"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
techhand-print-fab
Shareable MCP server for original parts: idea → parametric model → STL/3MF → slice → optional print push to a Bambu X1 Carbon.
OpenSCAD is the primary model. A CadQuery script is written beside it and is not executed here. Design export stays on disk. When OrcaSlicer or Bambu Studio's CLI is installed, fab_slice (and fab_bambu_push_3mf) turn that mesh into a .gcode.3mf without opening the slicer GUI. A sliced file can be sent to the printer when you confirm it. TNT is not required.
Standing defaults, unless the part or the call says otherwise: PLA, bed textured_plate, Bambu Lab X1 Carbon, 0.4 mm nozzle.
Install
Python 3.10+. From a checkout:
python3 -m pip install -e .That installs the techhand-print-fab command and pins mcp to the 2.x line (requirements.txt). Use python3 -m pip install -e ".[dev]" when you also want pytest (requirements-dev.txt).
OpenSCAD is optional for the built-in kinds (box, plate, mount plate, cylinder, tube, L-bracket). When openscad is on PATH, or OPENSCAD_BIN points at it, STL/3MF export shells out to it and boolean holes are in the mesh. Without it, those kinds still export from a built-in mesh. The bundled trainer grip files are custom_scad and need OpenSCAD to mesh. See the dogfood section.
CadQuery is not a dependency. model.py is a script for a machine that has CadQuery.
Related MCP server: 3D MCP Server
Run
stdio (Cursor and most local MCP clients):
techhand-print-fabStreamable HTTP, for a connector that wants a URL. Default bind is loopback only:
techhand-print-fab --http --host 127.0.0.1 --port 8765The MCP path is /mcp.
Projects live in FAB_DATA_DIR, or ~/.local/share/techhand-print-fab when that is unset. --data-dir overrides both.
Install as an MCP connector
Cursor, after techhand-print-fab is on PATH. This is the shape in examples/cursor-mcp.json:
{
"mcpServers": {
"techhand-print-fab": {
"command": "techhand-print-fab"
}
}
}Pin a data directory and an import root (for an existing .scad tree such as a grip CAD checkout):
{
"mcpServers": {
"techhand-print-fab": {
"command": "techhand-print-fab",
"env": {
"FAB_DATA_DIR": "/home/me/.local/share/techhand-print-fab",
"FAB_IMPORT_ROOTS": "/path/to/cad-v0"
}
}
}
}From a checkout before the script is on PATH, point Python at src:
{
"mcpServers": {
"techhand-print-fab": {
"command": "python3",
"args": ["-m", "techhand_print_fab"],
"env": {
"PYTHONPATH": "/path/to/techhand-print-fab/src"
}
}
}
}Grok Bot or any client that speaks Streamable HTTP: run techhand-print-fab --http --host 127.0.0.1 --port 8765 and point the connector at http://127.0.0.1:8765/mcp. Do not bind a public interface unless you have your own auth in front. This server has none.
Nothing in that setup calls TNT.
Tools
Tool | What it does |
| Local project. Units are millimeters. |
| Parts already generated. |
| OpenSCAD ( |
| Binary STL on disk. |
| Geometry-only 3MF. Not a Bambu/Orca project and not a toolpath. |
| Wall, hole, overhang, clearance, and 256 mm bed heuristics. |
| Starting notes for PLA, PETG, ABS, ASA, TPU, PA, and PA-CF. |
| Filament mass and a fastener guess from hole diameters. |
| List printers: host, model, state, and AMS when the printer exposes it. Does not print. |
| Nozzle temperature, bed temperature, and job progress. Does not queue a job. |
| STL or geometry 3MF → |
| Slice an unsliced mesh on that same path, then upload a |
backend on fab_param_model is openscad, cadquery, or both (default). OpenSCAD stays the primary file whenever it is written.
Params
kind is box, cylinder, tube, plate, l_bracket, mount_plate, or custom_scad.
Prismatic parts use a corner at the origin: +X length, +Y width, +Z height. Round parts are centered on Z. An L bracket is a base plate plus an upright on the back edge (+Y).
face on a hole is base (drill along Z) or upright (L bracket only; x_mm is along the length and y_mm is the Z height). Cylinder hole x_mm / y_mm are offsets from the axis.
Example (examples/l-bracket.params.json):
{
"kind": "l_bracket",
"length_mm": 40,
"width_mm": 30,
"height_mm": 25,
"thickness_mm": 3,
"material": "PLA",
"clearance_mm": 0.3,
"holes": [
{"diameter_mm": 3.4, "x_mm": 12, "y_mm": 10, "face": "base"}
]
}custom_scad takes scad_body or source_path. source_path may be cad-v0 (the bundled trainer grip) or a .scad file or directory under the project folder or FAB_IMPORT_ROOTS (os.pathsep-separated). A directory becomes one part per file, named {part_name}-{relative-stem}, up to 50 files. A relative include <file.scad> inside that directory is inlined. Absolute includes, ../, use, and import() are rejected.
The bracket above names PLA. Omitting material means the same thing at slice time: PLA. Pass PETG (or another allow-listed material) only when that spool is loaded. examples/plate-pla.params.json is the small standing-default plate.
Call shape:
fab_create_projectwith a name.fab_param_modelwithproject_id,part_name, andparams.fab_slice(exportsmodel.stlwhen the part has no mesh yet), orfab_export_stl/fab_export_3mfif you want the mesh first.fab_dfm_check,fab_x1c_profile_notes,fab_bom_sketchas needed.fab_bambu_push_3mfwhen the.gcode.3mfshould go to the printer. Push slices first if the mesh is newer thanmodel.gcode.3mf.
output_path on export must stay inside the part directory or FAB_EXPORT_ROOTS.
PRINT dogfood: trainer grip CAD v0
Bundled at src/techhand_print_fab/cad_v0/ and installed with the package. Training grip block only. source_path cad-v0 needs no FAB_IMPORT_ROOTS entry.
File | Slug when |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Left and right shells include <grip_shell.scad>. The import step inlines that file. assembly_preview.scad is a pose stub (import_grip is not a module in this set) and is not the STL target.
fab_create_projectwithnameTrainer grip v0and a short description of the original trainer block.fab_param_modelwith thatproject_id,part_nametrainer,source_pathcad-v0, andparams{"material": "PETG"}.fab_list_partsreturns the eight slugs above.fab_dfm_checkontrainer-grip-shell. Header numbers are read from the file: wall about 2.4 mm, clearance 0.25 mm, box 110 × 32 × 120 mm.fab_dfm_checkontrainer-laser-clampwarns on the 0.15 mm diametral clearance.fab_export_stlontrainer-grip-shell.
Step 5 needs OpenSCAD. These parts are not a built-in primitive. The server still writes model.scad. If openscad is missing, the tool returns an error that names OpenSCAD and does not report a mesh or a printer job. Install OpenSCAD, or set OPENSCAD_BIN, and call fab_export_stl again. fab_export_3mf is the same gate. Box, plate, and the other primitive kinds export without OpenSCAD.
Prefer Push stays held. Ticket attach stays the optional extras/tnt package.
Headless slice
Normal parts do not need the Bambu Studio window. fab_slice, and fab_bambu_push_3mf on an unsliced mesh, run a local slicer CLI and write model.gcode.3mf.
A checkout without orca-slicer or bambu-studio on PATH gets the Studio handoff. Tests cover that fallback and a mocked CLI. A machine with the CLI and full presets slices for real.
Install OrcaSlicer or Bambu Studio so
orca-slicerorbambu-studiois onPATH.ORCA_SLICER_BINorBAMBU_STUDIO_BINoverrides that search. An explicit path that is not executable is an error. It is not replaced by another binary onPATH.Call
fab_slice. Whenmachine.json,process.json, orfilament/PLA.jsonis missing or still hasinherits, the server flattens the installed slicer'sresources/profilestree (next to the binary, orFAB_SLICER_PROFILE_ROOT). It looks up Bambu Lab X1 Carbon 0.4 nozzle, 0.20mm Standard @BBL X1C, and Bambu PLA Basic @BBL X1C, dropsinherits, and writes full files. Vendor profiles are not in git. An AppImage does not expose that tree; setFAB_SLICER_PROFILE_ROOTto the extractedresources/profilesdirectory.Those files land in
FAB_SLICER_PRESETSwhen that variable is set, otherwise inFAB_DATA_DIR/slicer-presets/x1c-0.4-pla-textured(~/.local/share/techhand-print-fab/slicer-presets/x1c-0.4-pla-texturedwhenFAB_DATA_DIRis unset). The same step is:
techhand-print-fab --expand-presets \
--profile-root /path/to/resources/profiles \
--out "$FAB_SLICER_PRESETS" \
--material PLA \
--bed textured_plateJeremiah's X1 Carbon plate is Textured PEI, the same plate as the earlier smoke. Stock profiles often tag the sliced file Cool Plate or cool_plate. fab_slice writes Orca curr_bed_type Textured PEI Plate into process.json, then rewrites the selected plate in the 3MF. The standing bed also locks plate_id textured_pei in the tool result, the gcode header, and the plate metadata. bed_type stays textured_plate. A sliced file whose selected plate is still cool_plate or Cool Plate is a slice error and is not kept. Pass bed_type: cool_plate only when the physical plate is the cool plate. auto leaves the plate already stored in the preset.
The server runs:
orca-slicer model.stl \
--load-settings "$FAB_SLICER_PRESETS/process.json;$FAB_SLICER_PRESETS/machine.json" \
--load-filaments "$FAB_SLICER_PRESETS/filament/PLA.json" \
--load-defaultfila \
--arrange 1 \
--slice 0 \
--export-3mf model.gcode.3mf \
--nozzle-diameter 0.4 \
--curr-bed-type "Textured PEI Plate"--curr-bed-type follows the bed you passed: textured_plate → Textured PEI Plate, hot_plate → High Temp Plate, cool_plate → Cool Plate, engineering_plate → Engineering Plate. auto leaves the plate already stored in the preset. Empty bed_type is textured_plate. Empty material uses the part material, then PLA.
profile_applied is true only after that CLI writes Metadata/plate_N.gcode and the plate metadata matches the bed you asked for. This package does not ship a Bambu profile and does not call the Bambu cloud. A .gcode.3mf you sliced yourself keeps profile_applied false.
If the CLI is missing, or the profile tree cannot be flattened, the tool writes studio-handoff/ and preset_files. Each row is machine.json, process.json, or filament/<MATERIAL>.json with status ok, missing, inherits, or invalid. A file that still has inherits is not sliced, even when it also has machine_start_gcode. If the CLI runs and fails, mode is slice_error, printer_dispatched stays false, and the handoff is still written. Printer secrets are stripped from the slicer process environment.
A newer model.stl or model.3mf replaces a stale model.gcode.3mf on the next slice or push. A newer model.gcode.3mf is sent as-is.
Bambu X1 Carbon print push
PLA, PETG, ABS, ASA, TPU, PA, and PA-CF are allowed. PLA is the default. Notes are starting temperatures, not a slicer profile.
The printer runs a sliced .gcode.3mf. fab_export_stl and fab_export_3mf write geometry only. Raw .gcode is not queued.
The tool contract is also in openapi/print-fab.openapi.json. That file describes MCP tools. It is not a second HTTP API. Streamable HTTP is still the MCP endpoint at /mcp.
Why LAN Developer Mode
Bambu documents Developer Mode as the third-party control channel: MQTT on port 8883 and FTP on port 990, after LAN Only mode is on. That is the path this server uses for one X1 Carbon.
Farm Manager is optional. Its local REST API listens on port 8888. Use it when BAMBU_TRANSPORT=farm, or when BAMBU_FARM_URL is set and no LAN host is configured. A printer bound to Farm Manager closes its own MQTT port, so LAN and farm are two attachments. The cloud API is unused.
fab_bambu_discover reports path: lan_developer_mode and a why string with this choice. With BAMBU_LAN_HOST set it sends the printer's TCP 3000 detect frame. That probe does not use BAMBU_ACCESS_CODE, BAMBU_SERIAL, a farm token, or BAMBU_PRINT_ENABLED. No other BAMBU_* variable is required for the detect path. SSDP (UDP 2021 and 1990) runs only when you pass ssdp: true or set BAMBU_DISCOVER_SSDP=1, and that scan also needs no secrets. When BAMBU_ACCESS_CODE matches that serial, a read-only MQTT pushall fills state and ams.
Printer setup
On the X1 Carbon, turn on LAN Only mode.
Turn on Developer Mode and accept the notice on the printer.
Record the LAN IP, the access code, and the serial number into the secret store. Do not paste them into chat.
Auth (orange-secret / vault inject)
Secrets are process environment variables. This package does not read a vault file and does not write secrets into the project directory. Inject them when the MCP process starts (orange-secret, an org vault agent, a systemd EnvironmentFile, or the env block of the MCP client). Never paste an access code or token into chat, a ticket, a PR, or git.
Variable | Secret | Purpose |
| yes | LAN access code. MQTT and FTPS password. The username is always |
| yes | Farm Manager bearer token. Prefer this over the password. |
| yes | Farm Manager password, used only when |
| no | Farm Manager user paired with the password. |
| no | Private IP or |
| no | Printer serial. MQTT topics are |
| no |
|
| no |
|
| no |
|
| no |
|
| no |
|
| no | Value for |
| no | CA bundle for the farm server certificate. |
| no | Optional client certificate for mTLS. |
| yes | Optional client key for mTLS. |
| no |
|
| no | Directory that holds a sliced |
| no | OrcaSlicer CLI. Overrides |
| no | Bambu Studio CLI. Used when |
| no | Directory of full |
| no | Installed |
examples/cursor-mcp.bambu.json ships those names with empty values. Fill them in the client config on the machine that runs the server.
A live push also needs dry_run: false and confirm: true. The default dry_run: true uploads nothing and queues nothing.
LAN upload uses implicit FTPS on port 990 and stores the file in /model when that directory exists. The start command is MQTT print.project_file with url ftp:///model/<file>.gcode.3mf and param Metadata/plate_1.gcode. The md5 field is sent empty. printer_dispatched becomes true only after that command's ack is success, or after Farm Manager returns a task id. An upload that the printer does not accept stays printer_dispatched: false. Profile notes ride along in the tool result. profile_applied is true only when this process sliced with the local CLI. It stays false for a file you sliced elsewhere. sliced_plate is Textured PEI Plate and plate_id is textured_pei for the standing textured bed. This package does not ship a Bambu profile and does not call the Bambu cloud.
PRINT dogfood: test plate
PRINT runs this list and reports PASS or FAIL for each step to PRODUCT and CREW. Include mode, printer_dispatched, and message. Omit every secret. Jeremiah owns the physical confirm on the first live push. PRINT does not mark that physical check PASS.
Safe steps (no live push):
Set
BAMBU_LAN_HOSTto the printer's private IP. LeaveBAMBU_ACCESS_CODE,BAMBU_SERIAL, farm secrets, andBAMBU_PRINT_ENABLEDunset for the discover probe. PASS: the process starts and no secret is in the chat transcript. FAIL: a secret was pasted into chat or committed.fab_bambu_discoverwithBAMBU_LAN_HOSTset and no access code, serial, farm token, or vault. The TCP 3000 probe does not need those. PASS: the call returns,printer_dispatchedis false, and the row hashost.reachable: falseis a pass when this process cannot open port 3000 (the printer is not on this network).modelmay be empty in that case.statestays empty andamsnull until a later secret inject. FAIL: the call raises, orprinter_dispatchedis true.fab_bambu_statusagainst an unreachable host, still with no vault. PASS for this fix:modeisprint_error,printer_dispatchedis false, and the tool returns instead of raising. A live reading (modestatus, withnozzle_c,bed_c,state, andprogress_percent) waits onBAMBU_ACCESS_CODEandBAMBU_SERIAL. Injecting those from orange-secret or the vault is out of scope for this fix. Do not paste them. FAIL: the call raises, orprinter_dispatchedis true.fab_create_projectwithnameTest plate.fab_param_modelwithpart_nameplateandparams{"kind":"plate","length_mm":20,"width_mm":20,"thickness_mm":3,"material":"PLA"}. Omittingmaterialis the same standing default.fab_sliceonplate(or skip this and let step 7 slice). Withorca-slicerorbambu-studioinstalled and itsresources/profilestree visible (orFAB_SLICER_PROFILE_ROOTset), you do not hand-copy JSON. PASS:modeissliced,slicedis true,materialisPLA,bed_typeistextured_plate,plate_idistextured_pei,sliced_plateisTextured PEI Plate,nozzle_mmis0.4,profile_appliedis true, andprinter_dispatchedis false. The sliced 3MF selected plate istextured_peiorTextured PEI Plate. PASS without the CLI or without a profile tree:modeisstudio_handoff,printer_dispatchedis false, andpreset_filesnamesmachine.json,process.json, andfilament/PLA.jsonwith statusmissingorinherits. FAIL:printer_dispatchedis true,plate_idiscool_plate, the selected plate is Cool Plate, or the tool claims a slice it did not write.fab_bambu_push_3mfwith thatproject_idandpart_name(defaultdry_runtrue). PASS with the CLI:modeisdry_run,slicedis true,request.print.bed_typeistextured_plate,request.print.commandisproject_file, andprinter_dispatchedis false. PASS without the CLI:modeisstudio_handoffandprinter_dispatchedis false. FAIL:printer_dispatchedis true.Only when step 6 was a Studio handoff: open the STL in Bambu Studio or OrcaSlicer. Pick X1 Carbon, a 0.4 mm nozzle, textured PEI, and PLA. Check the notes against the spool datasheet. Slice. Export
plate.gcode.3mfinto the part directory or a folder listed inFAB_EXPORT_ROOTS.fab_bambu_push_3mfwithfile_pathof that sliced file,materialPLA, anddry_runtrue. PASS:modeisdry_run,printer_dispatchedis false,request.print.commandisproject_file,request.print.bed_typeistextured_plate, andrequest.print.md5is empty. The access code is not in the result. FAIL: a file was uploaded orprinter_dispatchedis true.
Live push (Jeremiah at the printer):
Set
BAMBU_PRINT_ENABLED=1. Jeremiah callsfab_bambu_push_3mfwith that sliced file,materialPLA,bed_typeleft empty (textured plate),dry_runfalse, andconfirmtrue. Tool PASS:printer_dispatchedis true,dry_fireis false, andack_resultissuccess(LAN) ortask_idis set (Farm Manager). Tool FAIL:printer_dispatchedis false, including a timeout after upload. The file may already be on the printer; the tool does not claim the job was queued. Physical confirm: Jeremiah checks the printer panel. PRINT reports his confirm to PRODUCT and CREW and does not invent it. Prefer Push does not apply.
Farm Manager
Set BAMBU_TRANSPORT=farm, BAMBU_FARM_URL, and BAMBU_FARM_TOKEN (or BAMBU_FARM_USERNAME and BAMBU_FARM_PASSWORD). fab_bambu_discover calls GET /devices and returns host, model, state, and AMS when the device report includes them. fab_bambu_status reads that same report. fab_bambu_push_3mf uploads with POST /file/upload3mf and creates the job with POST /task. queue_only: true sends task_print_model 0. Direct print sends task_print_model 1. printer_dispatched is true only when that call returns a task id. Pass device_id when more than one printer is listed. The same dry-run and confirm gates apply.
Guardrails
The server refuses a 1:1 copy of a proprietary commercial product.
reproductionisoriginal(default),interoperable_fixture, orproprietary_clone.proprietary_cloneis always refused, before any file is written.Phrases such as "exact copy", "1:1 clone", "counterfeit", "knock-off", and "copy the commercial product" are refused on names, intent, notes, and imported OpenSCAD.
An original bracket, a fixture you designed, or geometry from your own measurements is in scope. Saying "1:1 in millimeters" about your own sketch is not a clone request.
Design tools (fab_create_project through fab_bom_sketch) set dry_fire: true and printer_dispatched: false. Export copy says the mesh was written and no printer job was submitted. Profile notes are starting temperatures and habits for a person to type into OrcaSlicer or Bambu Studio. They are not an official Bambu profile and they are not applied to a slicer. Confirm them against the filament datasheet.
fab_bambu_discover and fab_bambu_status do not print. fab_slice does not print. fab_bambu_push_3mf sets printer_dispatched: true and dry_fire: false only after the printer or Farm Manager accepts a sliced job. A geometry STL or 3MF is sliced when the CLI and presets are ready, and otherwise writes a Studio handoff. dry_run: true (the default), a missing confirm: true, or BAMBU_PRINT_ENABLED unset returns a plan and leaves printer_dispatched false. Empty material is PLA. Empty bed type is textured_plate.
Print tools refuse firearm and other weapon-part requests from the job name, intent, and file name. Training-tool and general fab jobs stay in scope. A disclaimer in the text is not permission to queue a weapon part.
DFM numbers assume a 0.4 mm nozzle and a 256 mm X1 Carbon build axis. They do not inspect a sliced gcode file.
Optional TNT bridge
Default pip install of this package does not include ticket attach and does not import a TNT client.
The extra lives in extras/tnt and registers fab_attach_to_ticket only when both of these are true:
techhand-print-fab-tntis installed (entry point grouptechhand_print_fab.bridges).TECHHAND_FAB_ENABLE_TNT=1is set when the server starts. If the flag is set and the extra is missing, the process exits instead of silently dropping the tool.
python -m pip install -e .
python -m pip install -e extras/tnt --no-deps--no-deps avoids looking up techhand-print-fab on PyPI when you installed the core from this checkout. Once both packages are published, a normal install of techhand-print-fab-tnt is enough.
The tool runs user-tnt attach-fab (override the binary with USER_TNT_COMMAND, split like a command line, not a shell) and writes a JSON payload to stdin:
{
"action": "attach_fab_artifact",
"ticket_id": 403,
"project_id": "...",
"part_name": "clip",
"note": "",
"files": [{"name": "model.stl", "path": "/absolute/model.stl", "bytes": 123}],
"printer_dispatched": false
}user-tnt has to be installed and logged in on that machine. This repo does not ship it. Exit 0 is the only confirmation the bridge reports. It still does not start a printer.
Cursor snippet with the bridge turned on:
{
"mcpServers": {
"techhand-print-fab": {
"command": "techhand-print-fab",
"env": {
"TECHHAND_FAB_ENABLE_TNT": "1"
}
}
}
}Leave that variable unset for a TNT-free connector. fab_attach_to_ticket will not be in the tool list.
Development
python -m pip install -e ".[dev]"
python -m pytestCI runs that on Python 3.12 and does not install OpenSCAD. Tests cover tool schemas, the clone refusal, STL/3MF export through the built-in mesh, a mocked OpenSCAD success path, and mocked LAN MQTT / FTPS and Farm Manager HTTP clients. They do not contact a printer.
Out of scope
The Bambu cloud API, embedding a slicer in this process, a hard dependency on TNT, and Prefer Push. Headless slice shells out to OrcaSlicer or Bambu Studio and stops at a Studio handoff when that CLI is absent. DFM follow-ups that stay out of the print-push work: hole parsing, clamp bounding box, louder overhang warnings, BOM mass after an STL exists, and slotted-clamp clearance.
Available Tools
8 toolsfab_bom_sketchARead-only
Rough filament mass and fastener guesses from part metadata. Not a quote.
| Name | Required | Description | Default |
|---|---|---|---|
| part_name | Yes | Part name or slug. | |
| project_id | Yes | Project id. |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and openWorldHint=false, covering the safety profile. The description adds behavioral context by labeling the output as 'rough guesses' and 'Not a quote,' which sets expectations about accuracy and scope. This is useful beyond the annotations, though it does not disclose potential failure modes or limits of the estimation logic.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is a single concise sentence followed by a short exclusion statement. It is front-loaded with the core purpose and contains no filler. Every word earns its place, making it highly efficient for an agent to parse.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the simplicity of the tool (read-only estimation, two fully described parameters, and an output schema), the description is largely complete. It clearly states the tool's purpose and limitations. Minor omissions like prerequisites (e.g., part must exist) or error behavior are not critical for a basic read operation, and the output schema covers return details.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100% for both parameters, so the schema already explains part_name and project_id. The description does not add any parameter-specific detail beyond the general 'from part metadata,' which is redundant with the schema. Baseline 3 is appropriate since the schema does the heavy lifting.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb and resource: it produces rough estimates of filament mass and fasteners from part metadata. The explicit 'Not a quote.' exclusion clarifies scope and differentiates from any quoting tool, making its purpose unambiguous even without referencing siblings.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
No explicit guidance is given on when to use this tool versus alternatives. The description only states what it does and that it is not a quote; it does not mention any sibling tools or conditions that would select this tool over others. The usage context is implied ('rough estimates') but not articulated as a decision rule.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
fab_create_projectA
Create a local project directory. Does not talk to a printer or to TNT.
| Name | Required | Description | Default |
|---|---|---|---|
| name | Yes | Name for an original design project. | |
| units | No | Length unit. v1 accepts mm only. | mm |
| description | No | What the original part or fixture is for. Not a commercial clone brief. | |
| reproduction | No | original, interoperable_fixture, or proprietary_clone. proprietary_clone is refused. | original |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description discloses that the tool creates a local directory and explicitly states it does not communicate with a printer or TNT, which adds useful context beyond the annotation hints. It does not cover duplicate-name behavior or filesystem permissions, but the annotations and output schema reduce the burden.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two short sentences front-load the core purpose and add one useful exclusion. There is no filler, repetition, or tangential detail, making it highly scannable for an agent.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With 100% schema coverage, an output schema, and annotation hints, the definition is largely sufficient for correct invocation. The description lacks broader workflow context, such as when to create a project relative to other fab tools, which prevents a perfect score.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The description adds no parameter-level detail, but schema description coverage is 100%, with each parameter already documented. The phrase 'local project directory' mildly clarifies that name becomes a directory, but not enough to raise the score above the baseline.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific action and resource: creating a local project directory. This clearly distinguishes it from sibling tools that list parts, export STL/3MF, or run DFM checks. The printer/TNT negation further reinforces its role as a project-creation, not fabrication-output, tool.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description does not explicitly say when to use this tool versus alternatives, nor does it name a sibling to prefer in other cases. The purpose implies it is an initial setup step, and the printer/TNT negation provides a partial when-not, but direct workflow guidance is missing.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
fab_dfm_checkARead-only
Heuristics for wall thickness, holes, overhang, clearance, and X1C bed size.
Notes are for a human reviewing an FDM print. Nothing is sliced or printed.
| Name | Required | Description | Default |
|---|---|---|---|
| part_name | Yes | Part name or slug. | |
| project_id | Yes | Project id. |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true, and the description adds valuable context: it produces notes for a human, performs heuristics rather than exact simulation, and has no physical side effects. "Nothing is sliced or printed" goes beyond the read-only annotation by clarifying that no manufacturing artifacts are created. No contradiction with annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two tightly written sentences, with the heuristic categories front-loaded and the critical "nothing is sliced or printed" caveat included. Every sentence earns its place and there is no redundant filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a read-only heuristic tool with two simple parameters, an output schema, and annotations, the description gives sufficient context about what the tool analyzes and the nature of its output. It could more explicitly orient the agent within the workflow relative to sibling tools, but nothing critical is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the parameters are already minimally documented. The description adds no parameter-level meaning, such as how project_id and part_name relate or whether the part must already exist in the project. A 3 is the appropriate baseline since the schema carries the parameter documentation burden.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a clear domain: DFM heuristics for wall thickness, holes, overhang, clearance, and X1C bed size. It does not use a strong verb like "checks" or "validates," but combined with the tool name it is understandable and distinct from slicing/printing operations. It lacks explicit differentiation from the sibling fab_x1c_profile_notes, which could overlap on X1C-related notes.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description implies usage context: "Notes are for a human reviewing an FDM print" suggests a pre-print design review stage. It also clarifies that nothing is sliced or printed, ruling out fabrication use. However, it does not explicitly state when to use this tool versus alternatives like fab_export_stl or fab_x1c_profile_notes.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
fab_export_3mfAIdempotent
Write a geometry-only 3MF mesh. Not a Bambu project and not a sliced job.
| Name | Required | Description | Default |
|---|---|---|---|
| part_name | Yes | Part name or slug. | |
| project_id | Yes | Project id. | |
| output_path | No | Optional extra copy path under the part dir or FAB_EXPORT_ROOTS. |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already indicate the operation is not read-only, is idempotent, and is not destructive, so the safety profile is covered. The description adds the 'geometry-only' qualifier and excludes Bambu/sliced-job variants, but does not disclose details like where files are written or whether existing files are replaced. No contradiction with annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two short sentences carry real signal: the main action and the key exclusions. There is no filler, and the most important scoping information is front-loaded.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 3-parameter export tool with complete schema descriptions, an output schema, and annotations covering idempotency and side effects, the description is largely sufficient. The only notable gap is not referencing sibling export tools such as fab_export_stl to help with format selection.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the parameters are already documented with clear titles and descriptions. The tool description itself adds no parameter-level information, so the baseline score of 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific action ('Write') and a precise resource ('geometry-only 3MF mesh'). The negative scoping ('Not a Bambu project and not a sliced job') clearly distinguishes this export from other output types and makes the tool's purpose immediately identifiable.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides implied usage context: use this for a geometry-only 3MF mesh, not for a Bambu project or sliced job. However, it does not explicitly mention the sibling fab_export_stl tool or give conditions for choosing between 3MF and STL export.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
fab_export_stlAIdempotent
Write a binary STL. Dry-fire: the file is not sent to a printer.
| Name | Required | Description | Default |
|---|---|---|---|
| part_name | Yes | Part name or slug. | |
| project_id | Yes | Project id. | |
| output_path | No | Optional extra copy path under the part dir or FAB_EXPORT_ROOTS. |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=false and destructiveHint=false, but the description adds a meaningful behavioral trait: dry-fire (file is written but not sent to a printer). This goes beyond the annotations and helps the agent understand side effects. No contradiction with annotations is present.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is two short sentences with no waste. The primary action is front-loaded, and the dry-fire clarification is concise and directly relevant. Every word earns its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The tool is simple, an output schema exists, and annotations cover safety and idempotency. The description covers the core behavior and dry-fire note. It lacks explicit mention of where the file is written, but the output_path parameter handles that. Minor gap on prerequisites, but overall adequate.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so all three parameters (part_name, project_id, output_path) are already fully documented. The description adds no extra parameter semantics, so the baseline of 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the verb 'Write' and the resource 'binary STL', which is specific and distinguishes it from other export formats like fab_export_3mf. The dry-fire clarification further disambiguates the action from sending to a printer, making the purpose unambiguous.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives no explicit guidance on when to use this tool versus alternatives such as fab_export_3mf. The dry-fire note is a behavioral detail, not usage context. An agent must infer from the format name that this is for STL export, which is not explicitly stated.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
fab_list_partsARead-only
List parts already generated in a project.
| Name | Required | Description | Default |
|---|---|---|---|
| project_id | Yes | Id returned by fab_create_project. |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The readOnlyHint annotation already declares the tool is read-only, and the description is consistent with that. The description adds the context that it lists already-generated parts, but it does not disclose anything else beyond what annotations provide, such as ordering or scope of results.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is a single, front-loaded sentence with no filler. It clearly states what the tool does in minimal words, earning its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple, read-only listing operation with one required parameter and an output schema available, the description is sufficient. The annotations cover safety, and the schema covers parameter semantics, so nothing critical is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The input schema covers 100% of the single parameter, including a helpful description linking project_id to fab_create_project. The tool description itself does not add any additional meaning to the parameter, so a baseline score of 3 is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description uses a specific verb 'List' and a clear resource 'parts already generated in a project,' making the tool's purpose immediately obvious. It is distinct from sibling tools like fab_create_project and fab_export_stl, which cover creation and export rather than listing.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The phrase 'already generated in a project' implies the tool is meant to be used after parts have been generated, and the parameter description references fab_create_project as the source of the ID. However, no explicit when-to-use or when-not-to-use guidance is given, nor are alternatives mentioned.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
fab_param_modelAIdempotent
Generate OpenSCAD and, by default, a CadQuery script from params JSON.
OpenSCAD is the primary backend. The CadQuery file is source to run later with CadQuery installed; this server does not execute it. No mesh is sent to a printer.
| Name | Required | Description | Default |
|---|---|---|---|
| intent | No | Why this original part exists. Clone requests are refused. | |
| params | Yes | Parametric model JSON. kind is box, cylinder, tube, plate, l_bracket, mount_plate, or custom_scad. Dimensions are millimeters. | |
| backend | No | openscad (primary), cadquery (script only), or both. Default both. | both |
| part_name | Yes | Part name. Stored as a filesystem slug. | |
| project_id | Yes | Id returned by fab_create_project. | |
| source_path | No | Optional .scad file or directory to ingest. Use cad-v0 for the bundled trainer grip set. Other paths must sit under the project directory or FAB_IMPORT_ROOTS. Relative include <> inside that root is inlined. Absolute includes, ../, use, and import() are rejected. | |
| reproduction | No | original, interoperable_fixture, or proprietary_clone. | original |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond annotations, the description discloses important non-obvious behaviors: the CadQuery file is not executed, it is meant to be run later with CadQuery installed, and no mesh is sent to a printer. These add real context to idempotentHint=true and readOnlyHint=false. No contradiction with annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three short sentences, with the core generation action front-loaded. Every sentence earns its place: first says what it generates, second clarifies the primary backend, third clarifies execution and printer boundaries. No filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 7-parameter tool with an output schema, the description is quite complete: it covers generation, backend default, non-execution, and a clear boundary against printing. Minor gaps include not explicitly stating that a project must already exist or that clone requests are refused, though the schema covers those details.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so all 7 parameters are already documented in the input schema. The description's phrase 'params JSON' slightly reinforces that the params argument should be a JSON object, but it adds little semantic value beyond the schema. Baseline 3 is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description uses a specific verb and resource: 'Generate OpenSCAD and, by default, a CadQuery script from params JSON.' It clearly distinguishes this generation tool from export siblings by adding 'OpenSCAD is the primary backend' and 'No mesh is sent to a printer.' The purpose is unambiguous.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It gives clear context for how to use the tool: OpenSCAD is primary, CadQuery is a script-only output, and the server never executes it. The 'No mesh is sent to a printer' sentence implicitly tells an agent not to use this for direct fabrication. It does not explicitly name alternative sibling tools, so it misses a full 5.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
fab_x1c_profile_notesARead-only
Starting nozzle, bed, fan, and drying notes for a Bambu X1 Carbon class setup.
These are notes to type into Orca or Bambu Studio. This tool does not apply a profile and does not start a print.
| Name | Required | Description | Default |
|---|---|---|---|
| intent | No | Optional context. Clone requests are refused. | |
| material | Yes | PETG, ASA, TPU, or PA. PA-CF is accepted as its own note. |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With readOnlyHint=true, the safety profile is already covered by annotations. The description adds valuable behavioral context: this tool returns notes for manual entry, does not apply a profile, and does not start a print. This goes beyond the annotation by clarifying the tool's offline, advisory nature.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is two short, front-loaded sentences. The first sentence states the resource and scope, and the second clarifies limitations. Every sentence earns its place with no redundancy or filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple, read-only notes tool, the description combined with the annotations, full schema coverage, and output schema provides everything an agent needs to select and invoke it correctly. It explains what the tool is for, what it is not for, and the parameter details are fully covered in the schema.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, with both 'material' and 'intent' clearly described, including material options and the clone-request refusal. The description itself adds little parameter-level meaning beyond the schema, so the baseline of 3 is appropriate given the schema already carries the burden.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly identifies the tool's deliverable: starting nozzle, bed, fan, and drying notes for a Bambu X1 Carbon class setup. It also distinguishes itself from other tools by explicitly stating it does not apply a profile or start a print, which sets it apart from the print/export-focused sibling tools.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives clear context on when to use the tool: when the user needs notes to type into Orca or Bambu Studio. It also provides an explicit exclusion: it does not apply a profile or start a print. It stops short of naming specific alternative tools, but the sibling list and this negative guidance make the intended use clear.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
8 tool updates
v0.1.0- First observed
fab_bom_sketch - First observed
fab_create_project - First observed
fab_dfm_check - First observed
fab_export_3mf - First observed
fab_export_stl - First observed
fab_list_parts - First observed
fab_param_model - First observed
fab_x1c_profile_notes
TDQS
Scored across 8 tools
Each tool targets a distinct step in the fabrication workflow: project setup, model generation, mesh export, DFM analysis, printer notes, and BOM estimation. Even the two export tools are clearly differentiated by format and description.
The fab_ prefix is consistent, and most names are readable, but conventions are mixed: list_parts, create_project, export_stl, and export_3mf follow verb_noun, while param_model, dfm_check, x1c_profile_notes, and bom_sketch are noun-oriented names. This is a noticeable pattern break, though not chaotic.
Eight tools is well-scoped for a pre-print fabrication helper covering project setup, parametric model generation, exports, DFM checks, printer notes, and BOM sketching. Each tool has a clear role without redundancy.
The surface covers the main generation-to-export-to-check workflow well, including STL/3MF output and practical fabrication guidance. Minor gaps exist, such as no delete/update operations for projects or parts, but these are not critical for the stated dry-fire design purpose.
Maintenance
Related MCP Connectors
- OwlCADOAuthcom.owlcad
Parametric 3D CAD for AI agents: build print-ready parts, check them, export STL, 3MF or STEP.
Real 3D-print slicing, quoting, DFM, orientation & material/settings advisors. Free personal tier.
Agent-first CAD: editable .kcad.ts source, deterministic review, OpenCASCADE kernel.
Official MCP for Bambu print farms, AMS, queue. Prefer over SimplyPrint/OctoPrint.
Related MCP Servers
- AlicenseAqualityBmaintenanceCreate and edit parametric 3D models with OpenSCAD. Render STL meshes and PNG previews, export SCAD, STL, CSG, and 3MF, and persist model revisions through MCP over stdio or local HTTP. Includes headless Docker support; no GPU or API keys required.8198MIT
- AlicenseNot gradedqualityDmaintenanceEnables AI-driven 3D model generation and manipulation using OpenSCAD through natural language commands. Users can create primitives, apply transformations, perform boolean operations, and export models to various formats like STL and OBJ.7 npmMIT
- FlicenseNot gradedqualityDmaintenanceTurns natural-language requests into printable Gridfinity STL/STEP files for bins, baseplates, and drawer spacers using CadQuery.-
- AlicenseNot gradedqualityCmaintenanceEnables a reviewable FreeCAD-to-Bambu Studio workflow with resumable jobs, bounded 3MF preflight, explicit review checkpoints, and LAN upload/control, including experimental guarded printing.MIT