bambu-printer-mcp
# bambu-printer-mcp
[](https://dmontgomery40.github.io/bambu-printer-mcp/)
> **Thank you, [FULU Foundation](https://www.fulu.org/), [Louis Rossmann](https://www.youtube.com/watch?v=1jhRqgHxEP8), and the [OrcaSlicer-bambulab contributors](https://github.com/FULU-Foundation/OrcaSlicer-bambulab).** We stand with open-source developers, the right to repair, and your right to control hardware you own. You should be able to choose your software and print without a vendor cloud standing in the way.
>
> **Want to skip Bambu's software and cloud?** Use FULU OrcaSlicer-bambulab to slice and export, then this MCP's direct LAN path on supported printers and firmware. That workflow does not require Bambu Studio, Bambu Connect, or Bambu Cloud. Start with the [FULU setup guide](https://github.com/DMontgomery40/bambu-printer-mcp/blob/main/docs/FULU.md). The optional BambuNetwork bridge is a separate path that still uses Bambu's networking runtime; cloud jobs still use Bambu's services.
[](https://www.npmjs.com/package/bambu-printer-mcp)
[](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
[](https://www.typescriptlang.org/)
[](https://nodejs.org/en/download/)
[](https://github.com/DMontgomery40/bambu-printer-mcp)
[](https://www.npmjs.com/package/bambu-printer-mcp)
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.
**[Browse the documentation site](https://dmontgomery40.github.io/bambu-printer-mcp/)** for searchable setup guides, slicing and AMS guidance, and the full tool reference. It is generated from this README and the docs folder.
Built with help from our [contributors](./CONTRIBUTORS.md). Huge thanks to everyone sharing fixes, careful bug reports, and real printer testing!
This is a stripped-down, Bambu-only fork of [mcp-3D-printer-server](https://github.com/DMontgomery40/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.
---
## Set up with your agent
Tell your agent your printer's **model and LAN address**, if you know them, then copy and paste this:
```text
Install bambu-printer-mcp in the agent/harness I'm using now.
Read https://github.com/DMontgomery40/bambu-printer-mcp/blob/main/docs/SETUP.md
and https://github.com/DMontgomery40/bambu-printer-mcp/blob/main/docs/FULU.md
for the current setup instructions and supported workflows.
Detect my OS and harness, then use its native MCP configuration or installer.
Preserve my existing servers and settings. Prefer the published npm package
(npx -y bambu-printer-mcp, stdio); use Node.js 24 if a runtime is needed.
Find existing printer settings in relevant local configuration or available
LAN discovery. Confirm the detected printer's model, address, and identity
with me before connecting. Ask only for values you cannot find:
PRINTER_HOST, BAMBU_MODEL, BAMBU_SERIAL, and BAMBU_TOKEN (the LAN access code).
Keep credentials in local/private configuration; do not repeat access codes
or tokens in chat. Never guess the printer model.
Use direct LAN printing by default. Explain any LAN/Developer Mode setting
I need to enable. Prefer FULU OrcaSlicer-bambulab GUI slicing/export; discover
an existing slicer before suggesting an install. For FULU/Orca CLI auto-slicing,
require MCP 1.1.11+ and its matching installed profile tree (see guide).
A slicer is not needed here to print a pre-sliced file. Configure the optional FULU
BambuNetwork bridge only if I choose it, and explain its runtime/auth needs.
X2D supports status and slicing; native printing requires macOS or x86-64 Linux and a locally built helper.
If this harness does not already provide code mode or an equivalent, suggest
a compatible code-mode integration as an optional addition. It is not required;
finish ordinary MCP setup without it unless I choose to add it.
Verify that the MCP initializes, lists its tools, and reads printer status.
Do not start a print or change printer settings as a setup test. Tell me what
worked and whether I need to restart or reload the harness.
```
[Setup reference](https://github.com/DMontgomery40/bambu-printer-mcp/blob/main/docs/SETUP.md) · [FULU guide](https://github.com/DMontgomery40/bambu-printer-mcp/blob/main/docs/FULU.md) · [Optional code mode](https://github.com/DMontgomery40/bambu-printer-mcp/blob/main/docs/SETUP.md#optional-code-mode)
## What to ask your agent
Ask for the result you want, not the steps. Current models plan across tools: they search the web, read photos, look up exact dimensions, edit models, slice, and print. Expect a question or two when a choice matters, such as which filament to use or whether to start the print.
This server provides the printer, slicing, AMS, and mesh tools. Web search, photos, and Blender edits come from your agent and its other connections, such as a [Blender MCP server](#blender-mcp). Printing a model your agent edited uses CLI slicing with your installed slicer presets; see the [slicing guide](https://github.com/DMontgomery40/bambu-printer-mcp/blob/main/docs/SLICING.md).
### Start from anything
- **"Here's a phone stand on MakerWorld. Make it fit the new iPhone and print it in black."**\
Your agent looks up Apple's published dimensions, adjusts the stand in Blender, slices it for your printer, and finds the AMS tray with black filament.
- **"I want one of these."** *(with a photo of a planter you saw at a café)*\
It works out the shape and size from the photo, finds or models a matching design, and asks about anything the photo can't show.
- **"Can you print a replacement?"** *(with a photo of a snapped dishwasher rack clip)*\
It asks for a measurement or two where the fit matters, models the part, and prints it in a loaded material that suits the job.
### From anywhere, while it prints
If your agent is always on, such as OpenClaw or Hermes Agent running on a computer at home, message it from Telegram or any other chat app. For direct LAN printing, the server runs on the printer's local network; you don't have to.
- **"How's the print going? Send me a picture."**\
It reports progress and time remaining, and sends a snapshot from the chamber camera.
- **"The corner is lifting. Pause it."**\
It pauses the job so you can decide whether to resume or cancel.
- **"One of the parts came loose. Skip it and keep printing the rest."**\
It finds that object on the plate and skips only that one.
- **"Start drying the PETG so it's ready when I get home."**\
On a heated AMS, it starts the drying cycle for the unit holding that spool.
### Before you print
- **"Start the bracket I sliced last night."**\
It checks the camera image before it sends the job.
- **"What's loaded in the AMS? Print this in whatever black I have."**\
It reads the live AMS inventory and matches the file's filaments to your trays.
- **"Are there any errors on the printer?"**\
It reads the printer's HMS diagnostics and explains them.
<details>
<summary><strong>Start here</strong></summary>
## Start here
| I want to… | Read next |
|---|---|
| Use open-source slicing and a cloud-free print workflow | [FULU setup: slicer, LAN, and optional bridge](https://github.com/DMontgomery40/bambu-printer-mcp/blob/main/docs/FULU.md) |
| Connect this MCP to my agent | [Copy the setup request](#set-up-with-your-agent) |
| See what my agent can do with it | [What to ask your agent](#what-to-ask-your-agent) |
| Troubleshoot setup or configure it manually | [Installation, environment variables, and LAN reference](https://github.com/DMontgomery40/bambu-printer-mcp/blob/main/docs/SETUP.md) |
| Prepare a printable file or troubleshoot slicing | [Slicing guide and model routing](https://github.com/DMontgomery40/bambu-printer-mcp/blob/main/docs/SLICING.md) |
| Choose filament trays or inspect a printer | [AMS setup](#ams-automatic-material-system-setup) and [printer tools](#printer-control-tools) |
| Edit an STL through Blender | [Blender MCP setup](#blender-mcp) |
| See release changes or contributor credit | [Changelog](https://github.com/DMontgomery40/bambu-printer-mcp/blob/main/CHANGELOG.md), [releases](https://github.com/DMontgomery40/bambu-printer-mcp/releases), and [contributors](./CONTRIBUTORS.md) |
</details>
<details>
<summary><strong>What's new in bambu-printer-mcp</strong></summary>
## What's new in bambu-printer-mcp
See the [changelog](https://github.com/DMontgomery40/bambu-printer-mcp/blob/main/CHANGELOG.md) for versioned changes. Recent releases add reliable npm and desktop-extension installs, standard Blender MCP integration, corrected P2S/A1 routing, and safer multi-filament CLI slicing. X2D status and slicing are available, with optional native printing on macOS through the installed Bambu Studio networking plug-in.
</details>
<details>
<summary><strong>Table of Contents</strong></summary>
## Table of Contents
- [Start here](#start-here)
- [Description](#description)
- [FULU and open-source printing](#fulu-and-open-source-printing)
- [Features](#features)
- [Set up with your agent](#set-up-with-your-agent)
- [What to ask your agent](#what-to-ask-your-agent)
- [AMS (Automatic Material System) Setup](#ams-automatic-material-system-setup)
- [Bambu Communication Notes (MQTT and FTP)](#bambu-communication-notes-mqtt-and-ftp)
- [What this fork fixes](#what-this-fork-fixes)
- [Verified print procedure (H2S, LAN-only, no client cert)](#verified-print-procedure-h2s-lan-only-no-client-cert)
- [Available Tools](#available-tools)
- [STL Manipulation Tools](#stl-manipulation-tools)
- [Printer Control Tools](#printer-control-tools)
- [Slicing Tools](#slicing-tools)
- [Advanced Tools](#advanced-tools)
- [Available Resources](#available-resources)
- [Bambu Lab Printer Limitations](#bambu-lab-printer-limitations)
- [General Limitations and Considerations](#general-limitations-and-considerations)
- [Memory usage](#memory-usage)
- [STL manipulation limitations](#stl-manipulation-limitations)
- [Performance considerations](#performance-considerations)
- [Acknowledgements](#acknowledgements)
- [License](#license)
</details>
<details>
<summary><strong>Description</strong></summary>
## Description
`bambu-printer-mcp` is a Model Context Protocol server for Bambu Lab 3D printers. A straightforward workflow is: **slice in FULU OrcaSlicer-bambulab, OrcaSlicer, or Bambu Studio, export a sliced `.gcode.3mf`, then pass its path to `print_3mf`**. The default direct LAN path uploads via FTPS and chooses the MQTT command for the target model. See the [slicing guide](https://github.com/DMontgomery40/bambu-printer-mcp/blob/main/docs/SLICING.md) for CLI options, model routing, and validation limits.
**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](https://github.com/DMontgomery40/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. See each project's changelog for its current fixes and supported workflows.
**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](#general-limitations-and-considerations) for details.
</details>
<details>
<summary><strong>FULU and open-source printing</strong></summary>
## FULU and open-source printing
[FULU OrcaSlicer-bambulab](https://github.com/FULU-Foundation/OrcaSlicer-bambulab) is a supported slicer target (`SLICER_TYPE=orcaslicer-bambulab`; aliases include `fulu-orca` and `orca-studio`). Use its GUI to export a sliced project for direct LAN printing, or configure its CLI with matching installed profiles. From 1.1.11, FULU/Orca share the [machine-preset gate and profile safety checks](https://github.com/DMontgomery40/bambu-printer-mcp/blob/main/docs/FULU.md#fulu-and-orca-cli-safety-limit).
The optional FULU **BambuNetwork bridge** exposes `bambu_network_bridge_status`, `bambu_network_call`, and `print_3mf_bambu_network`, also reachable through `print_3mf` with `connection_mode: "bambu_network"`. Slicer selection does not enable the bridge. It needs a separately installed FULU runtime and an explicit launch command; cloud printing also needs an authenticated BambuNetwork session. On Linux, FULU's v1.0.0 host needs a small `cat` wrapper and the 02.05.02.5x plug-in; the guide covers both. LAN bridge prints open the plug-in's own LAN session with Bambu's printer certificate, which the server supplies.
**[Follow the FULU setup guide](https://github.com/DMontgomery40/bambu-printer-mcp/blob/main/docs/FULU.md)** for the direct LAN recipe, Linux/Windows/macOS bridge setup, connection probes, authentication, and troubleshooting. Bridge protocol tests and a successful handshake do not establish a successful physical print.
</details>
<details>
<summary><strong>Features</strong></summary>
## 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`. Profile suggestions use the selected slicer installation on Windows, macOS and Linux, including an explicit `BAMBU_PROFILES_ROOT`; unavailable profiles leave live inventory readable. 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, X2D (RTSP via ffmpeg). Requires ffmpeg in PATH for the RTSP path.
- Upload and print pre-sliced `.3mf` projects with checked plate selection and calibration flags. Legacy `.gcode.3mf` routes require a single external-spool-only plate; see the [slicing guide](https://github.com/DMontgomery40/bambu-printer-mcp/blob/main/docs/SLICING.md).
- Slice through BambuStudio CLI with automatic BBL inheritance/include resolution, per-slot filament colours, and fallback prime-tower placement for multi-nozzle printers. Missing dependencies stop the slice; custom settings and saved project tower positions are preserved. Multi-colour slicing is verified by the contributor on BambuStudio 02.08.02.60 for Windows; older CLI versions have separate limitations. See [slicing guide](https://github.com/DMontgomery40/bambu-printer-mcp/blob/main/docs/SLICING.md).
- Recognize X2D status and slice with its own installed BambuStudio preset (`BAMBU_MODEL=x2d`). On macOS and x86-64 Linux, `print_3mf` selects the native route after resolving the model, including an elicited model. Install the optional helper as described below; Windows native print requests, and Linux requests without the helper, fail before slicing or contacting the printer. Legacy FTPS and remote G-code starts remain unsupported for X2D.
- 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
- Print, heating and resume start without a prompt; clearing hardware errors and resuming a file that could not be re-read still ask a person; see [hardware safety](https://github.com/DMontgomery40/bambu-printer-mcp/blob/main/docs/SLICING.md).
- 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 an external CLI, or use the optional [`orca-slicer-api` backend](https://github.com/DMontgomery40/bambu-printer-mcp/blob/main/docs/SLICING.md) in a separate container without a local slicer executable. Remote slicing requires a matching read-only Orca profile tree and currently supports one filament slot and one selected plate. BambuStudio, FULU, and Orca require the exact machine preset and resolve BBL dependencies before slicing; see [FULU/Orca CLI setup and validation limits](https://github.com/DMontgomery40/bambu-printer-mcp/blob/main/docs/FULU.md#fulu-and-orca-cli-safety-limit).
- 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
</details>
<details>
<summary><strong>AMS (Automatic Material System) Setup</strong></summary>
## 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`:
```json
{
"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.
Mapping is positional: each entry corresponds to a project filament, and `-1` means unused. H2/P2S project-file commands use project-length mapping plus a parallel `ams_mapping2`; other project-file routes retain at least five positions without truncating longer projects. Prefer `ams_slots` in plate filament order or `auto_match_ams: true` when you do not already have the full project mapping.
### Single-material prints
For a single-material plate, explicitly select its loaded tray. For example, use AMS slot 2:
```json
{
"three_mf_path": "/path/to/model.3mf",
"ams_slots": [2]
}
```
This expands slot 2 into the correct project filament position. There is no universal fixed default mapping for every model and project.
### 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`:
```json
{
"three_mf_path": "/path/to/model.3mf",
"use_ams": false
}
```
For H2 projects with declared filaments, also provide the required mapping; `use_ams: false` alone does not remove the firmware's mapping requirement. See [`print_3mf`](#print_3mf).
Dual-nozzle printers (H2D, H2D Pro, H2C, X2D) have two external spool holders, and the printer reports one virtual slot per nozzle. Use Bambu Studio's ids in `ams_slots`/`ams_mapping`: `254` is the left/deputy nozzle's external spool and `255` the right/main nozzle's. The server composes the selected plate's `filament_map` with `physical_extruder_map` to verify each filament's physical nozzle (`0` = right, `1` = left), and requires the matching fresh `vir_slot` report. An explicit external-spool mapping is preserved with `use_ams: false`; a missing mapping is derived only from these inspected headers. Single-nozzle printers keep `254` as their only external tray.
### 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?"
```
</details>
<details>
<summary><strong>Bambu Communication Notes (MQTT and FTP)</strong></summary>
## 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. Authentication uses username `bblp` and your LAN access code; the serial number identifies the device topics. 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](https://github.com/Doridian/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
This package works around two protocol-level issues in the `bambu-js` library it previously used, and no longer depends on `bambu-js` at all: file transfers and listings use `basic-ftp` directly, and printer commands use `bambu-node`.
**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.
```typescript
// 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.
For the non-H2/P2S project-file route, this implementation retains at least five positions in the `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:
```typescript
// Non-H2/P2S project_file: at least five entries; preserve longer projects
ams_mapping = [0, -1, -1, -1, -1];
// H2S/H2D/H2C/P2S: 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 in the original LAN-only test. 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):
```bash
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`:
```js
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.
</details>
<details>
<summary><strong>Available Tools</strong></summary>
## Available Tools
<details>
<summary><strong>STL Manipulation Tools</strong></summary>
### 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.
```json
{
"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).
```json
{
"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:
```json
{
"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.
```json
{
"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.
```json
{
"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.
```json
{
"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.
```json
{
"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.
```json
{
"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.
</details>
<details>
<summary><strong>Printer Control Tools</strong></summary>
### 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](https://github.com/Doridian/OpenBambuAPI/blob/main/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.
#### counterpart_print_record
Use `list_jobs: true` to discover the retained job IDs before selecting an older `job_id`. Latest-job reads discover newer dispatches made by another MCP process. The process that authorized dispatch owns report writes while it is alive; after it exits, a restarted observer can take over from the saved record. Concurrent passive MCP sessions read the same disk evidence without overwriting the active recorder. Camera bytes appear once, in `structuredContent`, when `include_frames: true`.
Read the flight record of a job this server dispatched, without contacting the printer. Firmware `finished` means the printer finished its commands; it does not verify the physical object.
```json
{"job_id":"optional saved record UUID", "include_frames":false}
```
Omit `job_id` for the latest job on the configured printer; ordinary printer target overrides select another saved printer. The result has `contract: "counterpart/1"`, `status: "recorded" | "unavailable"`, an optional version-1 `record`, and separate `physical` evidence. Bambu records allowlisted native report fields; other adapters retain their existing normalized observations, without inventing fields their firmware/adapter does not expose. A record is established at the existing send-authorization boundary, so a lost transport/helper reply still leaves the exact hash/mapping with `dispatch.outcome: "unconfirmed"`. A successful transport reply marks it `accepted`; a matching active firmware report marks it `observed-active`. These outcomes do not verify physical success or authorize a retry. The record retains the inspected artifact SHA-256, selected plate/hash, filament mapping/materials, timestamped allowlisted state/error/stage/AMS/temperature changes, latest state, truncation count and camera metadata. Printer names, hosts, serials, credentials, arbitrary report text and URLs are excluded; the target and dispatched job identity are hashed. Raw unknown fields remain unknown; AMS `remain: -1` is not empty. After an active report has been seen, firmware terminal reports freeze the job record so later cooling does not evict its failure evidence. Returned task/subtask execution IDs are hashed. A changed execution ID or an observed ended-to-active reprint invalidates current-progress evidence durably (a bounded one-byte marker), including same-filename reprints; explicit archived reads keep the original evidence. History before this feature or outside retained/connected observations is unavailable, not reconstructed by contacting the printer.
Storage is private (directory 0700, files 0600), survives MCP restart, and is capped at ten jobs, 1 MiB/2048 report changes per job, a 64 KiB visual-observation sidecar, and 32 JPEGs of at most 512 KiB each. At most four images are returned per request. Older changes/jobs are removed; `droppedReports`, `droppedFrames` and `skippedCheckpoints` disclose gaps. `COUNTERPART_RECORD_DIR` is a trusted server environment override, never a tool-selected filesystem path. Storage failure warns on stderr and marks a record volatile; diagnostics never authorize printing, resume or another mutation.
While the client monitors `counterpart_print_progress`, existing reports trigger bounded asynchronous preparation-frame candidates (up to three stage changes), a first-layer frame, a completed-first-layer follow-up when layer 2 is observed, then a snapshot at crossed ten-layer milestones and height fractions (5%, 30%, 60%, 90%). The 32-frame ring preserves baseline/first-layer evidence and the latest recorded empty-footprint anomaly. There is no additional status polling or autonomous watcher. Without a monitoring client, known layer geometry, a matching active job and a usable existing camera source, no physical verdict is inferred. Skipped heights are not backfilled. Each frame binds its ID, actual report layer/time, expected sliced Z, capture receipt/acquisition time and image SHA-256. Image acquisition must be at or after its associated report boundary; an active stream waits for a newer frame when its cached frame predates that boundary. Old cached frames, a layer/state change during acquisition, and bounded camera failures are marked unavailable. A stable reported layer is not a hardware-synchronized shutter. `include_frames:true` reads saved JPEG base64 from disk; it does not open a camera. Expected geometry is the selected 3MF plate's or inspected standalone G-code snapshot's sliced layer Z and extrusion-path XY bounds, including swept extrema for G2/G3 XY arcs, not a calibrated camera silhouette or measured flow. Oversize/unsupported G-code or missing layer reports leave geometry/correspondence unknown.
`physical.status` is `unknown`, `consistent`, `suspect_no_growth` or `suspect_no_extrusion`, independently of firmware state. Without a usable client visual observation or calibrated height measurements it remains **unknown**, including after FINISH. When `physical.assessment` is present, the client worker should fetch its `frameId` and any useful `referenceFrameIds` with `counterpart_print_record` using `frame_format:"images"` and optional `frame_ids` (at most four). This returns normal MCP image content exactly once, indexed by `imageContentIndex` in frame metadata. Image format defaults to compact `view:"evidence"`; `omittedReports`/`omittedGeometryLayers` disclose omitted journal detail. `view:"record"` retains the full forensic record. The request includes the selected sliced footprint bounds, layer/height and explicit visibility/deposition criteria.
The client vision worker then calls **`counterpart_print_assess`** with:
```json
{"job_id":"saved job UUID","artifact_sha256":"saved dispatch hash","frame_id":"saved frame UUID","image_sha256":"saved JPEG hash","visibility":"clear","footprint_visible":true,"deposition":"absent","attempted":true}
```
The hashes/IDs must match the saved job and image. `visibility` is `clear|dark|occluded|out_of_view|unknown`; `deposition` is `absent|present|unknown`. Set `attempted` only when the frame follows attempted object deposition, and `footprint_visible` only when sufficient expected deposition area can be judged. Optional `reference_frame_id`, `reference_visibility` and `reference_empty` provide empty-bed context. **A clearly visible empty footprint after attempted printing is sufficient for `suspect_no_extrusion`; no reference is required.** A lowered, dark or occluded reference is not proof and does not suppress a clear direct observation. Dark/occluded/unlocatable current evidence stays unknown. Merely visible material does not verify height, coverage or final success.
This tool writes only bounded local visual observations (idempotently) and has no printer/camera I/O, model call or control. Observations live separately from the single-writer telemetry journal, so a worker process cannot overwrite live reports. Their result survives MCP restart and firmware FINISH. A `suspect_no_extrusion` warning stays active for the job through later unknown/dark/occluded observations and FINISH; only a later clear `present` observation with a visible footprint at a higher layer may supersede it (without proving final success). It is the first structured progress problem, with the observed layer (`physical.observedLayer`) and separate wording after firmware completion. A first-layer `unknown` observation still permits a new assessment at the crossed ten-layer checkpoint (10, 20, …), subject to fresh reports, available geometry and camera visibility. Image interpretation is the **client worker's** responsibility: the MCP does not contain an image classifier or pretend pixel change proves extrusion. Clients must consume pending assessments and should never announce a first layer is down from a counter alone. Installing these backend tools alone does not add that worker loop to an older client.
A worker may also fetch the images/geometry and pass up to four calibrated height measurements to this read tool:
```json
{"measurements":[
{"frameId":"saved frame UUID", "visible":true, "observedHeightMm":24, "uncertaintyMm":0.5, "method":"calibrated-height"},
{"frameId":"another saved frame UUID", "visible":true, "observedHeightMm":24, "uncertaintyMm":0.5, "method":"calibrated-height"}
]}
```
The MCP validates the saved frame identities/times and evaluates these **client-supplied** measurements; it runs no vision model. Two visible, sufficiently separated expected heights can support consistency, or an unchanged short observed height can support `suspect_no_growth`. Occluded, out-of-view, uncalibrated, uncertain, stale or contradictory evidence stays unknown. `consistent` concerns height in those frames only; it does not verify final shape, strength or print success. The assessment returns frame IDs and explicit limitations and is computed without altering the record. There is no automatic stop, pause, resume, model call or printer-setting change.
Sensor limits: Bambu Studio v02.08.02.61 declares [P1S lidar calibration unavailable](https://github.com/bambulab/BambuStudio/blob/v02.08.02.61/resources/printers/C12.json#L37), [X1 Carbon lidar calibration available](https://github.com/bambulab/BambuStudio/blob/v02.08.02.61/resources/printers/BL-P001.json#L42), and [H2D calibration/wrapping capabilities](https://github.com/bambulab/BambuStudio/blob/v02.08.02.61/resources/printers/O1D.json#L51). These are capability/configuration facts, not deposited-height measurements. Studio parses [xcam detection options and live support bits](https://github.com/bambulab/BambuStudio/blob/v02.08.02.61/src/slic3r/GUI/DeviceCore/DevPrintOptions.cpp#L37) and [AMS air-print capability with firmware exceptions](https://github.com/bambulab/BambuStudio/blob/v02.08.02.61/src/slic3r/GUI/DeviceManager.cpp#L1112). This feature records reported evidence; it never enables a detector or assumes a capability from a model name.
Validation is offline replay and mocked transports only. No real-printer physical-success claim is made.
#### 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](docs/CAMERA-STREAM.md) 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.
#### Print options
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.
#### print_3mf
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](https://github.com/DMontgomery40/bambu-printer-mcp/blob/main/docs/SLICING.md). 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](https://github.com/DMontgomery40/bambu-printer-mcp/blob/main/docs/FULU.md). 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](https://github.com/DMontgomery40/bambu-printer-mcp/blob/main/docs/FULU.md#probe-without-printing) 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)](#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.
X1C can opt in to the same route with `connection_mode: "bambu_native"` on `print_3mf` or `upload_file`; its default stays `lan_mqtt_ftps` and nothing is redirected. Use it when the printer refuses direct FTPS uploads: an X1C on firmware 01.12.00.00 (LAN Only and Developer Mode on, a ready writable SD card) answered every FTPS `STOR` with `553 Could not create file.` while Bambu Studio printed normally, and an X1C `print_3mf` or `upload_file` that fails with 553 now names this route. The same inspection, AMS mapping and fresh printer-state checks apply. Native X1C printing requires a freshly reported ready SD card or USB stick unless fresh status advertises internal-printing support (`fun2` bit 0, as Bambu Studio reads it). Explicit or environment eMMC requests without that capability are refused, and storage readiness/capability is rechecked before dispatch. That X1C completed a native print on macOS (AMS slot, SD card storage); Linux and other X1C firmware are untested, and X1E is not enabled. Native controls (`x2d_native_control`, native heating and error clearing) remain X2D-only: an X1C job is stopped, paused and resumed over direct MQTT, even when the request names `bambu_native`.
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.
#### print_3mf_bambu_network
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](https://github.com/DMontgomery40/bambu-printer-mcp/blob/main/docs/FULU.md#print-through-the-bridge). 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). To draw each plate from the open 3MF standard alone, `build_items` lists the root `3D/3dmodel.model` build items on it (0-based, in build order), and `parts` lists each model part of its objects once: `object_id` (the mesh object), `path` when that mesh is in another model part (`p:path`, e.g. `/3D/Objects/object_1.model`), `build_object_id` (the root object a build item places), `name`, `filament` (1-based, the project filament it prints in) and `painted: true` when slicer painting recolours some of its facets. Negative parts, modifiers and support blockers are not listed. 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
}
```
#### print_collar_charm
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](https://github.com/DMontgomery40/scan-to-make) (Scan to Make) reads printers through a small contract that this server and [mcp-3D-printer-server](https://github.com/DMontgomery40/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, displayName, serial, host}, capabilities}`. `displayName` is the model with its maker ("Bambu Lab P1S"), to show as given; it and `model` are absent when the model is unknown. `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_self_check`** returns `{contract, server: {name, version}, ok, checks: [{name, ok, version?, message?}]}`: one check that the running server is its installed package's version, one per runtime dependency in `package.json` (it resolves from this install and loads, and the installed version meets an exact pin, `^` or `~` range), and `bambu-node patch`: every hunk of the bundled patch is in the installed bambu-node (a read-only reverse dry run, as postinstall checks), and it reads an H2D version report in memory as only the patched module can. Run it after an update; `ok` is false when any check fails. It sends nothing to the printer.
- **`package.json` `counterpart`**: `{contract: "counterpart/1", notes, autoUpdate}`, readable with `npm view bambu-printer-mcp counterpart` before a client downloads a version. `notes` is one sentence about that release; `autoUpdate: false` asks clients not to install it automatically.
- **`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](docs/FULU.md)). 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, cameraSnapshot, fields}], model?}`. `cameraSnapshot: true` says `camera_snapshot` reads the printer's camera with only the settings the fields write, so a client's setup check can ask for a picture as well as the status (RTSP models still need ffmpeg, as `camera_snapshot` always does); every kind declares it. 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.
</details>
<details>
<summary><strong>Slicing Tools</strong></summary>
### Slicing Tools
> **Note:** the verified workflow is to slice in Bambu Studio (GUI) and feed the resulting `.gcode.3mf` to `print_3mf`. The CLI-driven slicing tools below (`slice_stl`, `slice_with_template`) work but are sensitive to profile drift and are not the recommended path for production prints. See [slicing guide](https://github.com/DMontgomery40/bambu-printer-mcp/blob/main/docs/SLICING.md).
#### list_templates
List saved templates from the local registry directory. You can override the registry root with `BAMBU_TEMPLATE_DIR`.
```json
{}
```
Each result includes the template `name`, absolute `path`, source type, and relative path inside the registry. You can then pass `template_name` to `get_slice_settings`, `slice_stl`, or `print_3mf` instead of a raw path.
#### save_template
Copy a local `3mf`, `json`, or `.config` file into the template registry and register it under a reusable template name.
```json
{
"source_path": "/path/to/sliced_project.3mf",
"template_name": "collars/p1p_petg_default"
}
```
This creates the destination under the template registry directory and makes it available immediately to `list_templates`, `get_slice_settings`, `slice_with_template`, `slice_stl`, and `print_3mf`.
#### get_slice_settings
Inspect the slicer settings embedded in a saved 3MF template or in an extracted JSON/config profile without slicing anything.
```json
{
"template_name": "h2s_template"
}
```
This returns a compact summary of the high-signal settings such as printer preset, default print profile, filament profiles, layer height, infill density, shell counts, support mode, and bed type. It accepts either `source_path` or `template_name`. For 3MF inputs it also writes the extracted settings blob to a temp path so the result can be reused directly, and returns `own_process`. With `source_path`, pass the slice's other arguments too (`slicer_profile`, `template_name`, `template_3mf_path`, `layer_height`, `wall_loops`, `sparse_infill_density`, `quality`, `strength`). `own_process` is then true when that `slice_stl` call prints the process the 3MF carries (a designer's project) unchanged. A 3MF with no slicer settings returns `raw_key_count` 0 and no extracted path; a file that can't be read is an error.
#### slice_with_template
Slice an STL or 3MF using a named template from the local registry. This is a higher-level wrapper over `slice_stl` for template-based workflows.
```json
{
"stl_path": "/path/to/model.stl",
"template_name": "collars/p1p_petg_default",
"bambu_model": "p1p"
}
```
This uses the named template as the slicing profile source and still supports live printer filament selection unless you explicitly override `load_filaments`. The template settings are applied at slice time, and the later `print_3mf` step computes H2-safe AMS mapping from the newly sliced output.
#### slice_stl
Slice an STL or 3MF file using an external slicer and return the path to the output file. The output is a sliced 3MF (for BambuStudio, OrcaSlicer, and FULU OrcaSlicer-bambulab) or a G-code file (for PrusaSlicer, Cura, Slic3r).
```json
{
"stl_path": "/path/to/model.stl",
"slicer_type": "bambustudio",
"bambu_model": "p1s"
}
```
`slicer_type` options: `bambustudio`, `orcaslicer`, `orcaslicer-bambulab` (FULU), `prusaslicer`, `cura`, `slic3r`. Aliases include `fulu-orca` and `orca-studio`. When omitted, the value from the `SLICER_TYPE` environment variable is used (default: `bambustudio`).
**FULU/Orca CLI safety:** from 1.1.11, these backends require the exact model/nozzle preset from the selected installation and stop on profile preparation failures. A process override cannot bypass that gate. See [configuration and validation limits](https://github.com/DMontgomery40/bambu-printer-mcp/blob/main/docs/FULU.md#fulu-and-orca-cli-safety-limit).
**OrcaSlicer CLI transforms:** OrcaSlicer 2.4.2's CLI segfaults when `scale` or `rotate_x`/`rotate_y` moves an object below the bed while arranging, which is the default for STL input. OrcaSlicer fixed this after 2.4.2 in [#14415](https://github.com/OrcaSlicer/OrcaSlicer/pull/14415), first released in 2.5.0-alpha. For STL input with `orcaslicer` or `orcaslicer-bambulab`, the server applies `scale` and the rotations to a copy of the mesh in the CLI's order and leaves those flags out. For 3MF input it refuses `scale` above 1, `rotate_x` or `rotate_y` together with `arrange: true`. `repetitions` copies an STL with Orca's `--clone-objects`. `clone_objects` takes one total count, because the CLI counts copies per loaded file. With `arrange: false`, an STL that is not already on the plate fails to slice. A failure names the slicer's own reason.
`slicer_path` and `slicer_profile` fall back to the `SLICER_PATH` and `SLICER_PROFILE` environment variables when omitted. Per-call `slicer_path` overrides require `MCP_ALLOW_EXECUTABLE_ARG=1`.
**A slicer that waits on a window:** on macOS each Bambu Studio, OrcaSlicer or FULU slice runs with an empty home folder of its own (`CFFIXED_USER_HOME` under `TEMP_DIR`), so macOS's "reopen windows?" question after a slicer crash can't stop it. On macOS, a Bambu-compatible slicer that still uses no CPU and writes nothing for `SLICER_STALL_SECONDS` (default 20, 0 turns it off) is stopped, and the error's `structuredContent.code` is `slicer-waiting-on-dialog`. Nothing is sent to the printer. Open the slicer once, answer what it shows, quit it and slice again.
You can provide either `template_3mf_path` or `template_name` when you want to slice from a saved template. `template_name` resolves through the local template registry directory configured for the server.
For printing on a Bambu printer, the recommended workflow is: export a sliced 3MF from your selected Bambu-compatible slicer, then pass that output path to `print_3mf`.
#### BambuStudio Slicer Options
When `slicer_type` is `bambustudio` (the default), these additional parameters are available on `slice_stl`:
| Parameter | Type | Description |
|-----------|------|-------------|
| `uptodate` | boolean | Update 3MF configs to latest BambuStudio presets |
| `repetitions` | number | Total STL copies, using the CLI's `--clone-objects`; do not also pass `clone_objects`. Copies may span plates, and `print_3mf` prints one selected plate per call. For 3MF repetitions, choose `slice_plate` explicitly. |
| `orient` | boolean | Auto-orient model for optimal printability |
| `arrange` | boolean | Auto-arrange objects on the build plate |
| `ensure_on_bed` | boolean | Lift floating models onto the bed |
| `clone_objects` | string | Clone counts per object, comma-separated (e.g. `"1,3,1,10"`) |
| `skip_objects` | string | `identify_id`s of the object copies to skip, comma-separated (e.g. `"78,92"`); `list_3mf_plates` gives them per plate as `instances` |
| `load_filaments` | string | Filament profile paths, semicolon-separated |
| `load_filament_ids` | string | Filament-to-object mapping, comma-separated. STL input only: Bambu Studio's CLI refuses it for a 3MF, where each object or part names its own filament (see `counterpart_make_3mf`) |
| `nozzle_volume_type` | string | Installed nozzle flow type: `standard` or `high_flow` (default: `BAMBU_NOZZLE_VOLUME_TYPE`, else the preset's Standard default). `high_flow` uses the presets' High Flow values, such as the filament's High Flow max volumetric speed, and is refused if the machine preset declares no High Flow variant. Also on `slice_with_template`. |
| `filament_colours` | string | Slot colours, one `#RRGGBB` per filament slot, semicolon-separated. Explicit values take priority, followed by input 3MF colours, each custom profile's colour, then the BambuStudio default. |
| `enable_timelapse` | boolean | Enable timelapse-aware slicing |
| `allow_mix_temp` | boolean | Allow mixed-temperature filaments on one plate |
| `scale` | number | Uniform scale factor |
| `rotate` | number | Z-axis rotation in degrees |
| `rotate_x` | number | X-axis rotation in degrees |
| `rotate_y` | number | Y-axis rotation in degrees |
| `min_save` | boolean | Produce smaller output 3MF (faster uploads) |
| `skip_modified_gcodes` | boolean | Ignore stale custom gcodes in the 3MF |
| `slice_plate` | number | Which plate to slice (0 = all plates, default: 0) |
| `quality` | string | `draft`, `standard` or `fine`: the server picks the layer height for the nozzle in use (0.28 and 0.12 mm on 0.4 mm; standard keeps the preset) |
| `strength` | string | `standard`, `strong` or `max`: the server picks walls and infill (at least 4 walls and 25%, or 6 and 40%; a process that already has more keeps it; standard keeps the process as it is) |
| `layer_height` | number | Layer height in mm, such as 0.28, 0.2 or 0.12; at most the nozzle diameter |
| `wall_loops` | integer | Number of walls, 0 to 1000 |
| `sparse_infill_density` | string | Sparse infill density, such as `"15%"` |
| `enable_support` | boolean | Turn support generation on or off |
| `support_type` | string | `normal(auto)`, `tree(auto)`, `normal(manual)` or `tree(manual)` |
| `support_threshold_angle` | integer | Overhang threshold for automatic supports, 0 to 90 degrees (0 lets the slicer decide) |
`quality` and `strength` let a client send the person's choice without slicer numbers; an explicit `layer_height`, `wall_loops` or `sparse_infill_density` wins over them. `layer_height`, `wall_loops`, `sparse_infill_density` and the three support arguments are written as a process override on top of the machine and process preset the tool resolves, using Bambu Studio's keys and string forms (`"0.2"`, `"2"`, `"15%"`). Every other preset value, and the machine preset, stay as resolved, and your own profile files are not changed. They work with every Bambu-compatible slicer, including remote `orca-slicer-api` slicing, and are refused for PrusaSlicer, Slic3r and Cura.
**Example: Slice with auto-orient and 3 copies**
```json
{
"stl_path": "/path/to/model.stl",
"bambu_model": "p1s",
"orient": true,
"arrange": true,
"repetitions": 3
}
```
#### Smart Defaults (print_3mf auto-slice)
When `print_3mf` detects an unsliced 3MF and auto-slices it, these defaults are applied automatically:
- `uptodate: true` -- prevents stale config bugs from downloaded 3MFs
- `ensure_on_bed: true` -- safety net, lifts floating models onto the bed
- `min_save: true` -- smaller output for faster FTP uploads to the printer
- `skip_modified_gcodes: true` -- strips custom gcodes from other users' profiles
These defaults reduce stale-profile problems; inspect downloaded models and their slice preview before printing. When calling `slice_stl` directly, you have full control over every flag.
</details>
<details>
<summary><strong>Optional code-CAD engine</strong></summary>
### Code-CAD
This MCP includes an optional headless design engine built on **build123d**, with trimesh and Manifold for mesh checks. It supports dimensioned parts and script-based captured-mesh work, saving editable Python, named dimensions, STL, and STEP for solid designs without starting Blender or Fusion. Nothing is downloaded until you explicitly call `cad_setup` with `install: true`. Allow about **1.5 GiB** for its isolated Python, packages and download cache; the exact size varies by platform. It needs `uv`, plus macOS Seatbelt or Linux `bubblewrap`; it refuses to run without confinement.
Why include it: a local comparison made a bottle-holder cavity at 65.800 × 28.800 mm, changed its height by exactly 1 mm with one parameter edit in 137 seconds, and exported an articulated unicorn in 657 seconds where two recorded Blender attempts reached 901 seconds without a model. Initial holder worker passes took 238–893 seconds; this is not a speed guarantee. The unicorn still had dimension and pose errors. These few runs do not establish general superiority, reliable articulation or physical print fit. Both holders collided with an oversized raw scan; numerical checks exposed that rather than silently resizing it.
**Don't want it?** Set `CAD_ENABLED=0` in the MCP's environment and restart the MCP. Its CAD tools, prompt and advertised capability disappear; printer tools and the existing Blender/Fusion bridges stay available. To remove a downloaded environment, stop the MCP and move its CAD data folder to Trash (default locations and `CAD_DATA_DIR` are in [the CAD guide](docs/CAD.md)). Your saved model outputs are separate. Leaving it disabled installs nothing and starts no Python.
#### cad_status
Read the pinned version, Python path, platform and last verified readiness. No installation or Python process is started.
#### cad_setup
Check only by default. `{"install":true}` installs a pinned isolated environment in the MCP's data directory and proves a confined small build plus STL/STEP round trip. On macOS it requires an internal disk with file ownership enabled. USB/network data volumes are refused before installation because the experiment found native-import stalls there; nothing is re-signed.
#### cad_run
Read the `cad_design` prompt first. Supply `script` or a saved `script_path`, `output_dir`, and `name`. Scripts declare literal `P = {...}` parameters and a `build()` function returning a build123d shape. One later call with `script_path` and `parameters` changes a dimension and rebuilds. Each run creates a new subfolder and preserves its source/parameter checkpoint, even on failure. The result contains STL/STEP paths, measured geometry, validity checks, six orthographic views, a perspective view and a contact sheet with the perspective view large beside the six; `next` says to read the perspective view before the next change, and `printed` carries whatever the script wrote with `print()` (up to 4000 characters), so a design can report a number it computed.
Explicit `inputs: [{"name": "scan", "path": "/allowed/scan.stl"}]` snapshots the files a script may read as `INPUTS["scan"]`. For a mesh-only edit, use `mode: "mesh"` and return `trimesh.Trimesh`: it exports and validates STL plus renders, with `step: null` and a clear reason. For captured meshes, the injected `load_scan` helper takes explicit source units and an up-axis, converts coordinates to millimetres/Z-up, and grounds the mesh without fitting its size. Cleanup choices and labels belong to the caller’s rules; the MCP supplies execution, mesh libraries, checks and renders.
For a multi-colour result, `build()` returns `project_3mf()`: a new project of named parts with filaments and plates, or (with `project_3mf("kit")` on a declared input 3MF, in `mode: "mesh"`) an edit that adds, replaces, renames or recolours parts and keeps the designer's print settings, painting, plates and other files byte for byte. The run writes `<name>.3mf` with the same writer as `counterpart_make_3mf` and returns `three_mf` and `three_mf_project` (each plate's parts and filaments, read back from the file). See [docs/CAD.md](docs/CAD.md#multi-colour-3mf-projects).
`cad_status` and `cad_setup` expose `storage.expected_download_bytes`, `expected_installed_bytes` and cached measured `installed_bytes`; the first two are fresh-install budget estimates, not remaining transfer sizes. Setup failures carry `structuredContent.code`, `message`, `suggestion` and `retryable`, so clients do not parse wording.
Runs wait for the environment's execution slot up to their own total timeout and return `queue_wait_ms`. Mark the selected result with `final: true`: each run's script-protected `cad-result.json` keeps the flag, full validation and artifact paths for recovery after restart. Check `status`, `valid` and the checks as well as `final`.
Unchanged declared inputs reuse immutable content-hash bundles, with `snapshot_bytes_added` and `snapshot_bytes_reused` in the result. Keep the referenced `.cad-inputs` bundles with saved designs. Previews now use depth cues and feature edges to reveal windows and recesses without a heavy renderer.
#### cad_examples
The code engine's cookbook: nineteen worked build123d designs from simple to complex (cable clip, wall hook, drawer divider, organiser tray, plant pot, knurled knob, phone stand, swept headphone hook, lofted tool handle, shelf bracket, broom grip, desk-edge cable holder, threaded jar, desk-clamp headphone hook, bag clip, hinged box, bistable clicker, chainmail, trigger capo), each modelled on the best bought version of the thing, with what it is for, its concept, the published sizes it used and its script. Without arguments it lists them by purpose (`use_for`) with the pattern each shows; with `name` it returns one record and script. The `cad_design` prompt names every example by its purpose in one line, so a design worker chooses the one made for the nearest request before it chooses a mechanism. Read-only; needs no CAD runtime. The scripts are in `src/cad/examples/`.
#### cad_check_fit
Check a model and reference STL with an explicit rigid transform. Returns interference volume and minimum surface gap, without quietly scaling a scan. Open/nonmanifold meshes are refused. A gap of zero includes intentional floor contact, so choose clearance checks to match the interface you actually want to measure. Like `cad_run`, a check waits for the engine's execution slot up to its `timeout_ms` and returns `queue_wait_ms`, so several can be sent at once.
See [CAD setup, confinement, script contract and removal](docs/CAD.md) for configuration, examples and limits.
</details>
<details>
<summary><strong>Advanced Tools</strong></summary>
### Advanced Tools
#### Blender MCP
Use a standard stdio Blender MCP server with `BLENDER_MCP_COMMAND` and
`BLENDER_MCP_ARGS`. For the common Blender MCP server, install `uv` and its
matching Blender addon, enable the addon, and start its connection inside
Blender. Set the command to your full `uvx` path and the argument array to
`["blender-mcp"]`. The Claude Desktop extension also offers these two optional
settings. Printer tools work without Blender configured.
`blender_mcp_setup` checks the MCP for Blender add-on that the configured
package needs, when the bridge is that package run with `uvx`: which add-on file
Blender loads (for the running Blender's version), its protocol and the one the
package expects, whether Blender is listening, and the running add-on's own
`get_addon_status`. It changes nothing. With `{"update": true}` it stages the
add-on with the package's own installer (`mcp-for-blender install-addon`) at the
inspected version, checks that the installed and staged files are both regular
MCP for Blender add-ons and that the staged one carries the expected protocol,
keeps the old file as a backup next to it, and renames the new one into place.
It never starts, stops or edits Blender: re-enable the add-on or restart Blender
to load it. Another bridge reports `supported: false` and keeps its own installer.
1. Call `blender_mcp_status` with `{"connect": true}` to initialize the server
and discover its tools and input schemas. A connected MCP server does not
by itself prove that the Blender addon is running.
2. Call `blender_mcp_call` with a discovered tool name and its arguments. The
full MCP result, including images and errors, is returned. For example:
```json
{
"tool_name": "get_scene_info",
"arguments": {"user_prompt": "Inspect the scene before preparing a print."}
}
```
For advanced Blender operations, forward `execute_blender_code` with `code`
and the user's original `user_prompt`. These calls may edit the active scene.
Use the discovered schema rather than assuming a tool exists.
#### blender_mcp_edit_model
The standard MCP edit helper imports an STL, applies ordered edits, and exports
an STL without replacing the input or an existing output. Supported operations
are `decimate:<ratio>` (greater than 0 through 1), `remesh:<voxel size>` (positive,
in STL coordinate units), and `boolean_union:<STL path>`. Other operations can
use `blender_mcp_call`. Both processes must have access to the same file paths.
When `BLENDER_MCP_COMMAND` selects standard MCP, the advertised tool schema
requires an explicit `output_path` for both preview and execution. Legacy-only
bridge configurations keep `output_path` optional for compatibility.
```json
{
"stl_path": "/path/to/model.stl",
"output_path": "/path/to/model-edited.stl",
"operations": ["decimate:0.5"],
"user_prompt": "Reduce the triangle count of this model for printing.",
"execute": false
}
```
The default preview returns the plan and generated Python without launching
Blender. Set `execute` to `true` to run it. A successful standard edit returns
`output_verified: true`, `output_path`, byte count, and triangle count after
checking the matching export receipt and a valid finite mesh. The helper
preserves existing scene objects and requires Object Mode. Inspect the result
before slicing; a valid STL is not a guarantee of printability.
The bridge enforces request deadlines, closes child connections, and never
replays interrupted editing requests. After a timeout, inspect Blender before
trying the edit again because execution may already have started.
Existing custom executables still work through `BLENDER_MCP_BRIDGE_COMMAND`.
They receive `MCP_BLENDER_PAYLOAD` with the requested file and operations;
their results explicitly report `output_verified: false`. A missing executable
configuration is an error when execution is requested. Per-call legacy
`bridge_command` overrides remain disabled unless `MCP_ALLOW_EXECUTABLE_ARG=1`.
#### Fusion MCP
Design a part from scratch in Autodesk Fusion through
[faust-machines/fusion360-mcp-server](https://github.com/faust-machines/fusion360-mcp-server)
(MIT). It is a stdio MCP server that talks TCP on `localhost:9877` here to its
`Fusion360MCP` add-in inside Fusion. Fusion and its licence, including Autodesk's
free personal-use licence, are yours: this server never installs or requires
Fusion, and printer tools work without it.
The bridge and its setup pin [DMontgomery40/fusion360-mcp-server](https://github.com/DMontgomery40/fusion360-mcp-server) at commit
`86a3016a5a5b`: faust-machines' server and add-in with document binding, which
`fusion_mcp_new_design`'s `bind_document` needs, while that change is reviewed
upstream.
On macOS, `fusion_mcp_setup` sets it up. Without arguments it reports, changing
nothing: whether Fusion is installed, whether the add-in in
`~/Library/Application Support/Autodesk/Autodesk Fusion 360/API/AddIns/Fusion360MCP`
is at the pin and runs on startup, whether the login item that gives Fusion
`FUSION_MCP_PORT` is installed, and whether this server has its Fusion settings.
With `{"install": true}` it installs what is missing:
- the add-in from the pinned commit's archive, after checking every file against
its SHA-256 (a symlink, an extra file or a changed byte installs nothing), set
to **Run on Startup**. An add-in already at the pin is left in place;
- a login LaunchAgent, `com.scantomake.mac.fusion-port`, that runs
`launchctl setenv FUSION_MCP_PORT 9877` at login, because Fusion opened from the
Dock never reads this server's env file;
- the settings this server is missing, added to its own `.env` after a copy of
the file. A configured `FUSION_MCP_COMMAND` is kept with its arguments, so a
reviewed local runtime isn't replaced. The running server uses them at once.
It never starts or quits Fusion; `fusion_mcp_status` checks the connection. On
other systems, or by hand: copy the repository's `addon/` folder at that commit
to the AddIns folder above, set it to **Run on Startup**, and add these settings
next to the Blender ones in your env file:
```bash
FUSION_MCP_COMMAND=uvx
FUSION_MCP_ARGS='["--from","git+https://github.com/DMontgomery40/fusion360-mcp-server@86a3016a5a5b","fusion360-mcp-server","--mode","socket"]'
FUSION_MCP_TIMEOUT_MS=180000
FUSION_MCP_HOST=localhost
# The default here; Blender's MCP add-on holds the add-in's own default, 9876.
FUSION_MCP_PORT=9877
```
Don't use the PyPI release (0.1.0): it crashes under mcp 2.x (faust issue #30),
and the pinned commit pins `mcp<2`. Without `FUSION_MCP_ARGS`, the bridge uses
the pinned arguments above. `FUSION_MCP_HOST` and `FUSION_MCP_PORT` reach the
server; the child process gets only `FUSION_*` settings, never printer access
codes.
The bridge uses port 9877 when `FUSION_MCP_PORT` is unset, as PRD §8.9 and
Counterpart do. The add-in and the faust server default to 9876, which Blender's
MCP add-on also uses, and while Blender holds that port the add-in can't listen
there. The bridge passes the port to the faust server. The add-in reads
`FUSION_MCP_PORT` from Fusion's environment, and Fusion started from the Dock or
Finder gets none, so it falls back to 9876. `fusion_mcp_status` with
`{"launch": true}` starts Fusion with `open -a "Autodesk Fusion" --env
FUSION_MCP_PORT=<port>`. To start Fusion yourself, run the same command. If Fusion
is already open on the default port, quit it first, because `--env` takes effect
only when the app starts.
1. `fusion_mcp_status` returns `{configured, connected, addinReachable, fusionRunning, tools[]}`,
plus `launched`, `pin`, the server's `server` info and any `error`.
`addinReachable` is true only when the `Fusion360MCP` add-in answers its own
`ping` on `FUSION_MCP_HOST:FUSION_MCP_PORT`; Blender's MCP add-on, which also
defaults to port 9876, doesn't count, so it can't hide a stopped Fusion.
`connected` is true only when the add-in answers the server's `ping`. With
`{"launch": true}`, it starts Fusion on macOS (`open -a "Autodesk Fusion" --env FUSION_MCP_PORT=<port>`)
when it is not running and waits up to 90 seconds for the add-in.
2. `fusion_mcp_call` takes `{tool_name, arguments?, timeout_ms?}` and passes the
call to a discovered Fusion tool (`create_sketch`, `extrude`, `fillet`,
`create_parameter`, `set_parameter`, `check_interference`, `execute_code` and
the rest), returning its full content and errors. It is never retried, the
same contract as `blender_mcp_call`.
3. `fusion_mcp_new_design` opens a new parametric design document through
`execute_code` with `app.documents.add(FusionDesignDocumentType)`. The Fusion
tools work on the open design, so a design pass calls this first. At the
pinned commit the add-in refuses every command, `execute_code` included,
until some design is open (faust issue #12). After Fusion starts at its Home
tab, open any design once.
4. `fusion_mcp_export` takes `{output_dir, name, body_name?}`. It writes
`<name>.stl` (`export_stl` of one visible solid body), `<name>.f3d`
(`export_f3d`) and `<name>.parameters.json` (the user parameters, with the
fields `get_parameters` reports) into an existing directory, and returns their
absolute paths. Hidden and surface bodies are never exported. Without
`body_name` the design must have exactly one visible solid body. Fusion's
`export_stl` takes the first body with a name, hidden or not, so a name that
another body shares is refused. That includes hidden bodies, surface bodies
and copies of a component. It writes all three files
or none, never overwrites a file, and checks the STL is a finite triangle mesh.
The result also has:
- `boundingBox`: the STL's `{min, max, dimensions}` in its own units;
- `fusionSizeMm`: the body's size in Fusion, in mm;
- `stlUnits`: `mm`, `cm` or `unknown`, from comparing the two;
- `warnings`: present when the STL isn't in millimetres.
Fusion writes the files itself, so it must run on this computer. Slice the
STL like any other model.
The stdio session stays open for the life of this server, so a design pass does
not start a new `uvx` process and add-in connection for every call. A timeout,
cancellation or transport failure closes it, and the next call starts a new one.
An interrupted call may still be running in Fusion and is never replayed. At the
pinned commit `measure_distance` is broken (faust issue #27); measure the
exported STL with `get_stl_info` instead.
</details>
</details>
<details>
<summary><strong>Available Resources</strong></summary>
## Available Resources
Resources follow the MCP resource protocol and can be read by calling `ReadResource` with a URI. The server also lists them via `ListResources`.
### Printer resources
- `printer://{host}/status` -- Current printer status. Equivalent to calling `get_printer_status`. Returns a JSON object with temperature, progress, layer, AMS, and raw state data.
- `printer://{host}/files` -- File listing for the printer's SD card. Equivalent to calling `list_printer_files`. Returns files grouped by directory.
- `printer://{host}/hms` -- HMS and error diagnostics from the latest status payload. Returns connection state, printer status, explicit HMS payloads when present, and shallow raw fields whose names indicate errors, failures, warnings, or HMS data.
**Example:** To read the status of the default printer, use URI `printer://192.168.1.100/status`. The host segment must match a configured printer IP; the server uses `PRINTER_HOST` if the default URI template is used.
### Design guidance
The server carries manufacturing judgment for agents that design or edit a model to print. It is served as MCP prompts, so the client attaches each piece when it applies; the server's `instructions` tell any client they exist.
- `fdm_design` -- for every design or model-editing task. Plan how the object prints as part of the design: geometry, orientation, supports, colours and assembly, using the actual printer, filaments and slice. It keeps what the person asked for and judges overhangs, bridges and supports locally, with no fixed 45-degree or bridge-length ban. It also carries the everyday judgement for sizes (look published dimensions up, keep size, print correction and fit apart), for parts that hold something (compliant first, bending in the layer plane, strain by duty), for printed fasteners, and for stating support, fit and hold claims only with the check or slice number that shows them.
- `fdm_fit` -- whenever a part meets a real object or another part: choose each fit by what that interface has to do (drop in and lift out, slide, locate, grip, clamp, snap, press in), why filament prints fit the way they do, mechanisms that absorb size uncertainty, cited starting clearances, and what to report per interface.
- `fdm_flexures` -- whenever a part must flex, grip, snap, clip, spring, or hold something by bending: compliant architectures, strain chosen by duty, material and layer direction, bite at the forced opening, heat and creep.
- `fdm_multicolor` -- when several colours or support materials share a print: filament transitions across layers, colour placement, plate scheduling and flushing.
- `fdm_assembly` -- when a split, accent insert, joint or printed fastener could materially improve the result.
- `fdm_after_slice` -- a separate review turn after `slice_stl`, before presenting the manufacturing recommendation: keep, revise, or validate one specific uncertainty, using the returned `slicer_result`.
- `fdm_slicing` -- for every design or model-editing task, next to `fdm_design`: how this server's configured slicer (`SLICER_TYPE`) orients a model and generates supports, and how to check a choice with a checkpoint `slice_stl`. Its text follows the slicer: Bambu Studio's auto-orient and `enable_support`, or plainly that a slicer here doesn't orient or take support settings.
- `guidance://fdm/capability-guidance` (resource) -- planning bands and sources for bridges and overhangs, for when one is consequential and there is no sufficiently specific measured capability for the printer, material and profile.
- `guidance://fdm/fit-by-function` (resource) -- the same text as the `fdm_fit` prompt, in resource form for clients that read resources.
- `design_guidance` (tool) -- the same on-demand texts by name (`fdm_fit`, `fdm_flexures`, `fdm_assembly`, `fdm_multicolor`, `fdm_after_slice`, `fdm_capability`), for a design worker that is given tools but not prompts or resources. `fdm_design` and `cad_design` tell such a worker to read `fdm_fit` and `fdm_flexures` this way when they apply. Read-only.
mcp-3D-printer-server serves the same guidance.
</details>
<details>
<summary><strong>Bambu Lab Printer Limitations</strong></summary>
## Bambu Lab Printer Limitations
Understanding these constraints will help you avoid frustrating errors and set appropriate expectations.
1. **Printable 3MF required for print_3mf.** The `print_3mf` tool expects a sliced 3MF containing at least one `Metadata/plate_<n>.gcode` entry. If you pass an unsliced 3MF (one exported from a CAD tool without slicing), the server attempts auto-slicing. BambuStudio, FULU, and Orca CLI paths require the matching machine preset and complete profile preparation; any inspection or slicing failure stops before upload. For a previewable workflow, pre-slice in FULU OrcaSlicer-bambulab, OrcaSlicer, or Bambu Studio and pass the resulting `.gcode.3mf`. See [slicing guide](https://github.com/DMontgomery40/bambu-printer-mcp/blob/main/docs/SLICING.md) for the full procedure.
2. **Layer height, temperature, and slicer settings are baked in.** The `project_file` MQTT command tells the printer which plate to run. It does not support overriding layer height, temperature targets, infill percentage, or other slicing parameters at print time. These must be set in your slicer before generating the 3MF.
3. **G-code and 3MF jobs use different command paths.** `start_print_job` sends a `GCodeFileCommand` over MQTT and is intended only for plain G-code files stored in the `cache/` directory. Sliced 3MF files must go through `print_3mf`, which selects the model-specific command and upload location. See the [routing table](https://github.com/DMontgomery40/bambu-printer-mcp/blob/main/docs/SLICING.md#firmware-routing-handled-internally). Mixing these up will result in the printer either ignoring the command or displaying an error.
4. **Temperature commands depend on printer state.** `set_temperature` dispatches M104 or M140 G-code via MQTT. Whether the printer accepts these commands depends on its current firmware version and operational state. Some printer states (such as the idle screen with AMS management open) may ignore or queue the commands.
5. **Status and command submission are separate evidence.** Local MQTT connections are reused and status is read from received printer reports. Reports can lag during startup, sleep, or state transitions. Inspect HMS errors and the printer's actual state after submitting a job; a tool's success response is not physical-print confirmation.
6. **Only hidden or informational HMS entries let an operation proceed.** Fatal, serious and common HMS entries block printing, heating and resume. The exceptions are info-level entries and serious or common entries that Bambu lists for the exact printer model with empty text: the printer screen, Bambu Studio and Bambu Handy never show those, and the printer keeps printing with them. An X2D, for example, raises the hidden camera code `HMS 0500-0600-0002-0070` at every boot. The list comes from the bundled HMS tables of a pinned Bambu Studio commit (`scripts/generate-hidden-hms.mjs`), selected by the printer's serial prefix as Bambu Studio does, and covers X2D, P2S, H2D, H2D Pro, H2S and H2C. Each model's own table decides: `0500-0600-0002-0070` is hidden in the X2D table, but the P2S table does not list it, so it still blocks a P2S. If Bambu later publishes text for a listed code, the gate keeps passing it until the list is regenerated from a newer Studio release. The print preflight names any hidden entry it passes, and the first check that passes each one logs it on stderr, so headless runs still record it. `clear_hms_errors` leaves hidden entries alone. Unlisted codes and other models' tables never qualify.
7. **Network requirements depend on the path.** Direct MQTT/FTPS tools require local reachability and compatible LAN/Developer Mode settings. FULU bridge cloud jobs use BambuNetwork and require its runtime, internet access, and authentication. Standard status, camera, file, and control tools remain local; bridge printing does not turn them into cloud tools.
8. **Self-signed TLS certificate.** The printer's FTPS server uses a self-signed certificate. The `basic-ftp` client is configured with `rejectUnauthorized: false` to accept it. This is standard for local network Bambu connections but assumes a trusted local network environment.
</details>
<details>
<summary><strong>General Limitations and Considerations</strong></summary>
## General Limitations and Considerations
### Memory usage
STL manipulation tools load the entire mesh into memory as Three.js geometry. For large files:
- Files over 10 MB can consume several hundred MB of RAM during processing.
- Running multiple operations sequentially on large files may cause memory to accumulate between garbage collection cycles.
- If you encounter out-of-memory errors, try splitting large operations or working with smaller/simplified meshes.
- The server has no built-in memory cap. On constrained systems, set the `TEMP_DIR` to a fast local path and avoid processing multiple large files concurrently.
### STL manipulation limitations
- `lay_flat` identifies the largest flat face by analyzing surface normals. It works reliably on mechanical parts with clear flat faces and less reliably on organic or curved models where no single dominant face exists.
- `extend_stl_base` adds a new rectangular solid beneath the model. For models with complex or non-planar undersides, the result may include gaps or intersections at the join. Review the modified STL before printing.
- `merge_vertices` uses a distance tolerance to identify near-duplicate vertices. Setting the tolerance too high can alter model geometry. The default of 0.01 mm is safe for most models.
- Non-manifold meshes (meshes with holes, overlapping faces, or internal geometry) may produce unpredictable results for any transformation operation. Use a mesh repair tool (Meshmixer, PrusaSlicer's repair function, or Bambu Studio's repair option) before working with problematic files.
### Performance considerations
- Slicing with BambuStudio CLI can take 30 seconds to several minutes depending on model complexity, layer height, and your system's CPU. The `slice_stl` call is synchronous and will block until the slicer process completes.
- FTPS uploads for large 3MF files (multi-plate prints, high-detail models) may take 15 to 60 seconds depending on your local network speed.
- MQTT connections are pooled by `host + serial` key. The first call to any printer tool in a session establishes the MQTT connection; subsequent calls reuse it. If the connection drops (printer power cycled, network interruption), the next call will reconnect automatically.
</details>
<details>
<summary><strong>License</strong></summary>
## License
GPL-2.0. See [LICENSE](./LICENSE) for the full text.
This project is a fork of [mcp-3D-printer-server](https://github.com/DMontgomery40/mcp-3D-printer-server) by David Montgomery, also GPL-2.0.
</details>
<details>
<summary><strong>Acknowledgements</strong></summary>
## Acknowledgements
Thank you to **[FULU Foundation](https://www.fulu.org/), [Louis Rossmann](https://www.youtube.com/watch?v=1jhRqgHxEP8), and the [OrcaSlicer-bambulab community](https://github.com/FULU-Foundation/OrcaSlicer-bambulab)** for advancing user choice and interoperability. Our support for open-source tools, repair rights, and printing without Bambu's software or cloud is a project priority. See the [FULU setup guide](https://github.com/DMontgomery40/bambu-printer-mcp/blob/main/docs/FULU.md) and [contributor credits](./CONTRIBUTORS.md).
Some printer command surfaces and workflow priorities were informed by [Bambuddy](https://github.com/maziggy/bambuddy), an AGPL-3.0 Bambu Lab printer management project. This project does not vendor Bambuddy code.
</details>
### Print dispatch truth (`counterpart/1`)
An error from `print_3mf` or `upload_gcode` carries `structuredContent.printOutcome`: `not-sent` means no print command was handed to a transport (including fresh-report timeouts, preparation failures and upload failures); `unknown` means a command may have reached the printer; `rejected` means the printer refused the command. A file upload alone is not a print command. Clients use this field, never error wording or firmware codes, to distinguish “Not started” from a print that needs checking. Missing or unrecognized values cannot prove nothing was sent. Other structured error fields remain compatible.
TDQS
Scored across 72 tools
Several clusters overlap heavily: start_print is explicitly an alias of start_print_job, and print_3mf / print_3mf_bambu_network / print_collar_charm / upload_gcode all dispatch prints; slice_stl vs slice_with_template, upload_file vs upload_gcode, and blender_mcp_call vs blender_mcp_edit_model require careful reading to separate. Descriptions are rich and often disambiguate in prose, but the surface presents many near-duplicate entries (multiple cad_*, counterpart_*, and status tools) that an agent can easily misselect.
Most names are snake_case, but the conventions are mixed: verb_noun (slice_stl, start_print, delete_printer_file), noun-first status/info reads (cad_status, get_stl_info, counterpart_printer_info, get_printer_status), and subsystem-prefixed calls (bambu_connect_import_file, blender_mcp_*, fusion_mcp_*). Prefixes like get_ are applied inconsistently, so the pattern is readable but not predictable.
72 tools is a severe mismatch for a printer MCP; the server has absorbed slicing, STL editing, code-CAD, Blender, Fusion, camera streaming, AMS inventory and a counterpart contract layer into one surface. Individual capabilities could each be their own server, and the sheer volume makes discovery and correct selection costly.
Coverage is broad across the printer lifecycle — discovery, status, AMS/filament, slicing, upload, print start/pause/resume/cancel, camera, HMS clearing, file management — plus CAD and external-bridge workflows. Minor gaps remain (no explicit job queue/history listing beyond per-job records, no generic settings read/write for printer config), but agents can work around them.