Setup reference for agents and manual configuration
The README provides the copy-and-paste setup request. Use this reference for exact environment variables, per-printer credentials, manual MCP client configuration, Docker, and troubleshooting. Preserve the user's existing MCP configuration and use the current harness's supported configuration format.
Installation
Prerequisites
- Node.js 24, recommended (the package supports Node.js 18 or later, CI tests 18 through 24, and the Docker image uses 24; on end-of-life Node.js 18, npm installs the older
@hono/node-server1.x) - npm
- A slicer (only needed to slice): PrusaSlicer, OrcaSlicer, FULU OrcaSlicer-bambulab, Bambu Studio, Slic3r, or CuraEngine. A file that is already sliced can be uploaded and printed without a slicer on the MCP host. See the slicing guide.
- Blender with a Blender MCP server (optional): only for the
blender_mcp_*tools. See Blender MCP.
Run without installing (npx)
The fastest way to get started. No global install is required:
npx -y mcp-3d-printer-serverMCP clients normally launch this command for you over stdio. Set environment variables in the client's MCP configuration or in a .env file in the server's working directory (see Configuration).
Install globally from npm
npm install -g mcp-3d-printer-serverThe package installs three commands: mcp-3d-printer-server (stdio by default), mcp-3d-printer-server-http (streamable HTTP), and mcp-3d-printer-server-sse (a legacy name that also starts the streamable HTTP runtime).
Install from source
git clone https://github.com/DMontgomery40/mcp-3D-printer-server.git
cd mcp-3D-printer-server
npm ci
npm run build
npm linknpm link makes the mcp-3d-printer-server command available globally without publishing to npm. Build from a clean checkout before linking; dist/ is generated and not tracked in Git.
Choose your printer backend
PRINTER_TYPE selects the printer adapter. Every printer tool also accepts type, host, port, and api_key arguments, so one server can reach more than one printer; values you omit fall back to the environment.
PRINTER_PORT defaults to 80 for every backend. Set it explicitly when your system listens elsewhere, such as Moonraker on 7125 or Repetier-Server on 3344.
PRINTER_TYPE | System | Connection | Credentials | Testing evidence |
|---|---|---|---|---|
bambu | Bambu Lab | MQTT over TLS on port 8883, FTPS on port 990 | BAMBU_SERIAL, BAMBU_TOKEN (LAN access code), BAMBU_MODEL | Most tested. Maintainer hardware testing, shared with the Bambu-only fork. The FTPS fix for #22 is tested against a local FTPS server; confirmation on the reporter's X1C is outstanding. |
octoprint | OctoPrint | HTTP REST API, port 80 on OctoPi; or a URL for HTTPS and reverse-proxy sub-paths | API_KEY, sent as X-Api-Key; octoprint_request_api_key gets one through OctoPrint's Application Keys approval | Community-reported. Used against a real OctoPrint in #4. Checked with OctoPrint 1.11.8 and 2.1-dev Virtual Printers (npm run test:octoprint-live). |
klipper | Klipper (Moonraker) | HTTP API, usually port 7125 | Optional API_KEY, sent as X-Api-Key; otherwise Moonraker must trust the MCP host | Community-reported. Used with a Creality K1 Max through Moonraker in #16. |
prusa | PrusaLink / Prusa Connect | HTTP for local PrusaLink, HTTPS for connect.prusa3d.com or port 443 | API_KEY, sent as X-Api-Key and as a bearer token | Community-reported. PrusaLink 0.8.1 status fixed after #11; Prusa Connect setup discussed in #9. |
duet | Duet | HTTP, port 80 | None sent | Unverified. No hardware reports yet. |
repetier | Repetier-Server | HTTP, usually port 3344 | API_KEY, sent as the apikey query parameter | Unverified. No hardware reports yet. |
creality | Creality | HTTP, port 80 | API_KEY, sent as a bearer token | Unverified. No hardware reports yet. |
Bambu Lab has the most hardware testing. The other adapters call each system's HTTP API as described in Printer backend setup, and hardware evidence comes from community reports. If a backend misbehaves, please open an issue with the printer, firmware or host software version, and the tool error.
Configuration
Create a .env file in the directory where you run the server, or pass environment variables in your MCP client configuration. Configure only the block for your backend.
# --- Printer backend (pick one PRINTER_TYPE) ---
PRINTER_TYPE=octoprint # octoprint, klipper, duet, repetier, bambu, prusa, creality
PRINTER_HOST=192.168.1.100 # Printer or host-software address
PRINTER_PORT=80 # 7125 for Moonraker, 3344 for Repetier-Server
API_KEY=your_api_key # OctoPrint, authenticated Moonraker, Repetier, Prusa, Creality
# --- Bambu Lab only ---
# BAMBU_SERIAL=01P00A123456789 # Printer serial number
# BAMBU_TOKEN=your_access_code # LAN access code from the printer screen
# BAMBU_MODEL=p1s # Required for Bambu print operations
# BED_TYPE=textured_plate # textured_plate, cool_plate, engineering_plate, hot_plate, supertack_plate
# BAMBU_NOZZLE_TYPE=hardened_steel # Only if you replaced the stock nozzle (P1S/P1P/A1 ship stainless_steel)
# NOZZLE_DIAMETER=0.4 # 0.2, 0.4, 0.6, or 0.8 for printing and heating
# --- Print and heating safety (see "Print and heating safety" below) ---
# PRINTER_MAX_NOZZLE_TEMP=300 # Non-Bambu heater ceilings in °C (Bambu uses per-model limits)
# PRINTER_MAX_BED_TEMP=120
# PRINTER_MAX_CHAMBER_TEMP=60
# --- Slicer (only for slice_stl, process_and_print_stl, and Bambu auto-slicing) ---
# SLICER_TYPE=prusaslicer # prusaslicer, slic3r, orcaslicer, orcaslicer-bambulab, bambustudio, cura
# SLICER_PATH=/path/to/slicer/executable
# SLICER_PROFILE=/path/to/profile # Bambu-compatible slicers: a process profile only
# FILAMENT_PROFILE=/path/to/filament.json
# BAMBU_PROFILES_ROOT= # Profile tree containing BBL/, if not found from SLICER_PATH
# BAMBU_TEMPLATE_DIR=~/Sync/bambu/templates
# --- Progress, camera, pause and resume (counterpart/1) ---
# PRINTER_CAMERA_SNAPSHOT_URL=http://192.168.1.60/snapshot.jpg # Override the camera source for any printer
# REPETIER_SLUG=my_printer # Repetier-Server printer slug, when the server has more than one printer
# --- MCP transport ---
# MCP_TRANSPORT=stdio # stdio (default) or streamable-http
# MCP_HTTP_HOST=127.0.0.1
# MCP_HTTP_PORT=3000
# MCP_HTTP_PATH=/mcp
# --- Optional standard Blender MCP server ---
# BLENDER_MCP_COMMAND=/full/path/to/uvx
# BLENDER_MCP_ARGS=["mcp-for-blender"]
# BLENDER_MCP_TIMEOUT_MS=120000
# --- Optional Autodesk Fusion (DMontgomery40/fusion360-mcp-server: faust-machines with document binding, pinned; fusion_mcp_setup adds these on macOS) ---
# FUSION_MCP_COMMAND=/full/path/to/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
# FUSION_MCP_PORT=9877Environment variables reference
| Variable | Default | Required | Description |
|---|---|---|---|
PRINTER_TYPE | octoprint | Yes | Printer adapter: octoprint, klipper, duet, repetier, bambu, prusa, or creality |
PRINTER_HOST | localhost | Yes | Printer or host-software address. Prusa also accepts connect.prusa3d.com or a full https:// URL |
PRINTER_PORT | 80 | Depends on backend | HTTP port for non-Bambu backends. Set 7125 for Moonraker and 3344 for Repetier-Server |
API_KEY | OctoPrint, authenticated Moonraker, Repetier, Prusa, Creality | Backend API key or token. Duet does not send one | |
BAMBU_SERIAL | Bambu | Printer serial number | |
BAMBU_TOKEN | Bambu | LAN access code shown on the printer | |
BAMBU_MODEL | Bambu printing and Bambu slicing | Printer model: p1s, p1p, x1c, x1e, a1, a1mini, h2d. Required for print_3mf, start_print, upload with print: true, positive Bambu heating, and Bambu-compatible slicing. Slicing also accepts p2s, h2s, and h2c when the installed slicer has that preset. If omitted and the MCP client supports elicitation, the server asks. It selects the slicer machine preset and is checked against the live printer before printing. | |
BED_TYPE | textured_plate | No | Bambu bed plate: textured_plate, cool_plate, engineering_plate, hot_plate, supertack_plate. print_3mf requires it to match the sliced plate's bed metadata |
NOZZLE_DIAMETER | 0.4 | No | Nozzle diameter in mm. Selects the Bambu machine preset; printing and heating accept 0.2, 0.4, 0.6, or 0.8 and check it against the live printer |
BAMBU_NOZZLE_TYPE | model preset's stock nozzle | No | Installed nozzle for slicing: stainless_steel, hardened_steel, tungsten_carbide, or brass. Set it if you upgraded the nozzle; printing requires the job's nozzle type to match the printer's report |
BAMBU_DISPATCH_CHECK_MS | 15000 | No | How long to watch the printer's reports after a print command to confirm it started or was refused (0 to 60000; 0 skips the check) |
PRINT_CONFIRMATION_TIMEOUT_MS | 600000 (10 minutes) | No | How long a person has to answer a server prompt (the Bambu printer-model prompt, or a resume the printer cannot re-read), from 1000 to 3600000. When it runs out, nothing is sent and the error says so |
PRINTER_MAX_NOZZLE_TEMP | 300 | No | Nozzle ceiling in °C for non-Bambu printers. Only the server environment can change it; an invalid value refuses printing and positive heating |
PRINTER_MAX_BED_TEMP | 120 | No | Bed ceiling in °C for non-Bambu printers |
PRINTER_MAX_CHAMBER_TEMP | 60 | No | Chamber ceiling in °C for non-Bambu printers |
SLICER_TYPE | bambustudio when PRINTER_TYPE=bambu, otherwise prusaslicer | No | prusaslicer, slic3r, orcaslicer, orcaslicer-bambulab (FULU), bambustudio, or cura. See slicer aliases |
SLICER_PATH | Common install paths for FULU/Orca and Bambu Studio; none for other slicers | To slice | Slicer executable. Per-call slicer_path requires MCP_ALLOW_EXECUTABLE_ARG=1 |
SLICER_PROFILE | No | Bambu-compatible slicers: one process profile JSON; the machine preset always comes from the model and nozzle. Generic OrcaSlicer: machine.json;process.json, optionally |filament.json. See the slicing guide | |
FILAMENT_PROFILE | No | Filament profile path(s) loaded with --load-filaments, ;-separated in slot order; PrusaSlicer and Slic3r take one .ini preset, loaded with a second --load. Alias: SLICER_FILAMENT_PROFILE | |
SLICER_TIMEOUT_MS | 600000 | No | Slicer process deadline in milliseconds |
SLICER_STALL_SECONDS | 20 | No | On macOS, a Bambu-compatible slicer that uses no CPU and writes nothing this long is stopped as waiting on a dialog (slicer-waiting-on-dialog); 0 turns it off |
BAMBU_PROFILES_ROOT | found from SLICER_PATH | No | Profile tree containing BBL/ for the active Bambu-compatible slicer. Set it for AppImages or non-standard installs. It is never replaced by another installation's tree |
BAMBU_SLICER_PROFILE_DIRS | the slicers' user system/BBL directories | No | Directories (separated with your OS path delimiter) searched for custom process and filament parents, never for machine presets. Set it empty to disable the search |
BAMBU_TEMPLATE_DIR | ~/Sync/bambu/templates | No | Local template registry for slice_with_template, list_templates, save_template, and get_slice_settings |
BAMBU_TEMPLATE_3MF_PATH | No | Default template 3MF whose slicer settings are reused as the process profile. A 3MF that carries its own process settings slices with them instead | |
FULU_ORCA_PATH | No | FULU OrcaSlicer-bambulab executable when SLICER_PATH is unset. Aliases: ORCASLICER_BAMBULAB_PATH, ORCA_SLICER_BAMBULAB_PATH | |
FULU_ORCA_PLUGIN_DIR | No | Directory with FULU's runtime payload, such as OrcaSlicer.app/Contents/MacOS. Alias: ORCASLICER_BAMBULAB_PLUGIN_DIR | |
PJARCZAK_MAC_RUNTIME_DIR | ~/Library/Application Support/OrcaSlicer/macos-bridge/runtime | No | Installed FULU macOS runtime directory |
FULU_BAMBU_BRIDGE_COMMAND | FULU bridge tools | Trusted command that starts the FULU BambuNetwork bridge host. See the FULU guide | |
BAMBU_DEFAULT_CONNECTION_MODE | lan_mqtt_ftps | No | Bambu print and control route: lan_mqtt_ftps (direct LAN MQTT/FTPS) or bambu_network (FULU's BambuNetwork bridge). counterpart_print_route changes it. See FULU's route |
BAMBU_DEFAULT_CONNECTION_TYPE | cloud | No | For bambu_network: lan (the plug-in's authenticated local session) or cloud (Bambu Cloud) |
BAMBU_NETWORK_BRIDGE_COMMAND | FULU_BAMBU_BRIDGE_COMMAND, then the discovered FULU host | For bambu_network | Trusted FULU bridge host command for the print route, bambu-printer-mcp's name for it |
BAMBU_NETWORK_BRIDGE_IMAGE | For bambu_network | Instead of a command: a Docker image holding FULU's x86-64 Linux host, which the server runs with its own agent config folder mounted (print snapshots are written there). With BAMBU_NETWORK_BRIDGE_DOCKER, BAMBU_NETWORK_BRIDGE_DOCKER_HOST and the optional Colima profile settings. See FULU in Docker | |
BAMBU_NETWORK_CONFIG_DIR | ~/.config/mcp-3d-printer-server/bambu-network | No | FULU agent config folder; holds control-trace.log and the pinned certificates |
BAMBU_NETWORK_COUNTRY_CODE, BAMBU_NETWORK_USER_INFO, BAMBU_NETWORK_CERT_DIR, PJARCZAK_BAMBU_PLUGIN_DIR | US, none, <config>/cert, none | No | Region, optional cloud net.change_user JSON, certificate folder, and the networking plug-in folder for the bridge host |
MCP_ALLOW_EXECUTABLE_ARG | disabled | No | Accept per-call executable selectors (slicer_path, bridge_command, and bridge probe paths). Leave off for normal use; see executable settings |
MCP_ALLOW_BRIDGE_COMMAND_ARG | disabled | No | Legacy opt-in for bridge commands and bridge-derived probe paths only |
PRINTER_CAMERA_SNAPSHOT_URL | No | JPEG snapshot URL used by camera_snapshot for any printer, instead of the firmware's own webcam settings. Use it for PrusaLink and Duet, which have no local snapshot API | |
REPETIER_SLUG | the only printer | Repetier-Server with several printers | Printer slug the progress, camera, pause and resume tools address. Without it, those tools need a server with exactly one printer |
TEMP_DIR | private folder under the system temporary directory | No | Intermediate files. Each server instance gets its own folder unless you set this |
MCP_TRANSPORT | stdio | No | stdio or streamable-http |
MCP_HTTP_HOST | 127.0.0.1 | No | HTTP bind address (HTTP transport only) |
MCP_HTTP_PORT | 3000 | No | HTTP port. The legacy -http and -sse commands also read HTTP_PORT and SSE_PORT |
MCP_HTTP_PATH | /mcp | No | HTTP endpoint path |
MCP_HTTP_STATEFUL | true | No | Stateful HTTP sessions |
MCP_HTTP_JSON_RESPONSE | true | No | Return JSON responses instead of event streams where possible |
MCP_HTTP_ALLOWED_ORIGINS | No | Comma-separated allowed browser origins. Requests with any other Origin header are rejected; requests without one are accepted | |
BLENDER_MCP_COMMAND | No | Trusted executable for a standard stdio Blender MCP server, such as the full path to uvx | |
BLENDER_MCP_ARGS | [] | No | JSON array of arguments, such as ["mcp-for-blender"]; no shell parsing |
BLENDER_MCP_TIMEOUT_MS | 120000 | No | Connection, discovery, and call deadline, 100 to 300000 ms |
BLENDER_MCP_BRIDGE_COMMAND | No | Legacy trusted shell command for blender_mcp_edit_model; separate from the standard MCP integration | |
FUSION_MCP_COMMAND | No | Trusted executable for the Fusion MCP server: the full path to uvx. See the Fusion guide | |
FUSION_MCP_ARGS | the pinned server in socket mode | No | JSON array of arguments; no shell parsing. The default is ["--from","git+https://github.com/DMontgomery40/fusion360-mcp-server@86a3016a5a5b","fusion360-mcp-server","--mode","socket"] |
FUSION_MCP_TIMEOUT_MS | 180000 | No | Connection, discovery, and call deadline, 100 to 600000 ms |
FUSION_MCP_HOST | localhost | No | Where the Fusion360MCP add-in listens, passed through to the Fusion server |
FUSION_MCP_PORT | 9877 | No | The add-in's port. Blender's MCP addon holds the add-in's own default, 9876, so Fusion uses 9877. The Fusion server and the add-in both read it; Fusion, a GUI app, needs it in its own environment (see the Fusion guide) |
The Blender MCP server also reads its own BLENDER_HOST and BLENDER_PORT (default localhost:9876) if the addon listens elsewhere. This server passes only BLENDER_* settings to it, never printer credentials. See the Blender guide. The Fusion MCP server likewise receives only FUSION_* settings, and runs on FUSION_MCP_PORT=9877 so it does not collide with Blender on 9876. See the Fusion guide.
Print and heating safety
Every print start and positive heating command goes through the same gate on every backend. Heater-off (a target of 0) and cancel_print are never gated, and cancelling also cancels checked prints that are still waiting to start.
- No confirmation prompt and no bed check. Print starts and positive heating do not ask a person: the client's own print action is the approval, and the printer runs its own checks. A printer that reports a finished job (Moonraker complete or cancelled, PrusaLink FINISHED or STOPPED, Creality finished, Bambu FINISH) starts the next print like an idle one, so clear the plate first. Only a resume that the printer cannot re-read asks a person (see progress, camera, pause and resume).
- File inspection. The server inspects a private copy of the exact G-code or 3MF plate it will start: every
SandRheater target, tool changes, and RepRapFirmware and Klipper heater commands. Klipper macro parameters that name a heater are checked; macro bodies stored on the printer cannot be. - Ceilings. Nozzle, bed, and chamber targets have independent ceilings: 300, 120, and 60 °C by default on non-Bambu printers (
PRINTER_MAX_NOZZLE_TEMP,PRINTER_MAX_BED_TEMP,PRINTER_MAX_CHAMBER_TEMP), and per-model hardware limits on Bambu printers. Material ceilings also apply, such as 260 °C for PLA. Only the server environment can change a ceiling; tool arguments and G-code never raise one. - Declared material. Printing and positive nozzle heating need a material: slicer metadata in the G-code (
; filament_type = PETG) or thematerialargument, which must not contradict the file. A declaration cannot prove which spool is physically loaded. - Printer state. Printing, paused, errored, offline, or unreadable printers are refused. On Bambu printers, a fresh MQTT report must match the model, serial, nozzle, and loaded filament, when the job is checked, again just before upload, and again before dispatch.
- Files already on the printer.
start_printdownloads the file, inspects it, and starts a uniquely named checked copy on Bambu Lab, OctoPrint, Klipper (Moonraker), and Duet. Repetier, Prusa, and Creality refuse remote starts because no verified download route exists; upload the local G-code withprint: true. - Expected temperatures.
process_and_print_stlrefuses before uploading whenextruder_temporbed_tempdiffers from the sliced job's highest target.
Known gaps: Klipper macro bodies and RepRapFirmware stored tool temperatures (used when a job selects a tool with T0 without setting a target first) live on the printer and cannot be inspected. Activation-only M568 and M144 commands are refused for that reason.
Evidence: the gate is covered by unit tests and MCP-level tests with mocked MQTT and FTPS boundaries and loopback HTTP printer APIs. On one real P1S, the Bambu path read fresh status, refused a job sliced for the wrong nozzle type, asked for confirmation (an earlier release, before the prompt was removed), and uploaded the checked file. The firmware then refused the unsigned start command because Developer Mode was off. Acceptance at a mocked boundary does not prove firmware behavior, and the other printer systems have not been exercised on real hardware in this release.
MCP client configuration
Add the server to your MCP client (Claude Desktop, Claude Code, Cursor, Codex, or any MCP-compatible client). JSON-based clients use an mcpServers entry with the command, arguments, and environment. This OctoPrint example shows the shape:
{
"mcpServers": {
"3dprint": {
"command": "npx",
"args": ["-y", "mcp-3d-printer-server"],
"env": {
"PRINTER_TYPE": "octoprint",
"PRINTER_HOST": "192.168.1.100",
"PRINTER_PORT": "80",
"API_KEY": "your_octoprint_api_key"
}
}
}
}Replace the env block with the values for your backend:
| Backend | env values |
|---|---|
| OctoPrint | PRINTER_TYPE=octoprint, PRINTER_HOST, PRINTER_PORT (80 on OctoPi, 5000 for octoprint serve), API_KEY |
| Klipper (Moonraker) | PRINTER_TYPE=klipper, PRINTER_HOST, PRINTER_PORT=7125, API_KEY if required |
| Duet | PRINTER_TYPE=duet, PRINTER_HOST, PRINTER_PORT if not 80 |
| Repetier-Server | PRINTER_TYPE=repetier, PRINTER_HOST, PRINTER_PORT=3344, API_KEY |
| Bambu Lab | PRINTER_TYPE=bambu, PRINTER_HOST, BAMBU_SERIAL, BAMBU_TOKEN, BAMBU_MODEL |
| PrusaLink | PRINTER_TYPE=prusa, PRINTER_HOST (printer IP), API_KEY |
| Prusa Connect | PRINTER_TYPE=prusa, PRINTER_HOST=connect.prusa3d.com, PRINTER_PORT=443, API_KEY |
| Creality | PRINTER_TYPE=creality, PRINTER_HOST, PRINTER_PORT if not 80, API_KEY |
A Bambu Lab example with FULU OrcaSlicer-bambulab slicing:
{
"mcpServers": {
"3dprint": {
"command": "npx",
"args": ["-y", "mcp-3d-printer-server"],
"env": {
"PRINTER_TYPE": "bambu",
"PRINTER_HOST": "192.168.1.100",
"BAMBU_SERIAL": "01P00A123456789",
"BAMBU_TOKEN": "your_lan_access_code",
"BAMBU_MODEL": "p1s",
"SLICER_TYPE": "orcaslicer-bambulab",
"SLICER_PATH": "/Applications/OrcaSlicer.app/Contents/MacOS/OrcaSlicer"
}
}
}
}Codex uses TOML instead of JSON. Add a [mcp_servers.<name>] table to config.toml, or manage servers with codex mcp:
[mcp_servers.3dprint]
command = "npx"
args = ["-y", "mcp-3d-printer-server"]
env = { PRINTER_TYPE = "klipper", PRINTER_HOST = "192.168.1.50", PRINTER_PORT = "7125" }Claude Code can add the server from the command line:
claude mcp add 3dprint -e PRINTER_TYPE=klipper -e PRINTER_HOST=192.168.1.50 -e PRINTER_PORT=7125 -- npx -y mcp-3d-printer-serverWhere the configuration lives depends on your client and OS:
| Client / scope | macOS / Linux location | Windows location |
|---|---|---|
| Claude Desktop | ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) | %APPDATA%\Claude\claude_desktop_config.json |
| Claude Code (project-shared MCP) | .mcp.json in the project root | .mcp.json in the project root |
| Claude Code (user/local MCP) | ~/.claude.json; see Claude Code MCP configuration | %USERPROFILE%\.claude.json |
| Cursor (project) | .cursor/mcp.json in the project root | .cursor\mcp.json in the project root |
| Cursor (global) | ~/.cursor/mcp.json | %USERPROFILE%\.cursor\mcp.json |
| Codex (user; CLI, IDE extension, app) | ~/.codex/config.toml; see Codex MCP configuration | %USERPROFILE%\.codex\config.toml |
| Codex (project, trusted projects only) | .codex/config.toml in the project root | .codex\config.toml in the project root |
Restart or reload your client after editing its configuration. For Codex, run /mcp or codex mcp list to confirm the server loaded.
Executable settings stay in server configuration
Slicer paths, the FULU bridge command, and Blender MCP commands select which local program this server launches. By default they are read only from the server's environment. A per-call slicer_path, bridge_command, or bridge probe path returns an error that names the opt-in flag unless MCP_ALLOW_EXECUTABLE_ARG=1 is set. Accepting those arguments would let anything steering the model, such as a downloaded model's description or 3MF metadata, choose a program to run. Set the flag only for trusted, interactive diagnostics. The standard Blender MCP command and arguments are always server configuration.
Optional code mode
Use the harness's built-in code mode or equivalent when available. Otherwise, an agent may suggest a compatible, maintained integration as an optional way to discover and compose tools; check that integration's current setup instructions first. Direct MCP use is fully supported; code mode is not required. See code execution with MCP for the approach.
Printer backend setup
OctoPrint
- Set
PRINTER_TYPE=octoprintandPRINTER_HOST. OctoPi serves on port 80; a directly runoctoprint serveusually listens on 5000 (PRINTER_PORT). - Get a key. Either let the person approve this server in OctoPrint (no copying): call
octoprint_request_api_key, have them click Allow in OctoPrint's web page (or open the returnedapprove_url), and call it again with therequest_idto get the key forAPI_KEY. Or create an application key yourself under Settings → Application Keys.
PRINTER_HOST may be a full URL for an OctoPrint behind HTTPS or a reverse proxy at a sub-path, for example https://octopi.example.com or http://pi.lan/octoprint/ (OctoPrint's reverse-proxy setup with X-Script-Name). A URL keeps its own port. HTTPS certificates are verified, so OctoPi's self-signed certificate on port 443 is refused; use http:// on the local network instead. A URL with a user name or password is refused: the key goes in API_KEY.
The adapter sends the key as X-Api-Key. It calls /api/printer for status (state and temperatures, not job progress), /api/files?recursive=true to list files, /api/files/local to upload (optionally selecting and printing), /api/files/local/<file> to start a file, /downloads/files/local/<file> to download a stored file for inspection before start_print, /api/job to cancel, and /api/printer/bed or /api/printer/tool (nozzle targets go to tool0) for temperature targets. The file list keeps each file's name, path, type, size, date, OctoPrint's estimated print time and its last print, and each folder's children, so a large library stays small (#4).
Not connected. OctoPrint can be running while it isn't connected to the printer (state Closed, or Offline after error). counterpart_print_progress then reports the problem octoprint.disconnected instead of a bare offline, and a print is refused with the same words. connect_printer connects it (POST /api/connection {"command":"connect"}) with OctoPrint's own saved port, baud rate and printer profile, or the ones given. With PSU Control installed and the printer switched off, the problem says so, and connect_printer with power_on: true switches it on first (POST /api/plugin/psucontrol {"command":"turnPSUOn"}).
Prusa printers and binary G-code. OctoPrint streams plain-text G-code, and this server inspects the exact text before printing. Prusa's MK4, MK4S, MINI, XL and CORE One profiles turn on binary G-code (.bgcode) once it is enabled in PrusaSlicer's preferences, and PrusaSlicer then writes binary bytes even into a .gcode file. slice_stl with PrusaSlicer adds --no-binary-gcode when the loaded config has binary_gcode = 1, and stops with an explanation if a slice still comes out binary. Prusa recommends plain G-code with OctoPrint too (Prusa help).
Checked against OctoPrint 1.11.8 and the 2.1 development build (octoprint/octoprint:canary, past 2.0) in Docker with OctoPrint's Virtual Printer, directly and behind an nginx sub-path proxy (npm run test:octoprint-live): the approval flow, disconnect and connect_printer, upload and print, progress, the file list, and cancel. PSU Control is checked against a fake OctoPrint only. No physical printer was used.
Klipper (Moonraker)
- Make sure Moonraker is reachable from the machine running the MCP server, usually on port 7125.
- If Moonraker requires authentication, set
API_KEYto its API key. The adapter sendsX-Api-Keyon status, file, upload, start, cancel, and heater requests. Leave it empty only when Moonraker trusts the MCP host. See Moonraker authentication. - Set
PRINTER_TYPE=klipper,PRINTER_HOST, andPRINTER_PORT=7125.
get_printer_status comes from /printer/info, which reports the Klipper host state (for example ready) but not job progress or temperatures. For job progress, layers and temperatures (#12), use counterpart_print_progress; see progress, camera, pause and resume. The safety gate reads /printer/objects/query?webhooks&print_stats to refuse a printer that is printing, paused, or in error. Other calls: /server/files/list, /server/files/upload, /server/files/gcodes/<file> (download for inspection before start_print), /printer/print/start, /printer/print/cancel, and SET_HEATER_TEMPERATURE through /printer/gcode/script for bed and extruder.
Duet
Set PRINTER_TYPE=duet, PRINTER_HOST, and PRINTER_PORT if not 80. The adapter targets the Duet Software Framework (DuetWebServer) REST API and sends no password. It calls GET /machine/status, GET /machine/directory/0:/gcodes to list files, GET and PUT /machine/file/<path> to download and upload, and POST /machine/code with a raw-text G-code body (M32 to start, M0 to cancel, M140/M104 for temperatures). The routes follow DuetSoftwareFramework's MachineController; printing and file routes do not support standalone RepRapFirmware. The progress, pause and resume tools also speak the rr_model and rr_gcode routes that standalone boards and DSF 3.5 or later answer, falling back to /machine/status and /machine/code. They have not been verified against Duet hardware; please report which firmware and host mode you tested.
Repetier
Set PRINTER_TYPE=repetier, PRINTER_HOST, PRINTER_PORT (Repetier-Server usually uses 3344), and API_KEY from Repetier-Server. The adapter sends the key as an apikey query parameter to /printer/api/ with actions such as listPrinter, getPrinterInfo, ls, startJob, stopJob, setBedTemp, and setExtruderTemp. The print and file routes do not add a printer slug to the path, and they have not been verified against a Repetier-Server install. The progress, camera, pause and resume tools address /printer/api/<slug>, using REPETIER_SLUG or the server's only printer. It has no verified download route, so start_print of a file already on the server is refused; upload the local G-code with print: true.
Bambu Lab
Bambu Lab printers use local MQTT (port 8883, TLS) for status and commands and FTPS (port 990, implicit TLS) for uploads. You need the printer's IP address, serial number, LAN access code, and model.
Enable LAN Only Mode and Developer Mode
Enable LAN Only Mode and, on firmware with authorization controls, Developer Mode so third-party clients can send commands. Older firmware may expose the LAN interfaces without a separate Developer Mode toggle. Follow the model-specific LAN setup and Developer Mode instructions; firmware and menu names vary.
- On the printer's touchscreen, open Settings, then the Network (WLAN) page. It shows the network name, IP address, the LAN Only Mode toggle, and the access code.

- Turn LAN Only Mode on. This enables the local MQTT and FTPS services the server uses. It also disconnects the printer from Bambu's cloud, so the Bambu Handy app stops working while it is on; slicers can still connect over LAN.
- On firmware that provides it, turn Developer Mode on in the same LAN settings.
- Note the Access Code. It is your
BAMBU_TOKEN. Refreshing it disconnects clients that use the old code.

Find the serial number
The serial number is on a sticker on the back or underside of the printer, and on the touchscreen under Settings > Device Info. P1 series serials usually begin with 01P, X1 series with 01X, and A1 series with 01A. Bambu Studio also shows it under Device > Device Management.

Find the access code
- P1 series (P1P, P1S): Settings > Network / WLAN. The access code is at the bottom of the screen.
- X1 series (X1C, X1E): Settings > Network. Enable LAN Only Mode (and Developer Mode where offered) to see the code.
- A1 and A1 mini: In the Bambu Handy app, connect to the printer and open its Settings > Network page.
Menu names vary by model and firmware. The access code is not your Bambu account password. If a LAN or Developer Mode setting is missing, check the instructions for your exact model and firmware.
Set the model
Set BAMBU_MODEL to one of p1s, p1p, x1c, x1e, a1, a1mini, or h2d. Print tools refuse to guess: without a model they ask through MCP elicitation when the client supports it, or return an error. G-code for the wrong model can crash the bed into the nozzle.
How the server talks to Bambu printers
- MQTT (port 8883, TLS): Commands and status reports use the printer's own MQTT broker with username
bblpand the access code. Status requests a full report (pushall) and returns temperatures, job state, progress, layers, time remaining, and raw AMS data. The implementation follows community documentation such as OpenBambuAPI. - FTPS (port 990, implicit TLS): Uploads use
basic-ftpdirectly with TLS 1.2 on both channels and TLS session reuse, which the printer requires. This addresses the "Premature close" failures in #22 and avoids the double-path upload bug inbambu-js. File listing still usesbambu-jshelpers. The printer uses a self-signed certificate, so the connection assumes a trusted local network. - Printing:
print_3mfuploads a sliced.3mftocache/and sends aproject_filecommand with the plate's G-code path, MD5 and AMS mapping (ams_slotsorams_mapping), under a uniquechecked-…name that the printer reports back.start_printstarts a plain.gcodefile already on the printer. - Pause, resume and stop:
pause_print,resume_printandcancel_printsendpause,resumeandstopover the connection that printed. Resume only continues the job this server started, after re-reading its bytes over FTPS. - Authorization firmware without Developer Mode: direct LAN commands are refused (HMS 0500-0500-0001-0007). Set
BAMBU_DEFAULT_CONNECTION_MODE=bambu_networkandBAMBU_DEFAULT_CONNECTION_TYPE=lanto print and control through FULU's authenticated BambuNetwork session instead; see the FULU guide. Status and the safety checks still read the printer's LAN MQTT reports.
Prusa (PrusaLink and Prusa Connect)
- PrusaLink (local): Set
PRINTER_TYPE=prusa,PRINTER_HOSTto the printer's address, andAPI_KEYto the PrusaLink API key. Local hosts use HTTP unless you give anhttps://host or port 443. - Prusa Connect (cloud): Set
PRINTER_HOST=connect.prusa3d.comwithPRINTER_PORT=443, orPRINTER_HOST=https://connect.prusa3d.com. The server normalizes both forms to HTTPS. Cloud access through this adapter has not been confirmed on hardware; see #9.
The adapter sends the key as both X-Api-Key and Authorization: Bearer. If a PrusaLink install only accepts username/password (HTTP digest) authentication, this adapter cannot authenticate yet. Requests try newer routes first and fall back on 404, 405, or 501: status tries /api/v1/status, then /api/v1/printer, then /api/printer (the PrusaLink 0.8.1 fix for #11); files try /api/v1/storage, then /api/files, then /api/files/local. start_print of a file already on the printer is refused because no verified download route exists; upload the local G-code with print: true.
Creality
Set PRINTER_TYPE=creality, PRINTER_HOST, PRINTER_PORT if not 80, and API_KEY to a bearer token. The adapter calls /api/device/status, /api/storage/list, /api/storage/info, /api/storage/upload, /api/job/start, /api/job/cancel, and /api/printer/temperature over HTTP with Authorization: Bearer <API_KEY>. None of these routes has been verified against Creality Cloud or stock Creality firmware. start_print of a file already on the printer is refused; upload the local G-code with print: true.
If your Creality printer runs Klipper with Moonraker reachable on the network, as with the K1 Max in #16, use PRINTER_TYPE=klipper instead. With PRINTER_TYPE=creality, the progress, camera, filament, pause and resume tools probe Moonraker on port 7125, then 4408, and use it when it answers.
Progress, camera, pause and resume
counterpart_printer_info, counterpart_print_progress, camera_snapshot, get_printer_filaments, pause_print, and resume_print follow the counterpart/1 contract that bambu-printer-mcp also implements, so one client can treat both servers alike. Each declares an outputSchema and returns structuredContent plus the same JSON as text. A tool error carries text only, because MCP clients validate any structured content against the output schema.
| Firmware | Progress requests | State and job | Camera | Pause / resume | Checked against |
|---|---|---|---|---|---|
| OctoPrint | GET /api/job, GET /api/printer (temperatures; 409 when not operational). After a job ends, the file's prints.last | Operational is idle, or finished at 100%, or cancelled when the selected file's last print failed with no progress (OctoPrint records a cancel that way). Printing, Paused, Error, Offline and the transitional states (job.stage) map directly. completion is 0 to 100; times are seconds | plugins.classicwebcam.snapshot (1.9+), webcam.snapshotUrl (1.8, and 1.9+ compatibility), /webcam/?action=snapshot | POST /api/job {"command":"pause","action":"pause"|"resume"} | OctoPrint 1.11.2 and 1.8.7 virtual printers; job API |
| Klipper (Moonraker) | GET /printer/objects/query?print_stats&virtual_sdcard&display_status&extruder&heater_bed&webhooks, plus estimated_time from /server/files/metadata | print_stats.state; webhooks.state other than ready is busy. Progress is display_status.progress (else virtual_sdcard.progress) ×100; remaining is the slicer estimate minus print_duration; layers come from SET_PRINT_STATS_INFO | First enabled webcam's snapshot_url from /server/webcams/list | POST /printer/print/pause and /resume, waiting up to 60 s for the macro | Klipper and Moonraker 1.5.0 on simulavr (mainsail-crew virtual-klipper-printer), with Spoolman 0.27.1; Moonraker printer API, Klipper status reference |
| PrusaLink | GET /api/v1/status, and GET /api/v1/job for the long file name. Older firmware: the OctoPrint-compatible /api/job | PRINTING, PAUSED, FINISHED, STOPPED (cancelled), ERROR (failed), ATTENTION (paused, with a problem), IDLE/READY, BUSY | None | PUT /api/v1/job/<id>/pause and /resume | PrusaLink OpenAPI only |
| Duet (RRF 3) | GET /rr_model?key=state&flags=d99v and key=job (standalone and DSF 3.5+), else GET /machine/status | state.status; an idle board's job.lastFileName with lastFileCancelled/lastFileAborted gives cancelled, failed or finished. Progress is filePosition / file.size; remaining is timesLeft.slicer, else .file. The verbose v flag is required: f drops job.file | None | M25 and M24 through rr_gcode, else POST /machine/code | RRF HTTP requests, object model only |
| Repetier-Server | GET /printer/api/<slug>?a=listPrinter | online, job and paused; progress is done; elapsed is printedTimeComp, remaining printTime - printedTimeComp. These fields and @pause are returned or accepted by current servers but are not in the API manual | getPrinterConfig webcam staticUrl | send {"cmd":"@pause"}; continueJob | Docs only |
| Creality | Moonraker on port 7125, then 4408, when it answers; otherwise /api/device/status | As Klipper, or the device status's recognized state words | As Klipper, or none | Only through Moonraker | Not verified; the ports come from community K1 setups, not Creality documentation |
| Bambu Lab | The MQTT report (get_printer_status raw) | gcode_state; a user cancel (print_error 0300400C) is cancelled; HMS codes as AAAA_BBBB_CCCC_DDDD | None here; use bambu-printer-mcp | MQTT {"print":{"command":"pause"|"resume"}}, or FULU's BambuNetwork session (the connection that printed) | Offline mapping tests; in-process MQTT/FTPS fakes and a fake FULU host |
OctoPrint, Klipper and Duet (DSF) re-download the job for resume_print and compare its SHA-256 with the inspected bytes, then resume without a prompt. PrusaLink, Repetier and Creality match it by name only, so a person confirms through MCP elicitation first. A standalone Duet board has no download route here, so its resume is refused.
Bambu resume_print re-reads the uploaded 3MF over FTPS (else the plate G-code a P1S extracts from it) and compares its SHA-256; a file this server uploaded that cannot be re-read refuses the resume, and only a file FULU's plug-in placed asks a person.
get_printer_filaments on Bambu reports the AMS as bambu-printer-mcp does (trays, summary, recommended, load_filaments_all) plus a slot for every loaded tray and the external spool; profiles resolve by each tray's tray_info_idx in the slicer's BBL/filament folder. On other printers it reports one external slot. The slot also carries profile: the absolute path of the installed slicer filament profile for its material, which slice_stl takes as load_filaments. It is resolved in the slicer slice_stl uses by default (SLICER_TYPE, SLICER_PATH), for the printer in SLICER_PROFILE. OrcaSlicer searches its bundled profiles/<vendor>/filament, the OrcaSlicer/system copies and the user's OrcaSlicer/user/<id>/filament presets, reading filament_type and compatible_printers through inherits. PrusaSlicer and Slic3r search the data folder's filament/*.ini presets. The material must equal the profile's filament_type exactly (case aside): a PLA spool never gets a PLA-CF profile. Profiles that list other printers are skipped; one that lists this printer comes first, then profiles for every printer, user presets before bundled ones. With no reported material, profile is the filament slice_stl loads by default (FILAMENT_PROFILE), when that file exists. When nothing resolves, profile is omitted. On Moonraker it reads the active spool with GET /server/spoolman/spool_id and POST /server/spoolman/proxy (/v1/spool/<id>); without Spoolman, and on every other firmware, the slot's source is unknown.
Blender MCP (optional)
Blender MCP lets your agent model or refit parts in Blender, for example refitting a downloaded model before slicing, and export a verified STL. Printer tools work without it. The Blender guide covers setup, units, and a worked phone-case example.
- Install Blender, uv, and the mcp-for-blender Blender addon (
uvx mcp-for-blender install-addon, or install itsaddon.pyin Blender's add-on preferences). Enable the addon and keep Blender open: the addon does not run in background mode. - Set
BLENDER_MCP_COMMANDto the full path ofuvxandBLENDER_MCP_ARGSto["mcp-for-blender"]. The project was formerly published asblender-mcp;uvx blender-mcpstill works as a compatibility wrapper. - Ask your agent to call
blender_mcp_statuswith{"connect": true}. That initializes the MCP server and lists its tools by name; it does not prove the addon inside Blender is connected. Aget_scene_infocall throughblender_mcp_callchecks the addon.
Blender and this server must see the same local file paths. See the Blender tool reference for blender_mcp_status, blender_mcp_call, blender_mcp_export_stl, and blender_mcp_edit_model.
Running with Docker
The repository includes a Dockerfile (Node.js 24 on Alpine, running as a non-root user) and a docker-compose.yml.
- Copy
.env.exampleto.envand fill in your backend's values. - Build the image:
docker build -t mcp-3d-printer-server .- Run it over stdio for an MCP client, with host networking so it can reach printers on your LAN:
docker run --rm -i --network host --env-file .env mcp-3d-printer-serverDocker's --env-file reads each value literally, including any trailing # comment. Remove inline comments (such as the one after SLICER_TYPE in .env.example) or pass values with -e instead. Host networking behaves this way on Linux; on Docker Desktop, check that the container can reach your printer's address.
To serve streamable HTTP instead, add -e MCP_TRANSPORT=streamable-http -e MCP_HTTP_HOST=0.0.0.0 and set MCP_HTTP_ALLOWED_ORIGINS for any browser clients. scripts/docker-run.sh builds and runs the image with the variables from .env.
The image does not include a slicer, and a slicer installed on the host generally cannot run inside the container. To slice in Docker, install a Linux build of your slicer in a derived image and set SLICER_PATH to its path inside the container. Printing files that are already sliced needs no slicer.
Streamable HTTP transport
stdio is the default and what most local MCP clients use. For a long-running service, start the HTTP transport:
MCP_TRANSPORT=streamable-http MCP_HTTP_PORT=3000 npx -y mcp-3d-printer-serverThe endpoint is http://127.0.0.1:3000/mcp by default. The server binds to 127.0.0.1 unless you set MCP_HTTP_HOST. It has no built-in authentication, so keep it on a trusted network and restrict browser origins with MCP_HTTP_ALLOWED_ORIGINS.
Troubleshooting
- The server starts but printer calls fail: check
PRINTER_TYPE,PRINTER_HOST, andPRINTER_PORTfirst. The port defaults to 80 for every backend. - Klipper returns 401 or 403: Moonraker is enforcing authorization. Add the MCP host to
trusted_clients. - Bambu uploads fail with "Premature close": update to 1.2.9 or later, which negotiates the TLS session reuse the printer requires.
- Bambu print tools ask for or reject the model: set
BAMBU_MODELto your exact model. Do not substitute a similar model. - "No confirmation was received within 10 minutes": nobody answered a resume prompt in time, so nothing was sent. Ask again when you are at the printer, or raise
PRINT_CONFIRMATION_TIMEOUT_MS. - "The printer rejected its LAN access code" (Bambu, MQTT "Not authorized" or FTPS 530): the access code changes when LAN mode is toggled and after some resets. Read the current code on the printer under Settings > Network (WLAN) and update
BAMBU_TOKEN. - "Hardware safety confirmation was declined or cancelled": a person or the MCP client declined a resume prompt for a job the printer cannot re-read. Some clients decline prompts automatically, for example in a non-interactive permission mode. Resume the job at the printer instead.
- "Printer nozzle 0 information is unreliable; refresh the nozzle information on the printer" (Bambu): a nozzle with an identity chip reports its stored information as unreliable; refresh it on the printer. Nozzles without a chip, such as stock H2D nozzles (
sn: N/A), pass this check. Stderr records them once per printer; check that their configured diameter and type match the installed hotends. - A resume is refused because the client cannot ask for confirmation: PrusaLink, Repetier and Creality resumes need a person to confirm through MCP elicitation. Use a client that supports it, or resume the job at the printer.
- A print is refused for a missing material: the G-code has no
; filament_typeline. Passmaterial(for examplePLA) to the print or heating tool. - "The printer rejected the print command (HMS 0500-0500-0001-0007)": Bambu firmware 01.08.05 and later only accept third-party LAN control with LAN Only Mode and Developer Mode on (Settings > WLAN on the printer). LAN Only Mode turns off cloud and Bambu Handy remote access while it is on. Or start the uploaded file from Bambu Studio or the printer's screen.
- "Printer nozzle 0 type is unknown or does not match the job": the file was sliced for a different nozzle material than the printer reports (a stock P1S preset assumes stainless steel). Slice again with
nozzle_typeor setBAMBU_NOZZLE_TYPE. print_3mfrejects the bed type: pass thebed_typethe plate was sliced for, or slice again for the plate you have installed.- A Bambu slice stops before the slicer runs: the exact
<model> <nozzle> nozzlepreset is missing from the selected installation, orSLICER_PROFILElists a machine preset. See Bambu-compatible slicing. - A per-call
slicer_pathorbridge_commandis rejected: configure the executable in the server environment instead. See executable settings. - The MCP client lists no tools: restart or reload the client after changing its configuration, and check the client's MCP logs for the server's startup error.