Skip to content

Printer Control Tools ​

All printer tools accept optional host, bambu_serial, and bambu_token arguments. If omitted, values fall back to the environment variables PRINTER_HOST, BAMBU_SERIAL, and BAMBU_TOKEN. Passing them explicitly is useful when working with more than one printer.

The server also accepts the alias variables BAMBU_PRINTER_HOST, BAMBU_PRINTER_SERIAL, and BAMBU_PRINTER_ACCESS_TOKEN, plus BAMBU_PRINTER_MODEL and BAMBU_STUDIO_PATH.

get_printer_status ​

Retrieve current printer state including temperatures, print progress, layer count, time remaining, and AMS slot data. Internally sends a push_all MQTT command to force a fresh status report before reading cached state.

json
{
  "host": "192.168.1.100",
  "bambu_serial": "01P00A123456789",
  "bambu_token": "your_access_token"
}

Chamber status decodes the current temperature from newer firmware's packed CTC reports, including P2S, while retaining legacy chamber/frame fields.

Returns a structured object with fields including status (gcode_state string), temperatures.nozzle, temperatures.bed, temperatures.chamber, print.progress, print.currentLayer, print.totalLayers, print.timeRemaining, and ams (raw AMS data from the printer).

get_printer_filaments ​

Read the live AMS inventory and resolve each loaded tray to Bambu Studio filament profile JSON paths when bambu_model is known. The result includes a summary, per-slot display labels, profile match confidence, and a recommended load_filaments value for simple single-material CLI slicing.

json
{
  "bambu_model": "h2d",
  "nozzle_diameter": "0.4",
  "host": "192.168.1.100",
  "bambu_serial": "094...",
  "bambu_token": "your_access_token"
}

High-signal fields:

  • summary.loaded_slots, summary.resolved_profile_slots, summary.unresolved_loaded_slots, summary.empty_slots
  • trays[].display_name, trays[].tray_color, trays[].remain_percent
  • trays[].resolved_profile_path
  • trays[].profile_resolution: exact-model-nozzle, model, generic, or unresolved
  • trays[].match_confidence: high, medium, low, or none
  • recommended.load_filaments: the profile path the MCP will use for auto-slicing when no explicit filament override is provided

list_printer_files ​

List files stored on the printer's SD card. Scans the cache/, timelapse/, and logs/ directories and returns both a flat list and a directory-grouped breakdown.

This is a read-only query: it never creates directories. Optional directories absent from a successful root listing return empty lists. Authentication, TLS, permission, and transfer failures are reported as errors rather than empty or partial results.

json
{
  "host": "192.168.1.100",
  "bambu_serial": "01P00A123456789",
  "bambu_token": "your_access_token"
}

camera_snapshot ​

Capture a single JPEG frame from the printer's chamber camera. Read-only.

Two transports are wired in, picked by bambu_model:

  • TCP-on-6000 for A1, A1 mini, P1S, P1P. Native protocol per OpenBambuAPI/video.md: TLS on port 6000, 80-byte auth packet (bblp + access token), repeating 16-byte frame header + JPEG payload. The printer first replays the last frame it buffered, which after an idle period can show the chamber as it was long ago, so the tool skips that frame and returns the next live one (about 3 s). If no live frame follows within timeout_ms, it fails instead of returning a possibly stale image.
  • RTSP for X1, X1 Carbon, X1E, P2S, X2D and H2, H2S, H2D, H2C, H2D Pro. Shells out to ffmpeg with rtsps://bblp:<token>@<host>:322/streaming/live/1 -frames:v 1. The H2 series wasn't documented in OpenBambuAPI's video.md but its firmware uses the same RTSP endpoint as X1 (verified live against an H2S, 2026-04-27).

Requires ffmpeg in PATH for the RTSP path. Install with brew install ffmpeg on macOS. Configure a trusted custom binary with the server-side FFMPEG_PATH environment variable, or set MCP_ALLOW_EXECUTABLE_ARG=1 before using the ffmpeg_path tool argument. The TCP-on-6000 path uses native Node TLS and does not require ffmpeg.

json
{
  "save_path": "/tmp/snap.jpg",
  "timeout_ms": 8000,
  "bambu_model": "h2s",
  "host": "192.168.1.100",
  "bambu_serial": "01P00A123456789",
  "bambu_token": "your_access_token"
}

Returns { status, format: "image/jpeg", sizeBytes, base64, savedTo?, transport }. transport is "tcp-6000" or "rtsps-322" so callers can tell which path produced the frame. Pass save_path to also write the bytes to disk; otherwise only the base64 payload is returned.

camera_stream_start, camera_frame, camera_stream_stop ​

Development capability; discover these tools before using them with an installed server. Start one shared camera connection while a viewer is open, then request changed frames. The connection closes after 30 seconds without a viewer request. Snapshots reuse its latest frame. A1/P1 use the existing TLS protocol; X1/H2/P2S use one ffmpeg process for the stream lifetime.

json
{"fps": 1, "idle_timeout_ms": 30000, "bambu_model": "p1s"}

Pass the usual optional host, bambu_serial, and bambu_token overrides to each tool. camera_stream_start returns {status: "started" | "running", transport, fps, startedAt}. Then call camera_frame with, for example, { "since_seq": 0, "wait_ms": 10000, "max_width": 300, "max_bytes": 20000 }. A changed frame returns JPEG base64, seq, dimensions and capture time. Keep the last delivered seq on unchanged; those replies carry no image. max_width preserves aspect ratio; optional max_bytes caps JPEG bytes by downscaling further (base64 adds about one third), and changed_only defaults to true with a 0.02 normalized thumbnail threshold.

Stop requests close the shared connection immediately, affecting every viewer of that printer; normally let idle expiry close it. Other printers keep their own connections. These tools send no motion, heating or print commands. See the camera stream contract for limits, cancellation, availability and lifecycle details.

delete_printer_file ​

Delete a single file from the printer's SD card via FTPS. Destructive. Requires confirm: true — without it the call returns status: "skipped" and does not contact the printer. Path traversal segments (..) are rejected. Only files under cache/, timelapse/, and logs/ can be deleted.

json
{
  "filename": "old_print.gcode.3mf",
  "confirm": true,
  "host": "192.168.1.100",
  "bambu_serial": "01P00A123456789",
  "bambu_token": "your_access_token"
}

A bare filename defaults to cache/<filename>. To target other directories pass a relative path:

json
{ "filename": "timelapse/2026-04-26_12-00.mp4", "confirm": true }
json
{ "filename": "logs/printer.log", "confirm": true }

upload_gcode ​

Write G-code content from a string directly to the printer's cache/ directory. The content is written to a temporary file and uploaded via FTPS.

json
{
  "filename": "calibration.gcode",
  "gcode": "G28\nM104 S210\nG1 X100 Y100 Z10 F3000\n",
  "host": "192.168.1.100",
  "bambu_serial": "01P00A123456789",
  "bambu_token": "your_access_token"
}

upload_file ​

Upload a local file (G-code or 3MF) to the printer. If print is true and the file is a .gcode file, start_print_job is called automatically after a successful upload. For .3mf files, upload completes normally but you must use print_3mf to initiate the print (which handles plate selection and metadata).

json
{
  "file_path": "/Users/yourname/Downloads/part.3mf",
  "filename": "part.3mf",
  "print": false,
  "host": "192.168.1.100",
  "bambu_serial": "01P00A123456789",
  "bambu_token": "your_access_token"
}

start_print_job ​

Start printing a .gcode file that is already on the printer's SD card. Do not use this for .3mf files -- use print_3mf instead, which handles the project_file MQTT command with proper metadata.

json
{
  "filename": "cache/calibration.gcode",
  "host": "192.168.1.100",
  "bambu_serial": "01P00A123456789",
  "bambu_token": "your_access_token"
}

If filename does not include a directory prefix, the server prepends cache/ automatically.

cancel_print ​

Cancel the currently running print job. Sends Bambu Studio's stop, {"print":{"command":"stop","param":"","sequence_id":"<n>"}}, at MQTT QoS 1, and waits up to 10 seconds for the printer to acknowledge it. A stop that isn't acknowledged returns an error, and its MQTT client is closed so it can't be sent late. Not resumable — use pause_print if you may want to continue.

json
{
  "host": "192.168.1.100",
  "bambu_serial": "01P00A123456789",
  "bambu_token": "your_access_token"
}

pause_print ​

Pause the currently running print job. Sends an UpdateState MQTT command with state: "pause". Resumable via resume_print.

json
{
  "host": "192.168.1.100",
  "bambu_serial": "01P00A123456789",
  "bambu_token": "your_access_token"
}

resume_print ​

Resume a paused print job that this server inspected and started. Resume reheats and moves the printer, so it is checked like a print start:

  1. A fresh report must show that job paused, with the inspected nozzle and filament still in place.
  2. The server downloads the job's file from the printer over FTPS and compares its SHA-256 with the inspected bytes. A P1S deletes an uploaded project once it starts printing and keeps the plate G-code it extracted, cache/<name>_plate_<n>.gcode, which is byte-identical to the 3MF's Metadata/plate_<n>.gcode. When the project is gone, the server compares that file with the inspected plate. A different file refuses the resume, and the job can no longer be resumed from here.
  3. The resume starts without a prompt when the bytes were verified. Only a file that could not be re-read asks a person; that prompt names the job, its peak temperatures, materials and SHA-256.
  4. The printer is read again, and on X2D and H2 the native helper rechecks it once more right before sending. Only then does the server send UpdateState with state: "resume".

The server keeps one resume record per printer. Cancelling the job ends it, and so does a report that the job finished, failed or went idle after it was seen running, or another job seen in its place. A later job with the same name, such as the same file reprinted from the touchscreen, is never resumed. The server watches every MQTT report it receives, not just its own reads. It doesn't see reports while its MQTT session is disconnected.

The result is {status, message, jobName, bytesVerified}, where jobName is the name counterpart_print_progress reports. On the BambuNetwork and X2D native routes, Bambu's plug-in puts the file on the printer, so its FTPS path isn't verified. If the file can't be read back there, the resume matches the job by its unique name and returns bytesVerified: false, and a person is asked, with the prompt saying so. On every other route, a file that can't be re-read refuses the resume.

json
{
  "host": "192.168.1.100",
  "bambu_serial": "01P00A123456789",
  "bambu_token": "your_access_token"
}

clear_hms_errors ​

Clear HMS or print error state on the printer. Sends Bambu's clean_print_error MQTT command.

json
{
  "host": "192.168.1.100",
  "bambu_serial": "01P00A123456789",
  "bambu_token": "your_access_token"
}

set_print_speed ​

Set the active print speed mode. Accepted mode values are silent, standard, sport, ludicrous, or their numeric equivalents 1, 2, 3, and 4.

json
{
  "mode": "sport",
  "host": "192.168.1.100",
  "bambu_serial": "01P00A123456789",
  "bambu_token": "your_access_token"
}

set_airduct_mode ​

Set H2/P2 airduct mode to cooling or heating. This is intended for supported printers only.

json
{
  "mode": "cooling",
  "host": "192.168.1.100",
  "bambu_serial": "01P00A123456789",
  "bambu_token": "your_access_token"
}

reread_ams_rfid ​

Trigger a Bambu AMS RFID re-read for one AMS slot. This can move AMS filament; use it only when the printer is idle and unloaded.

json
{
  "ams_id": 0,
  "slot_id": 1,
  "host": "192.168.1.100",
  "bambu_serial": "01P00A123456789",
  "bambu_token": "your_access_token"
}

set_temperature ​

Set a checked target temperature for the bed or nozzle through MQTT. Positive targets require the printer model and fresh matching printer telemetry; nozzle heating also requires the declared loaded material and matching nozzle_diameter (default 0.4). Independent model/component and material ceilings apply. A target of zero turns the heater off without requiring material or nozzle metadata. Accepted component values are bed, nozzle, extruder, tool, and tool0.

Manual nozzle heating checks the reported currently loaded AMS tray or external spool. It refuses ambiguous active-nozzle/material selection on multi-nozzle printers; use a checked sliced job or the printer's own controls there. Stop and heater-off commands cancel pending server print/heating operations. Resuming through MCP requires the same paused job inspected and started by this server instance, with fresh matching telemetry; other paused jobs remain controllable at the printer.

json
{
  "component": "nozzle",
  "temperature": 220,
  "bambu_model": "p1s",
  "material": "PLA",
  "nozzle_diameter": 0.4,
  "host": "192.168.1.100",
  "bambu_serial": "01P00A123456789",
  "bambu_token": "your_access_token"
}

set_fan_speed ​

Set a printer fan speed from 0 to 100 percent. Accepted fan values are part, auxiliary, chamber, 1, 2, and 3.

json
{
  "fan": "chamber",
  "speed": 40,
  "host": "192.168.1.100",
  "bambu_serial": "01P00A123456789",
  "bambu_token": "your_access_token"
}

set_light ​

Set a printer light node mode. Common Bambu firmware reports the chamber light as chamber_light; valid modes are on, off, and flashing.

json
{
  "light": "chamber_light",
  "mode": "on",
  "host": "192.168.1.100",
  "bambu_serial": "01P00A123456789",
  "bambu_token": "your_access_token"
}

skip_objects ​

Skip specific object IDs during a running multi-object print. Use list_3mf_plate_objects on the sliced 3MF to find the IDs first.

json
{
  "object_ids": [6495, 6496],
  "host": "192.168.1.100",
  "bambu_serial": "01P00A123456789",
  "bambu_token": "your_access_token"
}

set_ams_drying ​

Start or stop the AMS filament drying cycle on heated AMS units (AMS Pro / AMS-HT). The action parameter accepts start or stop. The ams_id must be an integer from 0 to 3.

json
{
  "action": "start",
  "ams_id": 0,
  "host": "192.168.1.100",
  "bambu_serial": "01P00A123456789",
  "bambu_token": "your_access_token"
}

To stop drying:

json
{
  "action": "stop",
  "ams_id": 0
}

inspect_print_file ​

Inspect a prepared sliced 3MF or textual G-code file offline before deciding what to export or print. This tool copies the file into a private read-only snapshot and reuses the same archive, selected-plate, model/nozzle/material and temperature checks used by print preflight. It never contacts a printer or auto-slices an unsliced project.

json
{
  "file_path": "/path/to/bracket.gcode.3mf",
  "bambu_model": "p1s",
  "plate_index": 0,
  "nozzle_diameters": [0.4],
  "bed_type": "textured_plate"
}

file_path and explicit bambu_model are required; the model is never inferred from configuration. plate_index is zero-based and defaults to 0. Nozzle/bed checks are optional comparisons against file declarations, not observations of installed hardware. Only these five argument names are accepted; credentials, host and executable/bridge overrides are not part of this tool.

The generic result has receipt_version: "1", evidence: "file-only", dispatch_authorized: false, artifact (sha256, byte_length, file_format), the requested selection, and the original inspection including declared model, nozzles, materials/used filament positions, internal plate path and checked temperature peaks. Hash and byte length identify the exact inspected snapshot; a consumer must compare them with its own artifact and selected plate. The temporary snapshot is removed, and its path is not a dispatch capability. The receipt does not prove printer readiness, physical material/nozzle contents, fit, full motion simulation or permission to print. Actual print routes still independently inspect the dispatched snapshot and require fresh hardware state.

Every print route sends the options Bambu Studio's Send dialog sends (Bambu Studio 02.08.02.61, SelectMachine.cpp:3344-3356): bed leveling, flow calibration and nozzle offset calibration, vibration calibration, first-layer inspection and extruder_cali_manual_mode. That covers the BambuNetwork bridge, the native helper and LAN project_file. Each option you don't pass resolves as Studio resolves it:

  1. Studio's saved choice for that printer type in BambuStudio.conf, if the printer offers it. For example, a P1S offers only on/off for bed leveling, so a saved "auto" counts as unset.
  2. Otherwise "auto" if the printer offers it, else "on". On a P1S, flow calibration and nozzle offset calibration are hidden, so they stay "auto".

Studio hard-codes vibration calibration off, first-layer inspection on and extruder_cali_manual_mode 0. For a P1S with nothing passed, the bridge sends task_bed_leveling: true, auto_bed_leveling: 1, task_flow_cali: false, auto_flow_cali: 2, task_vibration_cali: false, task_layer_inspect: true, auto_offset_cali: 2 and extruder_cali_manual_mode: 0. An option you pass wins. bed_leveling/flow_calibration set "on" or "off", and auto_bed_leveling, auto_flow_cali and auto_offset_cali take 0 (off), 1 (on) or 2 (auto). On the H2/P2S LAN project_file route, the printer only receives flow calibration as on or off: no verified field requests "auto", so "auto" sends off, as before. The BambuNetwork and native routes carry auto_flow_cali. Only the printer type's own section of BambuStudio.conf is read; the file also holds access codes. BAMBU_STUDIO_CONFIG names another file, and an empty value uses no saved choices.

The primary tool for starting a Bambu print. Recommended input: a pre-sliced .gcode.3mf exported from FULU OrcaSlicer-bambulab, OrcaSlicer, or Bambu Studio — see slicing guide. This tool handles the complete workflow:

  1. Checks whether the 3MF contains embedded G-code (Metadata/plate_<n>.gcode entries).
  2. If no G-code is found, attempts to auto-slice via the configured slicer. Profile preparation or slicing failures stop the operation before upload. See the slicing guide for tested CLI versions and combinations.
  3. Parses the sliced 3MF to extract the correct plate file and compute its MD5 hash.
  4. Reads slicer metadata and any explicit AMS selection to build the filament mapping.
  5. Uploads via basic-ftp to the model-specific location: SD root for H2/full-size A1, cache/ for P1/X1/A1 mini/P2S. X2D print_3mf instead uses its checked native route on macOS or Linux, which stores the job on a ready SD card or USB stick and otherwise on internal storage (eMMC).
  6. Sends the correct MQTT print command for the target printer family. For H2S/H2D/H2C that means project_file with project-length ams_mapping, parallel ams_mapping2, and H2-compatible calibration flags.
json
{
  "three_mf_path": "/Users/yourname/Downloads/bracket.3mf",
  "bambu_model": "p1s",
  "bed_type": "textured_plate",
  "host": "192.168.1.100",
  "bambu_serial": "01P00A123456789",
  "bambu_token": "your_access_token",
  "bed_leveling": true,
  "flow_calibration": true,
  "vibration_calibration": true,
  "timelapse": false,
  "use_ams": true,
  "ams_mapping": [0, 1]
}

bambu_model is required for model-specific routing and preset selection. It does not by itself validate pre-sliced G-code. BambuStudio, FULU, and Orca CLI preparation additionally require the exact model/nozzle machine preset and reject incomplete profiles. Using the wrong model can damage hardware. If bambu_model is not provided in the tool call and BAMBU_MODEL is not set in the environment, the server will ask you interactively via MCP elicitation (if your client supports it) or return a clear error.

bed_type defaults to textured_plate if omitted. nozzle_type (stainless_steel, hardened_steel, tungsten_carbide, brass; default BAMBU_NOZZLE_TYPE) sets the installed nozzle when the project must be auto-sliced; the job's nozzle type must match the printer's report, and stock P1S/P1P/A1 presets assume stainless steel. nozzle_volume_type (standard, high_flow; default BAMBU_NOZZLE_VOLUME_TYPE) likewise sets the installed nozzle's flow type for auto-slicing; it must match the printer's reported nozzle flow. After an MQTT print command the server listens to the printer's pushed reports for up to 15 seconds (BAMBU_DISPATCH_CHECK_MS) and returns dispatch: "started" or "unconfirmed"; if firmware 01.08.05+ refuses the command (HMS 0500-0500-0001-0007, needs LAN Only Mode and Developer Mode), the call fails and says so; clear that fatal HMS entry with clear_hms_errors before printing again. ams_slots is the preferred override input; ams_mapping remains the raw escape hatch. On AMS-equipped H2 printers, use_ams: false does not suppress mapping lookup if the sliced file declares filaments. If no mapping is provided for an H2 pre-sliced job with declared filaments, the server fails before sending; pass explicit ams_slots, raw ams_mapping, or auto_match_ams: true.

Set auto_match_ams: true to match the sliced 3MF's tray_info_idx values against the live AMS inventory and use the matching ams_slots. The matcher joins on (tray_info_idx, tray_color) and tracks already-claimed slots, so prints with two filaments of the same SKU but different colors (e.g. two GFG02 PETG HF in black and white) resolve correctly. Falls back to SKU-only when the 3MF's filament has no color set or only one tray of that SKU is loaded. Returns a structured missing report (reason: "no_loaded_match" | "color_mismatch" | "exhausted" | "no_sku") when a filament can't be resolved. Ignored when you provide ams_slots or ams_mapping explicitly.

Layer height, nozzle temperature, and other slicer parameters cannot be overridden via this tool -- they are baked into the 3MF's G-code at slice time. Apply those settings in your slicer before generating the 3MF.

For the optional FULU bridge, set connection_mode: "bambu_network" and explicitly choose connection_type: "cloud" or "lan"; see FULU setup. A successful command submission is not proof that the printer accepted it: inspect printer state, HMS errors, and the printer itself.

bambu_network_bridge_status ​

Inspect the configured FULU bridge without starting it using {}. Use {"connect": true} to launch the host, handshake, and initialize an agent. See bridge probes for interpreting the result.

bambu_network_call ​

Call an allowed read-only probe such as {"method": "net.is_user_login", "payload": {}}. The default injects the initialized agent; use with_agent: false for bridge.handshake. Raw printer mutations and unknown methods are refused; use the dedicated checked print or printer tools.

X2D native transport (macOS) ​

Install Bambu Studio and its networking plug-in, plus the Apple command-line developer tools (clang++). The plug-in is loaded at runtime and is not redistributed. In the installed bambu-printer-mcp package directory, run npm run build:native. For a global install, locate that directory with npm root -g; for an extracted desktop extension, run the same command in the extension directory. Rebuild after updating the package. For Linux, see X2D native transport (Linux); Windows can use status and slicing only.

The npm package and desktop bundle include native/bambu-native-print.cpp, scripts/build-bambu-native.sh and the Linux setup script, never a developer's compiled binary. The server finds the built helper relative to its installed package, regardless of the launch directory. A trusted server setting BAMBU_NATIVE_HELPER can select an alternate executable.

For X2D, print_3mf defaults to connection_mode: "bambu_native"; legacy lan_mqtt_ftps requests are redirected to it. Supply ams_slots, a complete project-level ams_mapping, or auto_match_ams: true. A job that uses only one external spool requires explicit use_ams: false; a job mixing AMS trays with the external spools maps them as 254 (left/deputy nozzle) and 255 (right/main nozzle). Raw ams_mapping2 and nozzle/extra-option overrides are rejected before dispatch; the server derives both AMS representations from the checked structured mapping. Model/nozzle/material inspection and fresh printer-state checks apply before the helper receives a private snapshot. The native helper requests fresh shared printer-state authorization immediately before each print submission, including a certificate retry, and before checked heating, resume, or error-clearing commands; custom helpers must support this handshake. Stop and heater-off remain available without preflight. Native upload-only requests use the same all-plate inspection as FTPS uploads. They accept project_name, preset_name, bed_type, and an existing plate_index; AMS, nozzle, and calibration print options are rejected. Use print_3mf for checked print settings.

The helper waits for connection/certificate exchange and retries only the initial -4030 send once. Rebuild the helper after upgrading to receive the teardown fix. A successful dispatch receipt ends the helper before plug-in cleanup can turn an accepted job into an error. If an older/custom helper crashes after dispatch, the server reports uncertainty: check the printer state and job name before retrying, because resending can duplicate a print. Previous hardware testing reached RUNNING on X2D; this revision is validated with mocked transport regressions and clean installs, not a new physical print. bambu_connect is an optional macOS handoff for user review in Bambu Connect and does not start a print. Native task, heater, and error controls retain the common safety checks; raw controls cannot bypass them. Request cancellation, stop, and heater-off interrupt pending native helpers; the server waits for process exit before releasing a checked file. A command already sent to the printer cannot be recalled by cancelling the request, so verify printer state before retrying.

X2D native transport (Linux) ​

On x86-64 Linux, one command builds the helper and installs Bambu's networking plug-in and the Bambu Studio certificates. It needs a C++ compiler (g++ or clang++) and network access; no desktop or slicer is required:

bash
npx -y -p bambu-printer-mcp bambu-printer-mcp-native-setup

Use the same package version as the server, for example bambu-printer-mcp@1.2.3; when the installation is missing or stale, the server's error names the exact command. The setup downloads the Linux plug-in that Bambu Studio itself installs, pinned to an exact version and SHA-256, and stores it with the certificates under ~/.config/bambu-printer-mcp/bambu-native (BAMBU_NATIVE_HOME or XDG_CONFIG_HOME move it; set the same value for the server). The plug-in is not redistributed by this package, and Bambu Studio's own auto-updating copy is never used. The server finds the installation automatically and passes the plug-in and certificate paths to the helper. Rerun the setup with the server's package version after upgrading: the server refuses a helper built from another version. Only missing or changed files are downloaded again. The setup then loads the plug-in once, offline, so missing system libraries are reported during installation rather than on a stop command. --verify repeats these checks without network access. On Linux the server uses only this managed helper or BAMBU_NATIVE_HELPER; a package-local npm run build:native binary is not used.

--check-update asks Bambu's plug-in service for the newest Linux plug-in and compares the PrintParams layout in the matching Bambu Studio header with the helper. It reports only: the setup never installs an unpinned plug-in, because a changed layout would send wrong print parameters. A pin is raised by a package release after the check passes and the offered archive and required libraries are checksum-verified. Offline plug-in loading and physical X2D printing are separate validation steps, reported with each release.

Once the helper, plug-in and certificates are installed, X2D printing, upload and controls use the same routing, safety checks and authorization handshake described for macOS above. On Linux, stop and heater-off go out over the native route and MQTT at the same time, so a stuck helper cannot delay them. MQTT is sent again once every older print or heating helper for that printer has exited and the immediate MQTT attempt has settled, so none can dispatch after it; either the native route or that later MQTT send succeeding counts, and the result lists all sends under routes. Each MQTT send gives up after 15 seconds (BAMBU_EMERGENCY_MQTT_TIMEOUT_MS), so an unreachable MQTT connection cannot hold back a stop the native route already sent. A connection completing after that deadline cannot dispatch its expired command; an already queued MQTT publish is discarded by force-closing and invalidating that client. A repeated stop or heater-off is harmless. macOS keeps its native-only route. On both systems a print goes to the printer's SD card or USB stick when the printer reports one ready, so it can be reprinted from the printer and archived by tools that read the card; otherwise it goes to internal storage (eMMC), as Bambu Studio's Print button always does. BAMBU_NATIVE_TRY_EMMC_PRINT=1 (or 0) forces the choice. The server accepts only the pinned plug-in checksums; BAMBU_NATIVE_ALLOW_UNPINNED_PLUGIN=1 is a trusted override for testing another build at your own risk. Without a complete installation, Linux keeps its previous behavior: X2D status and slicing work, stop, pause and heater-off use MQTT, and native print requests name the setup command before contacting the printer. Bambu publishes the Linux plug-in for x86-64 only; other architectures keep the MQTT behavior.

x2d_native_control ​

This optional X2D metadata tool accepts ams_filament_setting and extrusion_cali_sel, plus the read-only queries extrusion_cali_get, extrusion_cali_get_result, and flowrate_get_result. It validates command fields, numeric metadata, and AMS unit/slot selection. Motion, filament loading, heating, safety-setting changes, and task control are not exposed through raw JSON; use the dedicated checked tools where available. A metadata declaration does not verify the physical spool contents.

Submit a sliced project through FULU's separately configured networking runtime. connection_type defaults to cloud. Both cloud and LAN bridge jobs require a printer IP, LAN access code, matching serial/device ID, and fresh MQTT safety telemetry. File, raw AMS/nozzle mapping, and extra-option overrides cannot replace checked parameters. See print examples and AMS requirements. A zero bridge return code confirms submission, not a physical print or direct X2D support.

resolve_3mf_ams_slots ​

Dry-run the AMS match without uploading or starting a print. The tool reads Metadata/plate_<n>.json and Metadata/slice_info.config, then compares required tray_info_idx values against live AMS trays.

json
{
  "three_mf_path": "/Users/yourname/Downloads/bracket.gcode.3mf",
  "bambu_model": "h2d",
  "host": "192.168.1.100",
  "bambu_serial": "094...",
  "bambu_token": "your_access_token"
}

list_3mf_plates ​

List a designer's 3MF plates as Bambu Studio shows them when the file is opened, before slicing. A downloaded kit's six plates are six plates here too. For each plate it gives index (from 0, so slice_stl's slice_plate is index + 1), name, the objects on it, and sliced (whether the file already holds its G-code). It also gives plate_count and the file's title, designer and licence when they're set. A plain 3MF with no plate metadata is one plate. include_thumbnails adds each plate's own preview from the file. Read-only.

json
{
  "three_mf_path": "/Users/yourname/Downloads/kit.3mf",
  "include_thumbnails": true
}

list_3mf_plate_objects ​

List the objects of a sliced 3MF plate that the printer can skip, one per copy, with each copy's identify_id from Metadata/slice_info.config, as Bambu Studio's skip dialog lists them. Use this before skip_objects so you pass real Bambu object IDs instead of display-order guesses.

json
{
  "three_mf_path": "/Users/yourname/Downloads/bracket.gcode.3mf",
  "plate_index": 0
}

High-level wrapper for a prepared two-part dog-collar-charm workflow. This tool is intentionally specialized: it expects a prepared two-part charm project and applies a fixed tray policy.

  • Smaller inner object -> black -> AMS 1 slot 1
  • Larger outer object -> white -> AMS 2 slot 1

The tool will:

  1. Resolve a local .3mf or template_name.
  2. Auto-slice if the 3MF is still an unsliced project.
  3. Inspect Metadata/plate_1.json to identify the smaller inner part and larger outer part.
  4. Preflight the required AMS trays on the printer.
  5. Dispatch the print through the existing H2-safe print3mf path using ams_slots.
json
{
  "template_name": "collars/letter_charm_a",
  "bambu_model": "h2d",
  "host": "192.168.1.100",
  "bambu_serial": "03W09C123456789",
  "bambu_token": "your_access_token",
  "bed_leveling": true,
  "flow_calibration": true,
  "vibration_calibration": true,
  "timelapse": false
}

You can also pass source_path directly instead of template_name.

This wrapper currently assumes:

  • the input is a prepared two-part charm .3mf, not a bare STL that needs color-region generation
  • the selected plate has exactly 2 objects
  • the selected plate has exactly 2 used filament positions
  • the smaller object is the inner insert/letter and the larger object is the outer body

If the project does not match those assumptions, the tool fails fast with a structured error instead of guessing. The role-to-color and color-to-tray mapping is isolated in code so the next version can evolve toward customer-requested colors without replacing the whole wrapper.

Counterpart tools (counterpart/1) ​

Counterpart (Scan to Make) reads printers through a small contract that this server and mcp-3D-printer-server share. Each tool declares an outputSchema and returns structuredContent, plus the same JSON as text for older clients. An error is returned as text only, because MCP clients check structuredContent against the schema even on errors. Other tools return an error's details in structuredContent (status, retryable, suggestion, message). When the printer refuses a print, the error also carries a stable code: printer-command-rejected, printer-not-ready, or printer-upload-failed when the file didn't reach the printer and no print command was sent, so a client can act on the code rather than the wording. A print_3mf call made with a progress token reports its LAN upload as MCP progress: progress is bytes sent, total the file's bytes, and _meta["counterpart/stage"] is "upload", at most once per whole percent. counterpart_printer_info reports capabilities.slicing (supports, copies, orient), so a client can offer those choices without knowing the slicer. counterpart_printer_info and counterpart_print_progress take the usual optional host, bambu_serial, bambu_token and bambu_model (default BAMBU_MODEL) arguments.

  • counterpart_printer_info returns {contract, server: {name, version}, printer: {kind: "bambu", model, serial, host}, capabilities}. capabilities lists progress, layers, the camera transport (bambu-tcp for A1/P1, rtsp for X1, P2S, H2 and X2D, none without a model), cameraStream: true for supported camera models, filaments: "ams", pause, resume, cancel, print3mf and gcode (false on X2D). It sends nothing to the printer and never returns the access code.

  • counterpart_print_route reads or changes how prints and Stop/Pause/Resume reach the printer: {route, mode, type, choices, savedTo}, where route is lan (the local network, in the configured BAMBU_DEFAULT_CONNECTION_MODE) or cloud (Bambu Cloud through the FULU bridge). Pass route to change it; the next print and control use it at once, and it is written to the .env in the server's working directory when one exists (savedTo, otherwise null). It is not part of the shared contract: it has no outputSchema, and mcp-3D-printer-server has no Bambu Cloud route.

  • counterpart_print_progress returns {contract, observedAt, state, job?, temps?, activeFilament?, problems} from the printer's MQTT reports, which this server keeps while it is connected. A held session is answered from that cache without any printer traffic, and observedAt is when the printer sent the report. The server asks the printer for a full report only to fill an empty cache, and at most once every five minutes after the printer has been quiet that long. A printer that sends no report within 8 seconds is offline.

    • state: IDLE is idle, PREPARE/SLICING preparing, RUNNING printing, PAUSE paused, FINISH finished and FAILED failed. A FAILED job with 0300400C ("The task was canceled.") in print_error, or in X2D's fail_reason, is cancelled. Any other or missing gcode_state on a connected printer is busy.
    • job (not for idle, busy or offline): name is subtask_name, else the basename of gcode_file (a raw G-code print reports only its file path; that basename is the jobName the print returns); id is task_id, omitted when it is the LAN placeholder "0"; progress is mc_percent; remainingSec is mc_remaining_time (minutes) × 60; layer/totalLayers appear once the printer reports a total; stage is Bambu Studio's words for stg_cur ("Heatbed preheating", "Auto bed leveling").
    • temps: nozzle and bed {actual, target}, and chamber from the H2 chamber controller or the X1 sensor. On H2 and X2D the nozzle is the active extruder's, from device.extruder. P1 and A1 printers have no chamber sensor, so it is omitted.
    • activeFilament: the loaded spool's material and #RRGGBB color. slot is the index ams_slots uses: amsId × 4 + tray for an AMS, the unit id (128 and up) for an AMS-HT, and "ext" for the external spool, or "254" (left) / "255" (right) on dual-nozzle H2D, H2C and X2D. On H2 and X2D it comes from the active extruder's report.
    • problems: a non-zero print_error as 8 hex digits, such as 07008011, and each HMS entry as AAAA_BBBB_CCCC_DDDD, such as 0300_1800_0001_0003. Severity follows Bambu's HMS level: fatal is fatal, serious error, common warning and info info. Each HMS problem links to Bambu's page for that code. Messages come from the English HMS tables bundled with the pinned Bambu Studio release; scripts/generate-hms-messages.mjs turns them into src/safety/hms-messages.ts and the stage words into src/printers/bambu-stages.ts. A model without its own table, such as the P1S, gets the text the other tables share. A code none of them has falls back to the user's own Bambu Studio download: hms_en_<serial prefix>.json, then hms_en.json, in the hms folder next to BambuStudio.conf (see BAMBU_STUDIO_CONFIG). Those files are read only, and Bambu's text is passed through unchanged. Codes that Bambu lists for a printer without text are reported as info.
  • counterpart_discover_printers listens for Bambu printers' own SSDP announcements on UDP 2021 for duration_ms (default 4000, 500–15000) and sends one M-SEARCH: {contract, network, printers: [{kind: "bambu", source: "ssdp", name, host, serial, model?, firmware?}]}. model is the bambu_model value the announced DevModel or the serial prefix names (none for the X1, which bambu_model doesn't take). An announcement counts only from a private IPv4 address that names itself as its Location. network is unavailable when another app (Bambu Studio) holds the port. It never connects to or authenticates with a printer.

  • counterpart_setup_fields says what this server needs to drive a printer, so a client can offer setup without printer tables of its own: {contract, server, kinds: [{kind: "bambu", label, env, fields}], model?}. Each field names its form role (host, serial, model, secret), the .env variable to write (PRINTER_HOST, BAMBU_SERIAL, BAMBU_MODEL, BAMBU_TOKEN) and its older aliases, which this server reads first (a client writing the variable removes them), a label and hint, and whether it is required. The model field lists the bambu_model choices with Bambu's names and fromSerial, the model each three-character serial prefix names. Pass serial to get that model as model. slicer reports the slicer this server would run (type, path, whether it is there, and the Bambu profiles folder) with the SLICER_TYPE, SLICER_PATH (input: "path") and BAMBU_PROFILES_ROOT fields, each required only when what it defaults to is missing. It sends nothing to a printer and returns no secret.

  • counterpart_propose_filaments proposes which loaded AMS spool prints each of a model's colours, before slicing. Pass filaments (index, name, colour #RRGGBB, material), the painted slots no part names, sliced: true for an already-sliced file and, at print time, the person's chosen spools. It reads the live AMS once and returns:

    • trays: the spools to choose from, each with label (A2, as Bambu Studio labels trays), name (Bambu Studio's colour name and the product, "Sunflower Yellow PLA Basic", from the configured slicer's filaments_color_codes.json), colour, material, filamentId, feed, sliceable and, when one resolves, profile (the filament profile path slice_stl takes in load_filaments). An unsliced model lists only spools with a filament profile.
    • proposal: per filament, the nearest spool in OKLab colour with the asked-for material preferred, and a verdict: same-colour (within 0.08), closest (that colour isn't loaded; note says so, "White isn't loaded. Closest loaded: A2 Sunflower Yellow PLA Basic."), no-colour or none. A sliced file's material is a hard rule. One print feeds from one feed.
    • mapping: every filament slot, painted ones included, to its spool, when the proposal or the person's choice can print; problems (code, message, index) says why not, such as spool-changed, sliced-material, mixed-feeds or material-not-loaded.

    get_printer_filaments trays carry the same label and person-readable spool_name.

  • counterpart_make_3mf takes {parts: [{stl_path, name, filament?}], out_path} and writes a new 3MF for multi-colour printing. The parts become one assembly: they keep their relative positions, and each part prints with its own filament number (by default part 1 uses filament 1, part 2 filament 2, and so on). Slice it with slice_stl and one load_filaments profile per filament number, then print it with print_3mf and ams_slots or ams_mapping. The file holds one assembly rather than one object per part, because Bambu Studio 02.08.02.61's CLI arranges separate objects apart, reports G-code path conflicts between stacked objects when arranging is off, and refuses load_filament_ids for 3MF input. It never overwrites a file. The result lists path, objects, filaments (the highest filament number) and each part's triangle count.

print_3mf also returns subtaskName, the subtask_name it sends with the job, when it starts a 3MF with the LAN project_file command: an STL or 3MF sliced by slice_stl, and any 3MF on H2, P2S and A1. On H2 and P2S it also returns taskId, the task_id sent with the job; other models send task_id: "0". A client can match counterpart_print_progress reports to its own print with these. Pre-sliced .gcode.3mf files on P1, X1 and A1 mini (sent as a G-code file), the X2D native route, and the BambuNetwork and Bambu Connect routes return neither.

Released under the GPL-2.0 license. An independent open-source project, not affiliated with Bambu Lab.