bambu-printer-mcp

npm version License: GPL-2.0 TypeScript Node.js Version GitHub stars Downloads

A Bambu Lab-focused MCP server for controlling Bambu printers, manipulating STL files, and managing end-to-end 3MF print workflows from Claude Desktop, Claude Code, or any MCP-compatible client.

This is a stripped-down, Bambu-only fork of mcp-3D-printer-server. All OctoPrint, Klipper, Duet, Repetier, Prusa Connect, and Creality Cloud support has been removed. What remains is a focused, lean implementation for Bambu Lab hardware.

Local handoff note: see REMOTE-DEPLOYMENT.md for the custom H2D/H2S/H2C patches, per-printer MCP split, and remote deployment plan used in this clone.


What's new in bambu-printer-mcp

This fork adds a substantial set of printer control tools beyond the upstream mcp-3D-printer-server. Everything listed below is unique to this package.

v1.1.0 — AMS auto-match, camera snapshot, pause/resume, skip objects

  • AMS auto-match by RFID (auto_match_ams on print_3mf) — resolves sliced 3MF filament requirements against live AMS inventory. Handles same-SKU different-color filaments. Dry-run with resolve_3mf_ams_slots.
  • Structured AMS inventory (get_printer_filaments) — per-tray display names, profile resolution tier (exact-model-nozzle/model/generic/unresolved), match confidence, and a summary with recommended auto-slice filament.
  • AMS settle-time retry — transparently retries when AMS data hasn't arrived on the first MQTT push from an idle printer.
  • Camera snapshot (camera_snapshot) — JPEG from the chamber camera. TCP-on-6000 for A1/P1S/P1P, RTSP via ffmpeg for X1/P2S/H2 series.
  • Pause / resume (pause_print, resume_print) — alongside the existing cancel_print.
  • Skip objects (skip_objects) — skip specific object IDs during a running multi-object print. IDs from list_3mf_plate_objects.
  • HMS diagnostics (printer://{host}/hms MCP resource) — read-only error summary with automatic settle retry.
  • Utility controlsset_print_speed (silent/standard/sport/ludicrous), clear_hms_errors, reread_ams_rfid, set_airduct_mode (cooling/heating for H2/P2).
  • H2-family-safe print path — correct project_file format with ams_mapping2 parallel array, H2 firmware quirks handled.
  • BambuStudio CLI auto-flatten (BAMBU_CLI_FLATTEN=true) — works around upstream profile inheritance bugs.
  • Print collar charm (print_collar_charm) — specialized two-color wrapper with fixed tray policy.

v1.1.1 — AMS dryer control (current)

  • AMS dryer start/stop (set_ams_drying) — sends print.ams_control MQTT command. Works on heated AMS units (AMS Pro / AMS-HT). Action: start or stop, target by AMS index 0–3.
  • Same-SKU different-color fix for auto_match_ams.
  • AMS and HMS settle-time retry for idle printers.
  • Validation script (scripts/validate-printer.mjs) for live printer testing.

Full changelog at CHANGELOG.md.

Table of Contents


Description

bambu-printer-mcp is a Model Context Protocol server that gives Claude (or any MCP client) direct control over Bambu Lab 3D printers. The verified end-to-end path is: slice in Bambu Studio, export a .gcode.3mf, hand the path to print_3mf — the server reads the slicer's metadata out of the 3MF, builds the correct AMS mapping, uploads over FTPS, and starts the print via an MQTT project_file command. See docs/SLICING.md for the full recipe and why in-process slicing is not the recommended path.

What this is not. This package intentionally supports only Bambu Lab printers. It does not include adapters for OctoPrint, Klipper (Moonraker), Duet, Repetier, Prusa Connect, or Creality Cloud. If you need multi-printer support, use the parent project mcp-3D-printer-server instead.

Why a separate package? The parent project carries all printer adapters in a single binary. When working exclusively with Bambu hardware, that breadth adds unnecessary weight. This fork strips the project to its Bambu core for a smaller, faster install. Both packages share the same protocol fixes and safety features.

Note on resource usage. STL manipulation loads entire mesh geometry into memory. For large or complex STL files (greater than 10 MB), these operations can be memory-intensive. See General Limitations and Considerations for details.


Features

  • Get detailed printer status: temperatures (nozzle, bed, chamber), print progress, current layer, time remaining, and live AMS slot data
  • Query live AMS inventory with resolved Bambu/Orca filament profile paths via get_printer_filaments. Includes per-tray display names, match confidence (high/medium/low/none), resolution tier (exact-model-nozzle/model/generic/unresolved), and a summary with recommended auto-slice filament. Retries automatically when AMS data hasn't arrived yet (common on first MQTT push from idle printers).
  • List, upload, and delete files on the printer's SD card via FTPS
  • Capture a JPEG snapshot from the chamber camera. Supports A1, A1 mini, P1S, P1P (TCP-on-6000), and X1, X1C, X1E, P2S, H2, H2S, H2D, H2C, H2D Pro (RTSP via ffmpeg). Requires ffmpeg in PATH for the RTSP path.
  • Upload and print pre-sliced .gcode.3mf files with full plate selection and calibration flag control (recommended path — see docs/SLICING.md)
  • Optional single-color auto-slice path via BambuStudio CLI. Set BAMBU_CLI_FLATTEN=true to enable a workaround that flattens BBL profile inheritance before invoking the CLI — works around upstream bugs in BambuStudio CLI mode (#9636, #9968). Single-color smoke is verified on H2S/H2D/X1C/P1S; H2C requires Bambu Studio 2.4.0 or newer and should use BAMBU_MODEL=h2c, not an H2D fallback. H2D two-color CLI slicing is blocked upstream (#10408); use a GUI-sliced .gcode.3mf for that workflow. Default off; Path A (GUI-slice) remains the recommended workflow for non-BBL profiles, multi-color H2 jobs, or first-time prints. See docs/SLICING.md.
  • Parse AMS mapping from the 3MF's embedded slicer metadata (Metadata/plate_<n>.json + gcode filament header) and send it correctly formatted per the OpenBambuAPI spec, with correct H2S/H2D/H2C ams_mapping2 parallel array format
  • Auto-match AMS slots by RFID (auto_match_ams flag on print_3mf). Resolves required tray_info_idx from the sliced 3MF against live AMS inventory. Handles same-SKU different-color filaments by matching on (tray_info_idx, tray_color) and tracking already-claimed slots. Dry-run with resolve_3mf_ams_slots before printing.
  • Cancel, pause, and resume in-progress print jobs via MQTT
  • Skip specific objects during a running multi-object print via skip_objects (use list_3mf_plate_objects to find object IDs first)
  • Set print speed mode (silent/standard/sport/ludicrous), clear HMS/print errors, trigger AMS RFID re-read, and control H2/P2 airduct mode (cooling/heating) via MQTT
  • Start/stop AMS filament drying (set_ams_drying) on heated AMS units (AMS Pro / AMS-HT). Sends print.ams_control MQTT command.
  • Set nozzle and bed temperature via G-code dispatch over MQTT
  • Set fan speed (part, auxiliary, chamber) and chamber light mode (on/off/flashing) via MQTT
  • Read HMS (Health Management System) diagnostics as an MCP resource at printer://{host}/hms — read-only error summary from the printer, with automatic settle retry
  • Start G-code files already stored on the printer
  • Collar charm print wrapper (print_collar_charm) — specialized two-color workflow with fixed tray policy for inner (black, AMS 1 slot 1) and outer (white, AMS 2 slot 1) charm parts
  • STL manipulation: scale, rotate, extend base, merge vertices, center at origin, lay flat, and inspect model info
  • Slice STL or 3MF files using BambuStudio, OrcaSlicer, PrusaSlicer, Cura, or Slic3r
  • Inspect slicer settings from a saved 3MF template or extracted profile via get_slice_settings
  • Enumerate saved slicing templates from the local registry via list_templates
  • Save templates into the local registry via save_template
  • Slice directly from a named template via slice_with_template
  • For simple single-material slices, auto-select the printer's current or first loaded AMS filament when no explicit slicer profile or load_filaments override is provided
  • Template-driven slicing can reuse a saved 3MF's process settings while still pulling the live printer filament choice over MQTT
  • Optional Blender MCP bridge for advanced mesh operations
  • Dual transport: stdio (default, for Claude Desktop / Claude Code) and Streamable HTTP

Installation

Prerequisites

  • Node.js 18 or higher
  • npm
  • BambuStudio (optional -- only needed for slicing) -- download from bambulab.com. Required by slice_stl and print_3mf auto-slice (when a 3MF has no embedded gcode). Not needed if you only print pre-sliced 3MF files. Default path: /Applications/BambuStudio.app/Contents/MacOS/BambuStudio (macOS); set SLICER_PATH if installed elsewhere.

Run without installing (npx)

The fastest way to get started. No global install required:

npx @rowbotik/bambu-printer-mcp

Set environment variables inline or via a .env file in your working directory (see Configuration).

Install globally from npm

npm install -g @rowbotik/bambu-printer-mcp

After installation, the bambu-printer-mcp command is available in your PATH.

Install from source

git clone https://github.com/rowbotik/bambu-printer-mcp.git
cd bambu-printer-mcp
npm install
npm run build
npm link

npm link makes the bambu-printer-mcp binary available globally without publishing to npm.


Configuration

Create a .env file in the directory where you run the server, or pass environment variables directly in your MCP client config. All printer connection variables can also be passed as tool arguments on a per-call basis, which is useful when working with multiple printers.

# --- Bambu printer connection (required for all printer tools) ---
PRINTER_HOST=192.168.1.100        # IP address of your Bambu printer on the local network
BAMBU_SERIAL=01P00A123456789      # Printer serial number (see Finding Your Serial Number below)
BAMBU_TOKEN=your_access_token     # LAN access token from printer touchscreen
# Compatible aliases also accepted:
# BAMBU_PRINTER_HOST / BAMBU_PRINTER_SERIAL / BAMBU_PRINTER_ACCESS_TOKEN

# --- Printer model (CRITICAL for safe operation) ---
BAMBU_MODEL=p1s                   # Your printer model: p1s, p1p, p2s, x1c, x1e, a1, a1mini, h2d, h2s, h2c
# Alias also accepted: BAMBU_PRINTER_MODEL
BED_TYPE=textured_plate           # Bed plate type: textured_plate, cool_plate, engineering_plate, hot_plate, supertack_plate
NOZZLE_DIAMETER=0.4               # Nozzle diameter in mm (default: 0.4)

# --- Slicer configuration (required for slice_stl and print_3mf auto-slice) ---
SLICER_TYPE=bambustudio           # Options: bambustudio, prusaslicer, orcaslicer, cura, slic3r
SLICER_PATH=/Applications/BambuStudio.app/Contents/MacOS/BambuStudio
                                  # Default on macOS. Adjust for your OS and install path.
# Alias also accepted: BAMBU_STUDIO_PATH
SLICER_PROFILE=                   # Optional: path to a slicer profile/config file

# --- Temporary file directory ---
TEMP_DIR=/tmp/bambu-mcp-temp      # Directory for intermediate files. Created automatically if absent.

# --- MCP transport ---
MCP_TRANSPORT=stdio               # Options: stdio (default), streamable-http

# --- Streamable HTTP transport (only used when MCP_TRANSPORT=streamable-http) ---
MCP_HTTP_HOST=127.0.0.1
MCP_HTTP_PORT=3000
MCP_HTTP_PATH=/mcp
MCP_HTTP_STATEFUL=true
MCP_HTTP_JSON_RESPONSE=true
MCP_HTTP_ALLOWED_ORIGINS=http://localhost

# --- Optional Blender MCP bridge ---
BLENDER_MCP_BRIDGE_COMMAND=       # Shell command to invoke your Blender MCP bridge executable

Environment variables reference

Variable Default Required Description
PRINTER_HOST localhost Yes IP address of the Bambu printer. Alias: BAMBU_PRINTER_HOST
BAMBU_SERIAL Yes Printer serial number. Alias: BAMBU_PRINTER_SERIAL
BAMBU_TOKEN Yes LAN access token. Alias: BAMBU_PRINTER_ACCESS_TOKEN
BAMBU_MODEL Yes Printer model: p1s, p1p, p2s, x1c, x1e, a1, a1mini, h2d, h2s, h2c. Required for safe operation -- determines the correct G-code generation. Alias: BAMBU_PRINTER_MODEL. If omitted and the MCP client supports elicitation, the server will ask you interactively. Use h2c for H2C; do not use h2d as a fallback.
BED_TYPE textured_plate No Bed plate type: textured_plate, cool_plate, engineering_plate, hot_plate, supertack_plate
NOZZLE_DIAMETER 0.4 No Nozzle diameter in mm. Used to select the correct BambuStudio machine preset.
SLICER_TYPE bambustudio No Slicer to use for slicing operations
SLICER_PATH BambuStudio macOS path No Full path to the slicer executable. Alias: BAMBU_STUDIO_PATH
SLICER_PROFILE No Path to a slicer profile or config file
TEMP_DIR ./temp No Directory for intermediate files
MCP_TRANSPORT stdio No Transport mode: stdio or streamable-http
MCP_HTTP_HOST 127.0.0.1 No HTTP bind address (HTTP transport only)
MCP_HTTP_PORT 3000 No HTTP port (HTTP transport only)
MCP_HTTP_PATH /mcp No HTTP endpoint path (HTTP transport only)
MCP_HTTP_STATEFUL true No Enable stateful HTTP sessions
MCP_HTTP_JSON_RESPONSE true No Return structured JSON alongside text responses
MCP_HTTP_ALLOWED_ORIGINS No Comma-separated list of allowed CORS origins
BLENDER_MCP_BRIDGE_COMMAND No Command to invoke Blender MCP bridge
BAMBU_CLI_FLATTEN false No When true, the MCP flattens BBL profile inheritance before invoking the BambuStudio CLI. Workaround for upstream issues #9636 / #9968. BBL printers only. Single-color smoke verified on H2S/H2D/X1C/P1S; H2C requires Bambu Studio 2.4.0 or newer. H2D two-color CLI slicing remains blocked by #10408. See docs/SLICING.md.
BAMBU_PROFILES_ROOT derived from SLICER_PATH No Override path to the BambuStudio Resources/profiles directory used by the CLI flattener. Useful for non-standard installs or dev environments.

SuperTack can be passed for pre-sliced print jobs, but BambuStudio CLI slicing currently fails fast for supertack_plate because the accepted CLI bed identifier is not verified. Use a pre-sliced 3MF for SuperTack until this is confirmed.


Usage

Add this server to your MCP client's config (Claude Desktop, Claude Code, Cursor, Codex CLI, or any MCP-compatible client). The config format is the same everywhere -- an mcpServers entry with the command and env vars:

{
  "mcpServers": {
    "bambu-printer": {
      "command": "npx",
      "args": ["-y", "@rowbotik/bambu-printer-mcp"],
      "env": {
        "PRINTER_HOST": "192.168.1.100",
        "BAMBU_SERIAL": "01P00A123456789",
        "BAMBU_TOKEN": "your_access_token",
        "BAMBU_MODEL": "p1s",
        "SLICER_TYPE": "bambustudio",
        "SLICER_PATH": "/Applications/BambuStudio.app/Contents/MacOS/BambuStudio"
      }
    }
  }
}

Where this config lives depends on your client:

Client Config location
Claude Desktop (macOS) ~/Library/Application Support/Claude/claude_desktop_config.json
Claude Desktop (Windows) %APPDATA%\Claude\claude_desktop_config.json
Claude Code (project) .mcp.json in project root
Claude Code (global) ~/.claude/settings.json
Cursor MCP settings in Cursor preferences
Codex CLI MCP config per Codex docs

Restart your client after editing the config.

Recommended: use with codemode-mcp

For any MCP server with a large tool surface, wrapping it behind codemode-mcp dramatically reduces token usage. Instead of exposing every tool definition to the model (which can consume tens of thousands of tokens per turn), codemode lets the agent write code against a two-tool interface (search() and execute()), loading only the tools it needs on demand.

Anthropic and Cloudflare independently demonstrated this pattern reduces MCP token costs by up to 98%:

This applies to all MCP servers, not just this one.


Enabling Developer Mode (Required)

This MCP server communicates directly with your printer over your local network using MQTT and FTPS. For this to work, Developer Mode must be enabled on the printer. Without it, the printer will reject third-party LAN connections even if you have the correct access code.

On H2D/H2-series firmware, the printer may stream push_status data without ever answering the legacy get_version handshake used by older libraries. This fork treats the live status stream as authoritative and does not require that extra ACK before considering the connection usable.

Developer Mode is available on the following firmware versions and later:

Series Minimum Firmware
P1 Series (P1P, P1S) 01.08.02.00
X1 Series (X1C, X1E) 01.08.03.00
A1 Series (A1, A1 Mini) 01.05.00.00
H2D 01.01.00.01

If your firmware is older than these versions, update through Bambu Studio or the Bambu Handy app before proceeding.

Step 1: Navigate to Network Settings

On the printer's touchscreen, go to Settings, then select the Network (WLAN) page. You should see your WiFi network name, IP address, and the LAN Only Mode toggle.

Step 2: Enable LAN Only Mode

Toggle LAN Only Mode to ON. This enables direct local network communication protocols (MQTT on port 8883 and FTPS on port 990) that this server requires.

Important: Enabling LAN Only Mode disconnects the printer from Bambu Lab's cloud services. The Bambu Handy mobile app will stop working while this mode is active. Bambu Studio and OrcaSlicer can still connect over LAN.

Step 3: Enable Developer Mode

Once LAN Only Mode is on, a Developer Mode option appears in the same settings menu. Toggle it ON. This allows third-party clients (like this MCP server) to authenticate and send commands over MQTT.

Step 4: Note the Access Code

The Access Code displayed on the network settings screen is your LAN access token. You will need this value for the BAMBU_TOKEN environment variable.

The access code can be refreshed by tapping the circular arrow icon next to it. If you refresh it, any existing connections using the old code will be disconnected and you will need to update your configuration with the new code.


Finding Your Bambu Printer's Serial Number and Access Token

Two values are required to connect directly to a Bambu Lab printer over your local network: the printer's serial number and its LAN access token (the Access Code from Developer Mode setup above).

Serial number

The serial number is printed on a sticker on the back or underside of the printer. It typically follows one of these formats:

  • P1 Series: begins with 01P
  • X1 Series: begins with 01X
  • A1 Series: begins with 01A

You can also find it on the printer's touchscreen. Navigate to Settings and select the Device Info page:

The Printer line shows your serial number. In Bambu Studio, you can also find it under Device > Device Management in the printer information panel.

LAN access token

The access token is the Access Code shown on the printer's network settings screen. It is separate from your Bambu Cloud account password. If you followed the Developer Mode setup above, you already have this value.

P1 Series (P1P, P1S):

  1. On the printer touchscreen, go to Settings.
  2. Select the Network / WLAN page.
  3. The Access Code is displayed at the bottom of the screen.

X1 Series (X1C, X1E):

  1. On the printer touchscreen, go to Settings.
  2. Select Network.
  3. Enable LAN Only Mode and Developer Mode if not already on.
  4. The Access Code appears on this screen.

A1 and A1 Mini:

  1. Open the Bambu Handy app on your phone.
  2. Connect to your printer.
  3. Navigate to Settings > Network.
  4. The Access Code is shown here.

Your printer must also be logged into a Bambu Cloud account for LAN mode to function. You can verify this on the cloud/account settings screen:

Troubleshooting: If the LAN Only Mode or Developer Mode options are not visible, your printer firmware is likely outdated. Update to the latest firmware version through Bambu Studio or the Bambu Handy app and try again.


AMS (Automatic Material System) Setup

The Bambu AMS is a multi-spool feeder that lets you assign different filaments to different parts of a multi-color or multi-material print. This section explains how AMS slot mapping works with this MCP server.

How AMS slots work

The AMS has 4 slots per unit, numbered 0 through 3. If you have multiple AMS units chained together, the second unit's slots are 4 through 7, and so on. When you slice a model in Bambu Studio or OrcaSlicer, each color/material in the print is assigned to a specific AMS slot.

Automatic AMS mapping from the 3MF

When you slice a model in Bambu Studio, the slicer embeds AMS mapping information inside the 3MF file at Metadata/project_settings.config. The print_3mf tool reads this file automatically and extracts the correct mapping. In most cases, you do not need to specify ams_mapping manually -- the tool handles it.

Manual AMS mapping

If you need to override the embedded mapping (for example, you swapped filament positions since slicing), pass the ams_mapping array to print_3mf:

{
  "three_mf_path": "/path/to/model.3mf",
  "ams_mapping": [0, 2],
  "use_ams": true
}

Each element in the array corresponds to a filament slot used in the print file, in the order they appear in the slicer. The value is the physical AMS slot number (0-based) where that filament is currently loaded. In the example above, the first filament in the print uses AMS slot 0, and the second uses AMS slot 2.

The server pads this array to the 5 elements required by the printer's MQTT protocol. An ams_mapping of [0, 2] becomes [0, 2, -1, -1, -1] on the wire, where -1 indicates unused positions.

Single-material prints

For a single-material print (the most common case), the default mapping is [-1, -1, -1, -1, 0], which tells the printer to pull filament from AMS slot 0. If your filament is in a different slot, specify it:

{
  "three_mf_path": "/path/to/model.3mf",
  "ams_mapping": [2]
}

This tells the printer to use AMS slot 2 for the single filament in the print.

Printing without AMS

If you are using the direct-feed spool holder (no AMS attached) or want to bypass the AMS entirely, set use_ams to false:

{
  "three_mf_path": "/path/to/model.3mf",
  "use_ams": false
}

Auto-match AMS by RFID

For pre-sliced 3MFs that declare filament types, the auto_match_ams flag on print_3mf (or the standalone resolve_3mf_ams_slots dry-run tool) automatically resolves the required filaments against your live AMS inventory. The matcher works as follows:

  1. Reads the required tray_info_idx values from the 3MF's Metadata/slice_info.config and Metadata/plate_<n>.json
  2. Reads your live AMS trays from the printer's MQTT status push
  3. Matches on (tray_info_idx, tray_color) — so two filaments of the same SKU but different colors (e.g. two GFG02 PETG HF spools in black and white) resolve to different slots
  4. Tracks already-claimed slots so two requirements can't collapse onto the same physical position
  5. Falls back to SKU-only matching when the 3MF carries no color data or only one tray of that SKU is loaded

If resolution fails, returns a structured missing report with per-requirement reasons:

  • no_loaded_match — no AMS tray of that SKU is loaded
  • color_mismatch — the SKU matches but the loaded color differs
  • exhausted — all matching trays are already claimed by other requirements
  • no_sku — the 3MF doesn't declare a tray_info_idx for this filament

Dry-run with resolve_3mf_ams_slots before printing to preview the match without uploading or starting a job.

AMS settle-time handling

The first MQTT status push from an idle printer is often sparse (model/module info only) — AMS slot data arrives on a second push. The server's filament inventory and HMS handlers both retry after a 1.5-second settle window when the expected data isn't present in the first response. This is transparent to the caller.

Checking AMS status

Use get_printer_filaments for the parsed, enriched view (profile paths, display names, match confidence) or get_printer_status for the raw AMS data from the printer:

"What filaments are loaded in my AMS right now?"

Bambu Communication Notes (MQTT and FTP)

Bambu Lab printers do not use a conventional REST API. Instead, they expose two local protocols that this server uses directly:

MQTT (port 8883, TLS): All printer commands and state reports flow over an MQTT broker running on the printer itself. The broker requires your serial number as the client ID and your access token as the password. Commands like starting a print, cancelling a job, and dispatching G-code lines are all MQTT publishes to the device topic. Status data is received by subscribing to the printer's report topic and requesting a push_all refresh. This implementation is based on community reverse engineering documented in the OpenBambuAPI project.

FTPS (port 990, implicit TLS): File operations (upload and directory listing) use FTPS. The printer's SD card is accessible as a filesystem with directories including cache/ (for 3MF and G-code print files), timelapse/, and logs/. Authentication uses the username bblp and your access token as the password.

What this fork fixes

Both this package and the parent project (mcp-3D-printer-server) include fixes for two protocol-level issues in the underlying bambu-js library.

Bug 1: FTP double-path error in bambu-js.

The bambu-js library's sendFile method has a path construction bug. It calls ensureDir to change the working directory into the target directory (e.g., /cache), and then calls uploadFrom with the full relative path including the directory prefix (e.g., cache/file.3mf). The result is that the file lands at the wrong path on the printer (e.g., /cache/cache/file.3mf instead of /cache/file.3mf), and the subsequent print command fails because it references a file that does not exist at the expected path.

This fork bypasses bambu-js for all uploads and uses basic-ftp directly. The upload function (ftpUpload) connects to the printer, resolves the absolute remote path, changes to the correct directory with ensureDir, and then uploads using only the basename -- avoiding the double-path construction entirely.

// From src/printers/bambu.ts
private async ftpUpload(host, token, localPath, remotePath): Promise<void> {
  const client = new FTPClient(15_000);
  await client.access({ host, port: 990, user: "bblp", password: token,
                        secure: "implicit", secureOptions: { rejectUnauthorized: false } });
  const absoluteRemote = remotePath.startsWith("/") ? remotePath : `/${remotePath}`;
  const remoteDir = path.posix.dirname(absoluteRemote);
  await client.ensureDir(remoteDir);
  // basename only -- no double-path
  await client.uploadFrom(localPath, path.posix.basename(absoluteRemote));
  client.close();
}

Bug 2: AMS mapping format in the project_file MQTT command.

The bambu-js library's project file command hardcodes use_ams: true and does not support the ams_mapping field at all. Without the fix, the mapping is a simple array of slot indices (e.g., [0, 2]), which does not match the OpenBambuAPI specification.

According to the OpenBambuAPI spec, P1/A1/X1-series printers use a 5-element ams_mapping array where position i is the project filament index and the value is the AMS slot feeding that filament. For example, a single-filament print from AMS slot 0 sends [0, -1, -1, -1, -1].

This fork sends the project_file command directly via bambu-node (bypassing bambu-js entirely for print initiation) and constructs the mapping in the format the target firmware expects:

// P1/A1/X1-series: 5-element project lookup table
ams_mapping = [0, -1, -1, -1, -1];

// H2S/H2D/H2C: project-length lookup table + parallel ams_mapping2
ams_mapping = [-1, 1, -1, -1];
ams_mapping2 = [
  { ams_id: 255, slot_id: 255 },
  { ams_id: 0, slot_id: 1 },
  { ams_id: 255, slot_id: 255 },
  { ams_id: 255, slot_id: 255 }
];

The command payload also includes all required fields per the OpenBambuAPI spec: param (the internal gcode path within the 3MF), url (the sdcard path), md5 (computed from the plate's embedded gcode), and all calibration flags.

Verified print procedure (H2S, LAN-only, no client cert)

This is the sequence that successfully started a print on an H2S running current (post-Jan 2025) firmware in LAN-only mode. It's documented here because several common approaches fail on this firmware, and this fork's transport is what makes it reliable.

Result: print started in RUNNING state, printer accepted the MQTT project_file command, no client certificate was required. Authentication was plain bblp + LAN access code over TLS with rejectUnauthorized: false.

What doesn't work on stock bambu-cli:

  • bambu-cli print start <file> and bambu-cli files upload both fail with 522 SSL connection failed: session reuse required. Bambu's FTPS server requires TLS session reuse between the control and data channels, which the Go FTPS client in bambu-cli does not negotiate correctly.
  • bambu-cli print start --no-upload still opens an FTPS session (to stat the remote file) and hits the same 522.

What works — two-step upload + MQTT dispatch:

  1. Upload the .gcode.3mf via curl (curl's OpenSSL backend negotiates FTPS session reuse correctly):

    curl -k --ftp-pasv --ssl-reqd \
      -u "bblp:<ACCESS_CODE>" \
      -T /path/to/file.gcode.3mf \
      "ftps://<PRINTER_IP>:990/<remote-name>.gcode.3mf"
    

    Keep <remote-name> simple ASCII, ending in .gcode.3mf. The file lands at the FTP root, which corresponds to /data/ on the printer's SD card.

  2. Send the project_file command over MQTT to device/<SERIAL>/request:

    import mqtt from "mqtt";
    const payload = {
      print: {
        sequence_id: "0",
        command: "project_file",
        param: "Metadata/plate_1.gcode",          // path inside the 3MF
        subtask_name: "<remote-name>.gcode.3mf",
        file: "<remote-name>.gcode.3mf",
        url: "ftp:///<remote-name>.gcode.3mf",    // three slashes, FTP root
        md5: "",
        project_id: "0", profile_id: "0", task_id: "0", subtask_id: "0",
        timelapse: false,
        bed_type: "auto",
        bed_leveling: true, bed_levelling: true,
        flow_cali: true, vibration_cali: true, layer_inspect: true,
        use_ams: true,
        ams_mapping: [0, -1, -1, -1, -1]
      }
    };
    const client = mqtt.connect(`mqtts://<PRINTER_IP>:8883`, {
      username: "bblp",
      password: "<ACCESS_CODE>",
      rejectUnauthorized: false,
    });
    client.on("connect", () => {
      client.publish(`device/<SERIAL>/request`, JSON.stringify(payload));
    });
    

Notes:

  • url must be ftp:///<filename> (three slashes) — the empty host component is required; the printer rejects ftp://<filename> as "unsupported print file path or name".
  • param uses the internal plate path inside the 3MF (Metadata/plate_1.gcode for plate 1), not a filesystem path.
  • md5: "" is accepted; populating it is optional.
  • On AMS-equipped H2 printers, use_ams: false does not suppress mapping lookup if the sliced file declares filaments. The working H2 path is to send use_ams: true plus a valid mapping. For H2, the mapping length must match the project-level filament declaration length, and the populated positions must match plate_<n>.json.filament_ids. Prefer ams_slots at the tool layer and let the server expand it. 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.
  • No client X.509 certificate was needed. The earlier assumption that post-Jan 2025 firmware mandates mTLS on all models does not hold for the H2S in LAN mode — user/password over TLS is sufficient.
  • The MCP server's ftpUpload helper (basic-ftp with secure: "implicit" and a short idle timeout) performs the equivalent upload natively and is the preferred path when using the server itself; the curl form is the manual-debug equivalent.

Available Tools

STL Manipulation Tools

All STL tools load the full mesh geometry into memory. For files larger than 10 MB, monitor memory usage and prefer testing on smaller files first.

get_stl_info

Inspect an STL file without modifying it. Returns bounding box dimensions, face count, vertex count, and model center.

{
  "stl_path": "/path/to/model.stl"
}

scale_stl

Scale an STL model along individual axes. Omit any axis to leave it unchanged (defaults to 1.0).

{
  "stl_path": "/path/to/model.stl",
  "scale_x": 1.5,
  "scale_y": 1.5,
  "scale_z": 1.0
}

For uniform scaling, set all three axes to the same value:

{
  "stl_path": "/path/to/model.stl",
  "scale_x": 2.0,
  "scale_y": 2.0,
  "scale_z": 2.0
}

rotate_stl

Rotate an STL model around one or more axes. Angles are in degrees. Omitted axes default to 0.

{
  "stl_path": "/path/to/model.stl",
  "angle_x": 0,
  "angle_y": 0,
  "angle_z": 90
}

extend_stl_base

Add solid geometry underneath the model to increase its base height. Useful for improving bed adhesion on models with a small or unstable footprint.

{
  "stl_path": "/path/to/model.stl",
  "extension_height": 3.0
}

extension_height is in millimeters.

merge_vertices

Merge vertices that are closer together than the specified tolerance. This can close small gaps in a mesh and slightly reduce file size. Useful as a cleanup step before slicing.

{
  "stl_path": "/path/to/model.stl",
  "tolerance": 0.01
}

tolerance is in millimeters and defaults to 0.01 if omitted.

center_model

Translate the model so the center of its bounding box sits at the world origin (0, 0, 0). Useful before applying transformations or exporting for use in another tool.

{
  "stl_path": "/path/to/model.stl"
}

lay_flat

Identify the largest flat surface on the model and rotate the model so that face is oriented downward on the XY plane (Z = 0). This is a common preparation step before slicing to minimize the need for supports.

{
  "stl_path": "/path/to/model.stl"
}

Note: this works best on models with a clearly dominant flat face. Results on organic or rounded shapes may be unpredictable.

Printer Control Tools

All printer tools accept optional host, bambu_serial, and bambu_token arguments. If omitted, values