bambu-printer-mcp 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +882 -44
- package/dist/index.js +130 -17
- package/dist/printers/bambu.d.ts +1 -0
- package/dist/printers/bambu.js +1 -1
- package/dist/stl/stl-manipulator.d.ts +2 -1
- package/dist/stl/stl-manipulator.js +9 -1
- package/package.json +2 -4
- package/src/index.ts +155 -19
- package/src/printers/bambu.ts +2 -1
- package/src/stl/stl-manipulator.ts +10 -1
package/dist/index.js
CHANGED
|
@@ -16,6 +16,36 @@ const DEFAULT_HOST = process.env.PRINTER_HOST || "localhost";
|
|
|
16
16
|
const DEFAULT_BAMBU_SERIAL = process.env.BAMBU_SERIAL || "";
|
|
17
17
|
const DEFAULT_BAMBU_TOKEN = process.env.BAMBU_TOKEN || "";
|
|
18
18
|
const TEMP_DIR = process.env.TEMP_DIR || path.join(process.cwd(), "temp");
|
|
19
|
+
// Printer model and bed type
|
|
20
|
+
const DEFAULT_BAMBU_MODEL = process.env.BAMBU_MODEL?.trim().toLowerCase() || "";
|
|
21
|
+
const DEFAULT_BED_TYPE = process.env.BED_TYPE?.trim().toLowerCase() || "textured_plate";
|
|
22
|
+
const DEFAULT_NOZZLE_DIAMETER = process.env.NOZZLE_DIAMETER?.trim() || "0.4";
|
|
23
|
+
const VALID_BAMBU_MODELS = ["p1s", "p1p", "x1c", "x1e", "a1", "a1mini", "h2d"];
|
|
24
|
+
const VALID_BED_TYPES = ["textured_plate", "cool_plate", "engineering_plate", "hot_plate"];
|
|
25
|
+
// Map model IDs to BambuStudio --load-machine preset names
|
|
26
|
+
const BAMBU_MODEL_PRESETS = {
|
|
27
|
+
p1s: (n) => `Bambu Lab P1S ${n} nozzle`,
|
|
28
|
+
p1p: (n) => `Bambu Lab P1P ${n} nozzle`,
|
|
29
|
+
x1c: (n) => `Bambu Lab X1 Carbon ${n} nozzle`,
|
|
30
|
+
x1e: (n) => `Bambu Lab X1E ${n} nozzle`,
|
|
31
|
+
a1: (n) => `Bambu Lab A1 ${n} nozzle`,
|
|
32
|
+
a1mini: (n) => `Bambu Lab A1 mini ${n} nozzle`,
|
|
33
|
+
h2d: (n) => `Bambu Lab H2D ${n} nozzle`,
|
|
34
|
+
};
|
|
35
|
+
function validateBambuModel(model) {
|
|
36
|
+
const normalized = model.trim().toLowerCase();
|
|
37
|
+
if (!VALID_BAMBU_MODELS.includes(normalized)) {
|
|
38
|
+
throw new Error(`Invalid bambu_model: "${model}". Valid models: ${VALID_BAMBU_MODELS.join(", ")}`);
|
|
39
|
+
}
|
|
40
|
+
return normalized;
|
|
41
|
+
}
|
|
42
|
+
function resolveBedType(argsBedType) {
|
|
43
|
+
const bedType = (argsBedType || DEFAULT_BED_TYPE).trim().toLowerCase();
|
|
44
|
+
if (!VALID_BED_TYPES.includes(bedType)) {
|
|
45
|
+
throw new Error(`Invalid bed_type: "${bedType}". Valid types: ${VALID_BED_TYPES.join(", ")}`);
|
|
46
|
+
}
|
|
47
|
+
return bedType;
|
|
48
|
+
}
|
|
19
49
|
// Slicer configuration (defaults to bambustudio)
|
|
20
50
|
const DEFAULT_SLICER_TYPE = process.env.SLICER_TYPE || "bambustudio";
|
|
21
51
|
const DEFAULT_SLICER_PATH = process.env.SLICER_PATH || "/Applications/BambuStudio.app/Contents/MacOS/BambuStudio";
|
|
@@ -95,6 +125,59 @@ class BambuPrinterMCPServer {
|
|
|
95
125
|
this.setupResourceHandlers();
|
|
96
126
|
this.setupToolHandlers();
|
|
97
127
|
}
|
|
128
|
+
/**
|
|
129
|
+
* Resolve the Bambu printer model from args, env, or by asking the user via elicitation.
|
|
130
|
+
* This is critical for safety: the wrong model can cause physical damage to the printer.
|
|
131
|
+
*/
|
|
132
|
+
async resolveBambuModel(argsModel) {
|
|
133
|
+
const fromArgs = (argsModel || DEFAULT_BAMBU_MODEL).trim().toLowerCase();
|
|
134
|
+
if (fromArgs) {
|
|
135
|
+
return validateBambuModel(fromArgs);
|
|
136
|
+
}
|
|
137
|
+
// No model from args or env — ask the user via elicitation
|
|
138
|
+
try {
|
|
139
|
+
const result = await this.server.elicitInput({
|
|
140
|
+
mode: "form",
|
|
141
|
+
message: "Your Bambu Lab printer model is required for safe operation. " +
|
|
142
|
+
"Using the wrong model can cause the bed to crash into the nozzle and damage the printer.",
|
|
143
|
+
requestedSchema: {
|
|
144
|
+
type: "object",
|
|
145
|
+
properties: {
|
|
146
|
+
bambu_model: {
|
|
147
|
+
type: "string",
|
|
148
|
+
title: "Printer Model",
|
|
149
|
+
description: "Which Bambu Lab printer do you have?",
|
|
150
|
+
oneOf: [
|
|
151
|
+
{ const: "p1s", title: "P1S" },
|
|
152
|
+
{ const: "p1p", title: "P1P" },
|
|
153
|
+
{ const: "x1c", title: "X1 Carbon" },
|
|
154
|
+
{ const: "x1e", title: "X1E" },
|
|
155
|
+
{ const: "a1", title: "A1" },
|
|
156
|
+
{ const: "a1mini", title: "A1 Mini" },
|
|
157
|
+
{ const: "h2d", title: "H2D" },
|
|
158
|
+
],
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
required: ["bambu_model"],
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
if (result.action === "accept" && result.content?.bambu_model) {
|
|
165
|
+
return validateBambuModel(String(result.content.bambu_model));
|
|
166
|
+
}
|
|
167
|
+
throw new Error("Printer model selection was cancelled. Cannot proceed without knowing the printer model.");
|
|
168
|
+
}
|
|
169
|
+
catch (elicitError) {
|
|
170
|
+
// Elicitation not supported by this client — fall back to a clear error
|
|
171
|
+
const msg = elicitError?.message || String(elicitError);
|
|
172
|
+
if (elicitError?.code === -32601 || elicitError?.code === -32600 ||
|
|
173
|
+
msg.includes("does not support") || msg.includes("elicitation")) {
|
|
174
|
+
throw new Error("bambu_model is required but your MCP client does not support elicitation. " +
|
|
175
|
+
`Set the BAMBU_MODEL environment variable or pass bambu_model in the tool call. ` +
|
|
176
|
+
`Valid models: ${VALID_BAMBU_MODELS.join(", ")}`);
|
|
177
|
+
}
|
|
178
|
+
throw elicitError;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
98
181
|
setupResourceHandlers() {
|
|
99
182
|
this.server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
100
183
|
return {
|
|
@@ -234,19 +317,25 @@ class BambuPrinterMCPServer {
|
|
|
234
317
|
},
|
|
235
318
|
{
|
|
236
319
|
name: "slice_stl",
|
|
237
|
-
description: "Slice an STL or 3MF file using a slicer to generate printable G-code or sliced 3MF",
|
|
320
|
+
description: "Slice an STL or 3MF file using a slicer to generate printable G-code or sliced 3MF. IMPORTANT: bambu_model must be specified to ensure the slicer generates safe G-code for the correct printer.",
|
|
238
321
|
inputSchema: {
|
|
239
322
|
type: "object",
|
|
240
323
|
properties: {
|
|
241
324
|
stl_path: { type: "string", description: "Path to the STL or 3MF file to slice" },
|
|
325
|
+
bambu_model: {
|
|
326
|
+
type: "string",
|
|
327
|
+
enum: ["p1s", "p1p", "x1c", "x1e", "a1", "a1mini", "h2d"],
|
|
328
|
+
description: "REQUIRED: Bambu Lab printer model. Ask the user if not known. Using the wrong model can damage the printer."
|
|
329
|
+
},
|
|
242
330
|
slicer_type: {
|
|
243
331
|
type: "string",
|
|
244
332
|
description: "Type of slicer to use (bambustudio, prusaslicer, cura, slic3r, orcaslicer) (default: bambustudio)"
|
|
245
333
|
},
|
|
246
334
|
slicer_path: { type: "string", description: "Path to the slicer executable (default: value from env)" },
|
|
247
|
-
slicer_profile: { type: "string", description: "Path to the slicer profile/config file (optional)" }
|
|
335
|
+
slicer_profile: { type: "string", description: "Path to the slicer profile/config file (optional, overrides bambu_model preset)" },
|
|
336
|
+
nozzle_diameter: { type: "string", description: "Nozzle diameter in mm (default: 0.4)" }
|
|
248
337
|
},
|
|
249
|
-
required: ["stl_path"]
|
|
338
|
+
required: ["stl_path", "bambu_model"]
|
|
250
339
|
}
|
|
251
340
|
},
|
|
252
341
|
{
|
|
@@ -335,25 +424,37 @@ class BambuPrinterMCPServer {
|
|
|
335
424
|
},
|
|
336
425
|
{
|
|
337
426
|
name: "print_3mf",
|
|
338
|
-
description: "Print a 3MF file on a Bambu Lab printer. Auto-slices if the 3MF has no gcode.",
|
|
427
|
+
description: "Print a 3MF file on a Bambu Lab printer. Auto-slices if the 3MF has no gcode. IMPORTANT: bambu_model must be specified to ensure safe printer operation.",
|
|
339
428
|
inputSchema: {
|
|
340
429
|
type: "object",
|
|
341
430
|
properties: {
|
|
342
431
|
three_mf_path: { type: "string", description: "Path to the 3MF file to print" },
|
|
432
|
+
bambu_model: {
|
|
433
|
+
type: "string",
|
|
434
|
+
enum: ["p1s", "p1p", "x1c", "x1e", "a1", "a1mini", "h2d"],
|
|
435
|
+
description: "REQUIRED: Bambu Lab printer model. Ask the user if not known. Using the wrong model can damage the printer."
|
|
436
|
+
},
|
|
437
|
+
bed_type: {
|
|
438
|
+
type: "string",
|
|
439
|
+
enum: ["textured_plate", "cool_plate", "engineering_plate", "hot_plate"],
|
|
440
|
+
description: "Bed plate type currently installed (default: textured_plate)"
|
|
441
|
+
},
|
|
343
442
|
host: { type: "string", description: "Hostname or IP of the printer (default: value from env)" },
|
|
344
443
|
bambu_serial: { type: "string", description: "Serial number (default: value from env)" },
|
|
345
444
|
bambu_token: { type: "string", description: "Access token (default: value from env)" },
|
|
346
|
-
|
|
347
|
-
nozzle_temperature: { type: "number", description: "Override nozzle temperature (°C)" },
|
|
348
|
-
bed_temperature: { type: "number", description: "Override bed temperature (°C)" },
|
|
349
|
-
support_enabled: { type: "boolean", description: "Override support generation" },
|
|
445
|
+
use_ams: { type: "boolean", description: "Whether to use the AMS (default: auto-detect from 3MF)" },
|
|
350
446
|
ams_mapping: {
|
|
351
|
-
type: "
|
|
352
|
-
description: "
|
|
353
|
-
|
|
354
|
-
}
|
|
447
|
+
type: "array",
|
|
448
|
+
description: "AMS slot mapping array, e.g. [0, 2] maps filaments to AMS slots 0 and 2",
|
|
449
|
+
items: { type: "number" }
|
|
450
|
+
},
|
|
451
|
+
bed_leveling: { type: "boolean", description: "Enable auto bed leveling (default: true)" },
|
|
452
|
+
flow_calibration: { type: "boolean", description: "Enable flow calibration (default: true)" },
|
|
453
|
+
vibration_calibration: { type: "boolean", description: "Enable vibration calibration (default: true)" },
|
|
454
|
+
timelapse: { type: "boolean", description: "Enable timelapse recording (default: false)" },
|
|
455
|
+
nozzle_diameter: { type: "string", description: "Nozzle diameter in mm for auto-slicing (default: 0.4)" }
|
|
355
456
|
},
|
|
356
|
-
required: ["three_mf_path"]
|
|
457
|
+
required: ["three_mf_path", "bambu_model"]
|
|
357
458
|
}
|
|
358
459
|
},
|
|
359
460
|
{
|
|
@@ -490,12 +591,18 @@ class BambuPrinterMCPServer {
|
|
|
490
591
|
}
|
|
491
592
|
result = await this.stlManipulator.getSTLInfo(String(args.stl_path));
|
|
492
593
|
break;
|
|
493
|
-
case "slice_stl":
|
|
594
|
+
case "slice_stl": {
|
|
494
595
|
if (!args?.stl_path) {
|
|
495
596
|
throw new Error("Missing required parameter: stl_path");
|
|
496
597
|
}
|
|
497
|
-
|
|
598
|
+
const sliceModel = await this.resolveBambuModel(args?.bambu_model);
|
|
599
|
+
const nozzleDiam = String(args?.nozzle_diameter || DEFAULT_NOZZLE_DIAMETER);
|
|
600
|
+
// Resolve printer preset for BambuStudio slicer
|
|
601
|
+
const printerPreset = BAMBU_MODEL_PRESETS[sliceModel]?.(nozzleDiam);
|
|
602
|
+
result = await this.stlManipulator.sliceSTL(String(args.stl_path), slicerType, slicerPath, slicerProfile || undefined, undefined, // progressCallback
|
|
603
|
+
printerPreset);
|
|
498
604
|
break;
|
|
605
|
+
}
|
|
499
606
|
case "print_3mf": {
|
|
500
607
|
if (!args?.three_mf_path) {
|
|
501
608
|
throw new Error("Missing required parameter: three_mf_path");
|
|
@@ -503,6 +610,10 @@ class BambuPrinterMCPServer {
|
|
|
503
610
|
if (!bambuSerial || !bambuToken) {
|
|
504
611
|
throw new Error("Bambu serial number and access token are required for print_3mf.");
|
|
505
612
|
}
|
|
613
|
+
const printModel = await this.resolveBambuModel(args?.bambu_model);
|
|
614
|
+
const printBedType = resolveBedType(args?.bed_type);
|
|
615
|
+
const printNozzle = String(args?.nozzle_diameter || DEFAULT_NOZZLE_DIAMETER);
|
|
616
|
+
const printPreset = BAMBU_MODEL_PRESETS[printModel]?.(printNozzle);
|
|
506
617
|
let threeMFPath = String(args.three_mf_path);
|
|
507
618
|
// Auto-slice if 3MF has no gcode
|
|
508
619
|
try {
|
|
@@ -511,8 +622,9 @@ class BambuPrinterMCPServer {
|
|
|
511
622
|
const zip = await JSZip.loadAsync(zipData);
|
|
512
623
|
const hasGcode = Object.keys(zip.files).some(f => f.match(/Metadata\/plate_\d+\.gcode/i) || f.endsWith('.gcode'));
|
|
513
624
|
if (!hasGcode) {
|
|
514
|
-
console.log(
|
|
515
|
-
threeMFPath = await this.stlManipulator.sliceSTL(threeMFPath, slicerType, slicerPath, slicerProfile || undefined
|
|
625
|
+
console.log(`3MF has no gcode — auto-slicing with ${slicerType} for ${printModel}`);
|
|
626
|
+
threeMFPath = await this.stlManipulator.sliceSTL(threeMFPath, slicerType, slicerPath, slicerProfile || undefined, undefined, // progressCallback
|
|
627
|
+
printPreset);
|
|
516
628
|
console.log("Auto-sliced to: " + threeMFPath);
|
|
517
629
|
}
|
|
518
630
|
}
|
|
@@ -560,6 +672,7 @@ class BambuPrinterMCPServer {
|
|
|
560
672
|
plateIndex: 0,
|
|
561
673
|
useAMS: useAMS,
|
|
562
674
|
amsMapping: finalAmsMapping,
|
|
675
|
+
bedType: printBedType,
|
|
563
676
|
bedLeveling: args?.bed_leveling !== undefined ? Boolean(args.bed_leveling) : undefined,
|
|
564
677
|
flowCalibration: args?.flow_calibration !== undefined ? Boolean(args.flow_calibration) : undefined,
|
|
565
678
|
vibrationCalibration: args?.vibration_calibration !== undefined ? Boolean(args.vibration_calibration) : undefined,
|
package/dist/printers/bambu.d.ts
CHANGED
package/dist/printers/bambu.js
CHANGED
|
@@ -177,7 +177,7 @@ export class BambuImplementation {
|
|
|
177
177
|
layer_inspect: options.layerInspect ?? true,
|
|
178
178
|
vibration_cali: options.vibrationCalibration ?? true,
|
|
179
179
|
bed_leveling: options.bedLeveling ?? true,
|
|
180
|
-
bed_type: "textured_plate",
|
|
180
|
+
bed_type: options.bedType || "textured_plate",
|
|
181
181
|
timelapse: options.timelapse ?? false,
|
|
182
182
|
use_ams: options.useAMS !== false,
|
|
183
183
|
ams_mapping: amsMapping,
|
|
@@ -96,9 +96,10 @@ export declare class STLManipulator extends EventEmitter {
|
|
|
96
96
|
* @param slicerPath Path to the slicer executable
|
|
97
97
|
* @param slicerProfile Optional path to the slicer profile/config file
|
|
98
98
|
* @param progressCallback Optional callback for progress updates
|
|
99
|
+
* @param printerPreset Optional BambuStudio printer preset name (e.g., "Bambu Lab P1S 0.4 nozzle")
|
|
99
100
|
* @returns Path to the generated G-code or sliced 3MF file
|
|
100
101
|
*/
|
|
101
|
-
sliceSTL(stlFilePath: string, slicerType: 'prusaslicer' | 'cura' | 'slic3r' | 'orcaslicer' | 'bambustudio', slicerPath: string, slicerProfile?: string, progressCallback?: ProgressCallback): Promise<string>;
|
|
102
|
+
sliceSTL(stlFilePath: string, slicerType: 'prusaslicer' | 'cura' | 'slic3r' | 'orcaslicer' | 'bambustudio', slicerPath: string, slicerProfile?: string, progressCallback?: ProgressCallback, printerPreset?: string): Promise<string>;
|
|
102
103
|
/**
|
|
103
104
|
* Enhanced version of confirmTemperatures with better error handling
|
|
104
105
|
* @param gcodePath Path to the G-code file
|
|
@@ -650,9 +650,10 @@ export class STLManipulator extends EventEmitter {
|
|
|
650
650
|
* @param slicerPath Path to the slicer executable
|
|
651
651
|
* @param slicerProfile Optional path to the slicer profile/config file
|
|
652
652
|
* @param progressCallback Optional callback for progress updates
|
|
653
|
+
* @param printerPreset Optional BambuStudio printer preset name (e.g., "Bambu Lab P1S 0.4 nozzle")
|
|
653
654
|
* @returns Path to the generated G-code or sliced 3MF file
|
|
654
655
|
*/
|
|
655
|
-
async sliceSTL(stlFilePath, slicerType, slicerPath, slicerProfile, progressCallback) {
|
|
656
|
+
async sliceSTL(stlFilePath, slicerType, slicerPath, slicerProfile, progressCallback, printerPreset) {
|
|
656
657
|
const operationId = this.generateOperationId();
|
|
657
658
|
this.activeOperations.set(operationId, true);
|
|
658
659
|
if (!fs.existsSync(slicerPath)) {
|
|
@@ -727,6 +728,13 @@ export class STLManipulator extends EventEmitter {
|
|
|
727
728
|
if (slicerProfile) {
|
|
728
729
|
args.push('--load-settings', slicerProfile);
|
|
729
730
|
}
|
|
731
|
+
// CRITICAL: Pass printer preset so slicer generates correct G-code
|
|
732
|
+
// for the target printer model. Without this, the slicer uses its
|
|
733
|
+
// default which may be a completely different printer, causing
|
|
734
|
+
// dangerous bed crashes or axis limit violations.
|
|
735
|
+
if (printerPreset && !slicerProfile) {
|
|
736
|
+
args.push('--load-machine', printerPreset);
|
|
737
|
+
}
|
|
730
738
|
args.push(stlFilePath);
|
|
731
739
|
// Override outputFilePath for bambustudio since it produces 3MF, not gcode
|
|
732
740
|
outputFilePath = bambuOutputPath;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bambu-printer-mcp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "MCP server for Bambu Lab 3D printers with STL manipulation, slicing, and print management",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"repository": "https://github.com/DMontgomery40/bambu-printer-mcp",
|
|
@@ -40,11 +40,9 @@
|
|
|
40
40
|
"dotenv": "^16.3.1",
|
|
41
41
|
"jszip": "^3.10.1",
|
|
42
42
|
"three": "^0.173.0",
|
|
43
|
-
"xml2js": "^0.6.2"
|
|
44
|
-
"zod": "^3.25.76"
|
|
43
|
+
"xml2js": "^0.6.2"
|
|
45
44
|
},
|
|
46
45
|
"devDependencies": {
|
|
47
|
-
"@types/jszip": "^3.4.0",
|
|
48
46
|
"@types/node": "^18.0.0",
|
|
49
47
|
"@types/three": "^0.173.0",
|
|
50
48
|
"@types/xml2js": "^0.4.14"
|
package/src/index.ts
CHANGED
|
@@ -16,9 +16,8 @@ import fs from "fs";
|
|
|
16
16
|
import path from "path";
|
|
17
17
|
import { createServer as createHttpServer, type IncomingMessage, type Server as HttpServer, type ServerResponse } from "node:http";
|
|
18
18
|
import { randomUUID } from "node:crypto";
|
|
19
|
-
import * as THREE from 'three';
|
|
20
19
|
import { STLManipulator } from "./stl/stl-manipulator.js";
|
|
21
|
-
import { parse3MF
|
|
20
|
+
import { parse3MF } from './3mf_parser.js';
|
|
22
21
|
import { BambuImplementation } from "./printers/bambu.js";
|
|
23
22
|
|
|
24
23
|
dotenv.config();
|
|
@@ -28,6 +27,47 @@ const DEFAULT_BAMBU_SERIAL = process.env.BAMBU_SERIAL || "";
|
|
|
28
27
|
const DEFAULT_BAMBU_TOKEN = process.env.BAMBU_TOKEN || "";
|
|
29
28
|
const TEMP_DIR = process.env.TEMP_DIR || path.join(process.cwd(), "temp");
|
|
30
29
|
|
|
30
|
+
// Printer model and bed type
|
|
31
|
+
const DEFAULT_BAMBU_MODEL = process.env.BAMBU_MODEL?.trim().toLowerCase() || "";
|
|
32
|
+
const DEFAULT_BED_TYPE = process.env.BED_TYPE?.trim().toLowerCase() || "textured_plate";
|
|
33
|
+
const DEFAULT_NOZZLE_DIAMETER = process.env.NOZZLE_DIAMETER?.trim() || "0.4";
|
|
34
|
+
|
|
35
|
+
const VALID_BAMBU_MODELS = ["p1s", "p1p", "x1c", "x1e", "a1", "a1mini", "h2d"] as const;
|
|
36
|
+
type BambuModel = typeof VALID_BAMBU_MODELS[number];
|
|
37
|
+
|
|
38
|
+
const VALID_BED_TYPES = ["textured_plate", "cool_plate", "engineering_plate", "hot_plate"] as const;
|
|
39
|
+
|
|
40
|
+
// Map model IDs to BambuStudio --load-machine preset names
|
|
41
|
+
const BAMBU_MODEL_PRESETS: Record<string, (nozzle: string) => string> = {
|
|
42
|
+
p1s: (n) => `Bambu Lab P1S ${n} nozzle`,
|
|
43
|
+
p1p: (n) => `Bambu Lab P1P ${n} nozzle`,
|
|
44
|
+
x1c: (n) => `Bambu Lab X1 Carbon ${n} nozzle`,
|
|
45
|
+
x1e: (n) => `Bambu Lab X1E ${n} nozzle`,
|
|
46
|
+
a1: (n) => `Bambu Lab A1 ${n} nozzle`,
|
|
47
|
+
a1mini: (n) => `Bambu Lab A1 mini ${n} nozzle`,
|
|
48
|
+
h2d: (n) => `Bambu Lab H2D ${n} nozzle`,
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
function validateBambuModel(model: string): string {
|
|
52
|
+
const normalized = model.trim().toLowerCase();
|
|
53
|
+
if (!VALID_BAMBU_MODELS.includes(normalized as BambuModel)) {
|
|
54
|
+
throw new Error(
|
|
55
|
+
`Invalid bambu_model: "${model}". Valid models: ${VALID_BAMBU_MODELS.join(", ")}`
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
return normalized;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function resolveBedType(argsBedType: string | undefined): string {
|
|
62
|
+
const bedType = (argsBedType || DEFAULT_BED_TYPE).trim().toLowerCase();
|
|
63
|
+
if (!(VALID_BED_TYPES as readonly string[]).includes(bedType)) {
|
|
64
|
+
throw new Error(
|
|
65
|
+
`Invalid bed_type: "${bedType}". Valid types: ${VALID_BED_TYPES.join(", ")}`
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
return bedType;
|
|
69
|
+
}
|
|
70
|
+
|
|
31
71
|
// Slicer configuration (defaults to bambustudio)
|
|
32
72
|
const DEFAULT_SLICER_TYPE = process.env.SLICER_TYPE || "bambustudio";
|
|
33
73
|
const DEFAULT_SLICER_PATH = process.env.SLICER_PATH || "/Applications/BambuStudio.app/Contents/MacOS/BambuStudio";
|
|
@@ -143,6 +183,69 @@ class BambuPrinterMCPServer {
|
|
|
143
183
|
this.setupToolHandlers();
|
|
144
184
|
}
|
|
145
185
|
|
|
186
|
+
/**
|
|
187
|
+
* Resolve the Bambu printer model from args, env, or by asking the user via elicitation.
|
|
188
|
+
* This is critical for safety: the wrong model can cause physical damage to the printer.
|
|
189
|
+
*/
|
|
190
|
+
private async resolveBambuModel(argsModel: string | undefined): Promise<string> {
|
|
191
|
+
const fromArgs = (argsModel || DEFAULT_BAMBU_MODEL).trim().toLowerCase();
|
|
192
|
+
if (fromArgs) {
|
|
193
|
+
return validateBambuModel(fromArgs);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// No model from args or env — ask the user via elicitation
|
|
197
|
+
try {
|
|
198
|
+
const result = await this.server.elicitInput({
|
|
199
|
+
mode: "form" as const,
|
|
200
|
+
message:
|
|
201
|
+
"Your Bambu Lab printer model is required for safe operation. " +
|
|
202
|
+
"Using the wrong model can cause the bed to crash into the nozzle and damage the printer.",
|
|
203
|
+
requestedSchema: {
|
|
204
|
+
type: "object",
|
|
205
|
+
properties: {
|
|
206
|
+
bambu_model: {
|
|
207
|
+
type: "string",
|
|
208
|
+
title: "Printer Model",
|
|
209
|
+
description: "Which Bambu Lab printer do you have?",
|
|
210
|
+
oneOf: [
|
|
211
|
+
{ const: "p1s", title: "P1S" },
|
|
212
|
+
{ const: "p1p", title: "P1P" },
|
|
213
|
+
{ const: "x1c", title: "X1 Carbon" },
|
|
214
|
+
{ const: "x1e", title: "X1E" },
|
|
215
|
+
{ const: "a1", title: "A1" },
|
|
216
|
+
{ const: "a1mini", title: "A1 Mini" },
|
|
217
|
+
{ const: "h2d", title: "H2D" },
|
|
218
|
+
],
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
required: ["bambu_model"],
|
|
222
|
+
},
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
if (result.action === "accept" && result.content?.bambu_model) {
|
|
226
|
+
return validateBambuModel(String(result.content.bambu_model));
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
throw new Error(
|
|
230
|
+
"Printer model selection was cancelled. Cannot proceed without knowing the printer model."
|
|
231
|
+
);
|
|
232
|
+
} catch (elicitError: any) {
|
|
233
|
+
// Elicitation not supported by this client — fall back to a clear error
|
|
234
|
+
const msg = elicitError?.message || String(elicitError);
|
|
235
|
+
if (
|
|
236
|
+
elicitError?.code === -32601 || elicitError?.code === -32600 ||
|
|
237
|
+
msg.includes("does not support") || msg.includes("elicitation")
|
|
238
|
+
) {
|
|
239
|
+
throw new Error(
|
|
240
|
+
"bambu_model is required but your MCP client does not support elicitation. " +
|
|
241
|
+
`Set the BAMBU_MODEL environment variable or pass bambu_model in the tool call. ` +
|
|
242
|
+
`Valid models: ${VALID_BAMBU_MODELS.join(", ")}`
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
throw elicitError;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
146
249
|
setupResourceHandlers() {
|
|
147
250
|
this.server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
148
251
|
return {
|
|
@@ -286,19 +389,25 @@ class BambuPrinterMCPServer {
|
|
|
286
389
|
},
|
|
287
390
|
{
|
|
288
391
|
name: "slice_stl",
|
|
289
|
-
description: "Slice an STL or 3MF file using a slicer to generate printable G-code or sliced 3MF",
|
|
392
|
+
description: "Slice an STL or 3MF file using a slicer to generate printable G-code or sliced 3MF. IMPORTANT: bambu_model must be specified to ensure the slicer generates safe G-code for the correct printer.",
|
|
290
393
|
inputSchema: {
|
|
291
394
|
type: "object",
|
|
292
395
|
properties: {
|
|
293
396
|
stl_path: { type: "string", description: "Path to the STL or 3MF file to slice" },
|
|
397
|
+
bambu_model: {
|
|
398
|
+
type: "string",
|
|
399
|
+
enum: ["p1s", "p1p", "x1c", "x1e", "a1", "a1mini", "h2d"],
|
|
400
|
+
description: "REQUIRED: Bambu Lab printer model. Ask the user if not known. Using the wrong model can damage the printer."
|
|
401
|
+
},
|
|
294
402
|
slicer_type: {
|
|
295
403
|
type: "string",
|
|
296
404
|
description: "Type of slicer to use (bambustudio, prusaslicer, cura, slic3r, orcaslicer) (default: bambustudio)"
|
|
297
405
|
},
|
|
298
406
|
slicer_path: { type: "string", description: "Path to the slicer executable (default: value from env)" },
|
|
299
|
-
slicer_profile: { type: "string", description: "Path to the slicer profile/config file (optional)" }
|
|
407
|
+
slicer_profile: { type: "string", description: "Path to the slicer profile/config file (optional, overrides bambu_model preset)" },
|
|
408
|
+
nozzle_diameter: { type: "string", description: "Nozzle diameter in mm (default: 0.4)" }
|
|
300
409
|
},
|
|
301
|
-
required: ["stl_path"]
|
|
410
|
+
required: ["stl_path", "bambu_model"]
|
|
302
411
|
}
|
|
303
412
|
},
|
|
304
413
|
{
|
|
@@ -387,25 +496,37 @@ class BambuPrinterMCPServer {
|
|
|
387
496
|
},
|
|
388
497
|
{
|
|
389
498
|
name: "print_3mf",
|
|
390
|
-
description: "Print a 3MF file on a Bambu Lab printer. Auto-slices if the 3MF has no gcode.",
|
|
499
|
+
description: "Print a 3MF file on a Bambu Lab printer. Auto-slices if the 3MF has no gcode. IMPORTANT: bambu_model must be specified to ensure safe printer operation.",
|
|
391
500
|
inputSchema: {
|
|
392
501
|
type: "object",
|
|
393
502
|
properties: {
|
|
394
503
|
three_mf_path: { type: "string", description: "Path to the 3MF file to print" },
|
|
504
|
+
bambu_model: {
|
|
505
|
+
type: "string",
|
|
506
|
+
enum: ["p1s", "p1p", "x1c", "x1e", "a1", "a1mini", "h2d"],
|
|
507
|
+
description: "REQUIRED: Bambu Lab printer model. Ask the user if not known. Using the wrong model can damage the printer."
|
|
508
|
+
},
|
|
509
|
+
bed_type: {
|
|
510
|
+
type: "string",
|
|
511
|
+
enum: ["textured_plate", "cool_plate", "engineering_plate", "hot_plate"],
|
|
512
|
+
description: "Bed plate type currently installed (default: textured_plate)"
|
|
513
|
+
},
|
|
395
514
|
host: { type: "string", description: "Hostname or IP of the printer (default: value from env)" },
|
|
396
515
|
bambu_serial: { type: "string", description: "Serial number (default: value from env)" },
|
|
397
516
|
bambu_token: { type: "string", description: "Access token (default: value from env)" },
|
|
398
|
-
|
|
399
|
-
nozzle_temperature: { type: "number", description: "Override nozzle temperature (°C)" },
|
|
400
|
-
bed_temperature: { type: "number", description: "Override bed temperature (°C)" },
|
|
401
|
-
support_enabled: { type: "boolean", description: "Override support generation" },
|
|
517
|
+
use_ams: { type: "boolean", description: "Whether to use the AMS (default: auto-detect from 3MF)" },
|
|
402
518
|
ams_mapping: {
|
|
403
|
-
type: "
|
|
404
|
-
description: "
|
|
405
|
-
|
|
406
|
-
}
|
|
519
|
+
type: "array",
|
|
520
|
+
description: "AMS slot mapping array, e.g. [0, 2] maps filaments to AMS slots 0 and 2",
|
|
521
|
+
items: { type: "number" }
|
|
522
|
+
},
|
|
523
|
+
bed_leveling: { type: "boolean", description: "Enable auto bed leveling (default: true)" },
|
|
524
|
+
flow_calibration: { type: "boolean", description: "Enable flow calibration (default: true)" },
|
|
525
|
+
vibration_calibration: { type: "boolean", description: "Enable vibration calibration (default: true)" },
|
|
526
|
+
timelapse: { type: "boolean", description: "Enable timelapse recording (default: false)" },
|
|
527
|
+
nozzle_diameter: { type: "string", description: "Nozzle diameter in mm for auto-slicing (default: 0.4)" }
|
|
407
528
|
},
|
|
408
|
-
required: ["three_mf_path"]
|
|
529
|
+
required: ["three_mf_path", "bambu_model"]
|
|
409
530
|
}
|
|
410
531
|
},
|
|
411
532
|
{
|
|
@@ -574,15 +695,22 @@ class BambuPrinterMCPServer {
|
|
|
574
695
|
result = await this.stlManipulator.getSTLInfo(String(args.stl_path));
|
|
575
696
|
break;
|
|
576
697
|
|
|
577
|
-
case "slice_stl":
|
|
698
|
+
case "slice_stl": {
|
|
578
699
|
if (!args?.stl_path) {
|
|
579
700
|
throw new Error("Missing required parameter: stl_path");
|
|
580
701
|
}
|
|
702
|
+
const sliceModel = await this.resolveBambuModel(args?.bambu_model as string | undefined);
|
|
703
|
+
const nozzleDiam = String(args?.nozzle_diameter || DEFAULT_NOZZLE_DIAMETER);
|
|
704
|
+
// Resolve printer preset for BambuStudio slicer
|
|
705
|
+
const printerPreset = BAMBU_MODEL_PRESETS[sliceModel]?.(nozzleDiam);
|
|
581
706
|
result = await this.stlManipulator.sliceSTL(
|
|
582
707
|
String(args.stl_path), slicerType, slicerPath,
|
|
583
|
-
slicerProfile || undefined
|
|
708
|
+
slicerProfile || undefined,
|
|
709
|
+
undefined, // progressCallback
|
|
710
|
+
printerPreset
|
|
584
711
|
);
|
|
585
712
|
break;
|
|
713
|
+
}
|
|
586
714
|
|
|
587
715
|
case "print_3mf": {
|
|
588
716
|
if (!args?.three_mf_path) {
|
|
@@ -592,6 +720,11 @@ class BambuPrinterMCPServer {
|
|
|
592
720
|
throw new Error("Bambu serial number and access token are required for print_3mf.");
|
|
593
721
|
}
|
|
594
722
|
|
|
723
|
+
const printModel = await this.resolveBambuModel(args?.bambu_model as string | undefined);
|
|
724
|
+
const printBedType = resolveBedType(args?.bed_type as string | undefined);
|
|
725
|
+
const printNozzle = String(args?.nozzle_diameter || DEFAULT_NOZZLE_DIAMETER);
|
|
726
|
+
const printPreset = BAMBU_MODEL_PRESETS[printModel]?.(printNozzle);
|
|
727
|
+
|
|
595
728
|
let threeMFPath = String(args.three_mf_path);
|
|
596
729
|
|
|
597
730
|
// Auto-slice if 3MF has no gcode
|
|
@@ -603,9 +736,11 @@ class BambuPrinterMCPServer {
|
|
|
603
736
|
f => f.match(/Metadata\/plate_\d+\.gcode/i) || f.endsWith('.gcode')
|
|
604
737
|
);
|
|
605
738
|
if (!hasGcode) {
|
|
606
|
-
console.log(
|
|
739
|
+
console.log(`3MF has no gcode — auto-slicing with ${slicerType} for ${printModel}`);
|
|
607
740
|
threeMFPath = await this.stlManipulator.sliceSTL(
|
|
608
|
-
threeMFPath, slicerType, slicerPath, slicerProfile || undefined
|
|
741
|
+
threeMFPath, slicerType, slicerPath, slicerProfile || undefined,
|
|
742
|
+
undefined, // progressCallback
|
|
743
|
+
printPreset
|
|
609
744
|
);
|
|
610
745
|
console.log("Auto-sliced to: " + threeMFPath);
|
|
611
746
|
}
|
|
@@ -658,6 +793,7 @@ class BambuPrinterMCPServer {
|
|
|
658
793
|
plateIndex: 0,
|
|
659
794
|
useAMS: useAMS,
|
|
660
795
|
amsMapping: finalAmsMapping,
|
|
796
|
+
bedType: printBedType,
|
|
661
797
|
bedLeveling: args?.bed_leveling !== undefined ? Boolean(args.bed_leveling) : undefined,
|
|
662
798
|
flowCalibration: args?.flow_calibration !== undefined ? Boolean(args.flow_calibration) : undefined,
|
|
663
799
|
vibrationCalibration: args?.vibration_calibration !== undefined ? Boolean(args.vibration_calibration) : undefined,
|
package/src/printers/bambu.ts
CHANGED
|
@@ -17,6 +17,7 @@ interface BambuPrintOptionsInternal {
|
|
|
17
17
|
filePath: string;
|
|
18
18
|
useAMS?: boolean;
|
|
19
19
|
plateIndex?: number;
|
|
20
|
+
bedType?: string;
|
|
20
21
|
bedLeveling?: boolean;
|
|
21
22
|
flowCalibration?: boolean;
|
|
22
23
|
vibrationCalibration?: boolean;
|
|
@@ -257,7 +258,7 @@ export class BambuImplementation {
|
|
|
257
258
|
layer_inspect: options.layerInspect ?? true,
|
|
258
259
|
vibration_cali: options.vibrationCalibration ?? true,
|
|
259
260
|
bed_leveling: options.bedLeveling ?? true,
|
|
260
|
-
bed_type: "textured_plate",
|
|
261
|
+
bed_type: options.bedType || "textured_plate",
|
|
261
262
|
timelapse: options.timelapse ?? false,
|
|
262
263
|
use_ams: options.useAMS !== false,
|
|
263
264
|
ams_mapping: amsMapping,
|
|
@@ -849,6 +849,7 @@ export class STLManipulator extends EventEmitter {
|
|
|
849
849
|
* @param slicerPath Path to the slicer executable
|
|
850
850
|
* @param slicerProfile Optional path to the slicer profile/config file
|
|
851
851
|
* @param progressCallback Optional callback for progress updates
|
|
852
|
+
* @param printerPreset Optional BambuStudio printer preset name (e.g., "Bambu Lab P1S 0.4 nozzle")
|
|
852
853
|
* @returns Path to the generated G-code or sliced 3MF file
|
|
853
854
|
*/
|
|
854
855
|
async sliceSTL(
|
|
@@ -856,7 +857,8 @@ export class STLManipulator extends EventEmitter {
|
|
|
856
857
|
slicerType: 'prusaslicer' | 'cura' | 'slic3r' | 'orcaslicer' | 'bambustudio',
|
|
857
858
|
slicerPath: string,
|
|
858
859
|
slicerProfile?: string,
|
|
859
|
-
progressCallback?: ProgressCallback
|
|
860
|
+
progressCallback?: ProgressCallback,
|
|
861
|
+
printerPreset?: string
|
|
860
862
|
): Promise<string> {
|
|
861
863
|
const operationId = this.generateOperationId();
|
|
862
864
|
this.activeOperations.set(operationId, true);
|
|
@@ -939,6 +941,13 @@ export class STLManipulator extends EventEmitter {
|
|
|
939
941
|
if (slicerProfile) {
|
|
940
942
|
args.push('--load-settings', slicerProfile);
|
|
941
943
|
}
|
|
944
|
+
// CRITICAL: Pass printer preset so slicer generates correct G-code
|
|
945
|
+
// for the target printer model. Without this, the slicer uses its
|
|
946
|
+
// default which may be a completely different printer, causing
|
|
947
|
+
// dangerous bed crashes or axis limit violations.
|
|
948
|
+
if (printerPreset && !slicerProfile) {
|
|
949
|
+
args.push('--load-machine', printerPreset);
|
|
950
|
+
}
|
|
942
951
|
args.push(stlFilePath);
|
|
943
952
|
// Override outputFilePath for bambustudio since it produces 3MF, not gcode
|
|
944
953
|
outputFilePath = bambuOutputPath;
|