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/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
- layer_height: { type: "number", description: "Override layer height (mm)" },
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: "object",
352
- description: "Override AMS filament mapping (e.g., {\"Generic PLA\": 0})",
353
- additionalProperties: { type: "number" }
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
- result = await this.stlManipulator.sliceSTL(String(args.stl_path), slicerType, slicerPath, slicerProfile || undefined);
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("3MF has no gcode — auto-slicing with " + slicerType);
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,
@@ -3,6 +3,7 @@ interface BambuPrintOptionsInternal {
3
3
  filePath: string;
4
4
  useAMS?: boolean;
5
5
  plateIndex?: number;
6
+ bedType?: string;
6
7
  bedLeveling?: boolean;
7
8
  flowCalibration?: boolean;
8
9
  vibrationCalibration?: boolean;
@@ -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.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, ThreeMFData } from './3mf_parser.js';
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
- layer_height: { type: "number", description: "Override layer height (mm)" },
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: "object",
404
- description: "Override AMS filament mapping (e.g., {\"Generic PLA\": 0})",
405
- additionalProperties: { type: "number" }
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("3MF has no gcode — auto-slicing with " + slicerType);
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,
@@ -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;