mcp-3d-printer-server 1.2.2 → 1.2.5

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
@@ -14,6 +14,7 @@ import { PrinterFactory } from "./printers/printer-factory.js";
14
14
  import { STLManipulator } from "./stl/stl-manipulator.js";
15
15
  import { parse3MF } from './3mf_parser.js';
16
16
  import { BambuImplementation } from "./printers/bambu.js";
17
+ import { inspectFuluOrcaSetup, invokeFuluBridgeRpc, isFuluPrintRpcMethod, } from "./fulu-orca.js";
17
18
  // Load environment variables from .env file
18
19
  dotenv.config();
19
20
  // Default values
@@ -23,9 +24,88 @@ const DEFAULT_API_KEY = process.env.API_KEY || "";
23
24
  const DEFAULT_TYPE = process.env.PRINTER_TYPE || "octoprint"; // Default to OctoPrint
24
25
  const TEMP_DIR = process.env.TEMP_DIR || path.join(process.cwd(), "temp");
25
26
  // Slicer configuration
26
- const DEFAULT_SLICER_TYPE = process.env.SLICER_TYPE || (process.env.PRINTER_TYPE === "bambu" ? "bambustudio" : "prusaslicer");
27
- const DEFAULT_SLICER_PATH = process.env.SLICER_PATH || (process.env.PRINTER_TYPE === "bambu" ? "/Applications/BambuStudio.app/Contents/MacOS/BambuStudio" : "");
27
+ const CANONICAL_SLICER_TYPES = [
28
+ "prusaslicer",
29
+ "cura",
30
+ "slic3r",
31
+ "orcaslicer",
32
+ "orcaslicer-bambulab",
33
+ "bambustudio",
34
+ ];
35
+ const SLICER_TYPE_ALIASES = {
36
+ prusa: "prusaslicer",
37
+ prusaslicer: "prusaslicer",
38
+ cura: "cura",
39
+ curaengine: "cura",
40
+ slic3r: "slic3r",
41
+ orca: "orcaslicer",
42
+ orcaslicer: "orcaslicer",
43
+ "orca-slicer": "orcaslicer",
44
+ "orcaslicer-bambulab": "orcaslicer-bambulab",
45
+ "orcaslicer_bambulab": "orcaslicer-bambulab",
46
+ "orca-bambulab": "orcaslicer-bambulab",
47
+ "orca_bambulab": "orcaslicer-bambulab",
48
+ "fulu-orca": "orcaslicer-bambulab",
49
+ "fulu_orca": "orcaslicer-bambulab",
50
+ "orca-studio": "orcaslicer-bambulab",
51
+ "orca_studio": "orcaslicer-bambulab",
52
+ orcastudio: "orcaslicer-bambulab",
53
+ bambustudio: "bambustudio",
54
+ "bambu-studio": "bambustudio",
55
+ "bambu_studio": "bambustudio",
56
+ };
57
+ const SLICER_TYPE_ENUM = [
58
+ ...CANONICAL_SLICER_TYPES,
59
+ "fulu_orca",
60
+ "fulu-orca",
61
+ "orca-studio",
62
+ "orca_bambulab",
63
+ ];
64
+ function normalizeSlicerType(rawValue) {
65
+ const normalized = (rawValue || "").trim().toLowerCase();
66
+ const resolved = SLICER_TYPE_ALIASES[normalized];
67
+ if (!resolved) {
68
+ throw new Error(`Unsupported slicer type "${rawValue}". Valid types: ${SLICER_TYPE_ENUM.join(", ")}`);
69
+ }
70
+ return resolved;
71
+ }
72
+ function isBambuProjectSlicer(slicerType) {
73
+ return slicerType === "orcaslicer-bambulab" || slicerType === "bambustudio";
74
+ }
75
+ function firstExistingPath(candidates) {
76
+ return candidates.find((candidate) => fs.existsSync(candidate));
77
+ }
78
+ function defaultFuluOrcaPath() {
79
+ return (process.env.FULU_ORCA_PATH ||
80
+ process.env.ORCASLICER_BAMBULAB_PATH ||
81
+ process.env.ORCA_SLICER_BAMBULAB_PATH ||
82
+ firstExistingPath([
83
+ "/Applications/OrcaSlicer.app/Contents/MacOS/OrcaSlicer",
84
+ "/Applications/Orca Studio.app/Contents/MacOS/OrcaSlicer",
85
+ "/Applications/OrcaStudio.app/Contents/MacOS/OrcaStudio",
86
+ "/usr/local/bin/orcaslicer",
87
+ "/usr/bin/orcaslicer",
88
+ ]) ||
89
+ "orcaslicer");
90
+ }
91
+ function defaultBambuStudioPath() {
92
+ return (firstExistingPath([
93
+ "/Applications/BambuStudio.app/Contents/MacOS/BambuStudio",
94
+ "/Applications/Bambu Studio.app/Contents/MacOS/BambuStudio",
95
+ "/usr/local/bin/bambustudio",
96
+ "/usr/bin/bambustudio",
97
+ ]) ||
98
+ "bambustudio");
99
+ }
100
+ const DEFAULT_SLICER_TYPE = normalizeSlicerType(process.env.SLICER_TYPE || (DEFAULT_TYPE.toLowerCase() === "bambu" ? "orcaslicer-bambulab" : "prusaslicer"));
101
+ const DEFAULT_SLICER_PATH = process.env.SLICER_PATH ||
102
+ (DEFAULT_SLICER_TYPE === "orcaslicer-bambulab"
103
+ ? defaultFuluOrcaPath()
104
+ : DEFAULT_SLICER_TYPE === "bambustudio"
105
+ ? defaultBambuStudioPath()
106
+ : "");
28
107
  const DEFAULT_SLICER_PROFILE = process.env.SLICER_PROFILE || "";
108
+ const DEFAULT_FILAMENT_PROFILE = process.env.FILAMENT_PROFILE || process.env.SLICER_FILAMENT_PROFILE || "";
29
109
  // Bambu-specific default values
30
110
  const DEFAULT_BAMBU_SERIAL = process.env.BAMBU_SERIAL || "";
31
111
  const DEFAULT_BAMBU_TOKEN = process.env.BAMBU_TOKEN || "";
@@ -193,6 +273,43 @@ class ThreeDPrinterMCPServer {
193
273
  this.setupResourceHandlers();
194
274
  this.setupToolHandlers();
195
275
  }
276
+ extractParsedAmsMapping(parsed3MFData) {
277
+ if (!parsed3MFData?.slicerConfig?.ams_mapping) {
278
+ return undefined;
279
+ }
280
+ const slots = Object.values(parsed3MFData.slicerConfig.ams_mapping).filter((value) => typeof value === "number");
281
+ return slots.length > 0 ? slots.sort((a, b) => a - b) : undefined;
282
+ }
283
+ resolveAmsPrintOptions(parsed3MFData, args) {
284
+ let finalAmsMapping = this.extractParsedAmsMapping(parsed3MFData);
285
+ let useAMS = args?.use_ams !== undefined
286
+ ? Boolean(args.use_ams)
287
+ : !!finalAmsMapping && finalAmsMapping.length > 0;
288
+ const override = args?.ams_mapping;
289
+ if (override !== undefined) {
290
+ let userMappingOverride;
291
+ if (Array.isArray(override)) {
292
+ userMappingOverride = override.filter((value) => typeof value === "number");
293
+ }
294
+ else if (typeof override === "object" && override !== null) {
295
+ userMappingOverride = Object.values(override)
296
+ .filter((value) => typeof value === "number")
297
+ .sort((a, b) => a - b);
298
+ }
299
+ if (userMappingOverride && userMappingOverride.length > 0) {
300
+ finalAmsMapping = userMappingOverride;
301
+ useAMS = true;
302
+ }
303
+ }
304
+ if (args?.use_ams === false) {
305
+ finalAmsMapping = undefined;
306
+ useAMS = false;
307
+ }
308
+ if (!finalAmsMapping || finalAmsMapping.length === 0) {
309
+ useAMS = false;
310
+ }
311
+ return { useAMS, amsMapping: finalAmsMapping };
312
+ }
196
313
  setupResourceHandlers() {
197
314
  // List available resources
198
315
  this.server.setRequestHandler(ListResourcesRequestSchema, async () => {
@@ -309,6 +426,205 @@ class ThreeDPrinterMCPServer {
309
426
  }
310
427
  }
311
428
  },
429
+ {
430
+ name: "list_printer_files",
431
+ description: "List files available on the 3D printer",
432
+ inputSchema: {
433
+ type: "object",
434
+ properties: {
435
+ host: {
436
+ type: "string",
437
+ description: "Hostname or IP address of the printer (default: value from env)"
438
+ },
439
+ port: {
440
+ type: "string",
441
+ description: "Port of the printer API (default: value from env)"
442
+ },
443
+ type: {
444
+ type: "string",
445
+ description: "Type of printer management system (octoprint, klipper, duet, repetier, bambu, prusa, creality) (default: value from env)"
446
+ },
447
+ api_key: {
448
+ type: "string",
449
+ description: "API key for authentication (default: value from env)"
450
+ },
451
+ bambu_serial: {
452
+ type: "string",
453
+ description: "Serial number for Bambu Lab printers (default: value from env)"
454
+ },
455
+ bambu_token: {
456
+ type: "string",
457
+ description: "Access token for Bambu Lab printers (default: value from env)"
458
+ }
459
+ }
460
+ }
461
+ },
462
+ {
463
+ name: "upload_gcode",
464
+ description: "Upload G-code content or a local G-code file path to the printer",
465
+ inputSchema: {
466
+ type: "object",
467
+ properties: {
468
+ host: {
469
+ type: "string",
470
+ description: "Hostname or IP address of the printer (default: value from env)"
471
+ },
472
+ port: {
473
+ type: "string",
474
+ description: "Port of the printer API (default: value from env)"
475
+ },
476
+ type: {
477
+ type: "string",
478
+ description: "Type of printer management system (octoprint, klipper, duet, repetier, bambu, prusa, creality) (default: value from env)"
479
+ },
480
+ api_key: {
481
+ type: "string",
482
+ description: "API key for authentication (default: value from env)"
483
+ },
484
+ bambu_serial: {
485
+ type: "string",
486
+ description: "Serial number for Bambu Lab printers (default: value from env)"
487
+ },
488
+ bambu_token: {
489
+ type: "string",
490
+ description: "Access token for Bambu Lab printers (default: value from env)"
491
+ },
492
+ filename: {
493
+ type: "string",
494
+ description: "Filename to use on the printer. Defaults to the basename of gcode_path when omitted."
495
+ },
496
+ gcode: {
497
+ type: "string",
498
+ description: "G-code content, or a local path to a G-code file."
499
+ },
500
+ gcode_path: {
501
+ type: "string",
502
+ description: "Local path to a G-code file to upload."
503
+ },
504
+ print: {
505
+ type: "boolean",
506
+ description: "Start printing after upload when the printer backend supports it."
507
+ }
508
+ },
509
+ anyOf: [
510
+ { required: ["filename", "gcode"] },
511
+ { required: ["gcode_path"] }
512
+ ]
513
+ }
514
+ },
515
+ {
516
+ name: "start_print",
517
+ description: "Start printing a file that is already available on the printer",
518
+ inputSchema: {
519
+ type: "object",
520
+ properties: {
521
+ host: {
522
+ type: "string",
523
+ description: "Hostname or IP address of the printer (default: value from env)"
524
+ },
525
+ port: {
526
+ type: "string",
527
+ description: "Port of the printer API (default: value from env)"
528
+ },
529
+ type: {
530
+ type: "string",
531
+ description: "Type of printer management system (octoprint, klipper, duet, repetier, bambu, prusa, creality) (default: value from env)"
532
+ },
533
+ api_key: {
534
+ type: "string",
535
+ description: "API key for authentication (default: value from env)"
536
+ },
537
+ bambu_serial: {
538
+ type: "string",
539
+ description: "Serial number for Bambu Lab printers (default: value from env)"
540
+ },
541
+ bambu_token: {
542
+ type: "string",
543
+ description: "Access token for Bambu Lab printers (default: value from env)"
544
+ },
545
+ filename: {
546
+ type: "string",
547
+ description: "Name/path of the printer-side G-code file to start."
548
+ }
549
+ },
550
+ required: ["filename"]
551
+ }
552
+ },
553
+ {
554
+ name: "cancel_print",
555
+ description: "Cancel the current print job",
556
+ inputSchema: {
557
+ type: "object",
558
+ properties: {
559
+ host: {
560
+ type: "string",
561
+ description: "Hostname or IP address of the printer (default: value from env)"
562
+ },
563
+ port: {
564
+ type: "string",
565
+ description: "Port of the printer API (default: value from env)"
566
+ },
567
+ type: {
568
+ type: "string",
569
+ description: "Type of printer management system (octoprint, klipper, duet, repetier, bambu, prusa, creality) (default: value from env)"
570
+ },
571
+ api_key: {
572
+ type: "string",
573
+ description: "API key for authentication (default: value from env)"
574
+ },
575
+ bambu_serial: {
576
+ type: "string",
577
+ description: "Serial number for Bambu Lab printers (default: value from env)"
578
+ },
579
+ bambu_token: {
580
+ type: "string",
581
+ description: "Access token for Bambu Lab printers (default: value from env)"
582
+ }
583
+ }
584
+ }
585
+ },
586
+ {
587
+ name: "set_printer_temperature",
588
+ description: "Set the temperature of a printer component",
589
+ inputSchema: {
590
+ type: "object",
591
+ properties: {
592
+ host: {
593
+ type: "string",
594
+ description: "Hostname or IP address of the printer (default: value from env)"
595
+ },
596
+ port: {
597
+ type: "string",
598
+ description: "Port of the printer API (default: value from env)"
599
+ },
600
+ type: {
601
+ type: "string",
602
+ description: "Type of printer management system (octoprint, klipper, duet, repetier, bambu, prusa, creality) (default: value from env)"
603
+ },
604
+ api_key: {
605
+ type: "string",
606
+ description: "API key for authentication (default: value from env)"
607
+ },
608
+ bambu_serial: {
609
+ type: "string",
610
+ description: "Serial number for Bambu Lab printers (default: value from env)"
611
+ },
612
+ bambu_token: {
613
+ type: "string",
614
+ description: "Access token for Bambu Lab printers (default: value from env)"
615
+ },
616
+ component: {
617
+ type: "string",
618
+ description: "Printer component to heat, such as extruder or bed."
619
+ },
620
+ temperature: {
621
+ type: "number",
622
+ description: "Target temperature in Celsius."
623
+ }
624
+ },
625
+ required: ["component", "temperature"]
626
+ }
627
+ },
312
628
  // New STL manipulation tools
313
629
  {
314
630
  name: "extend_stl_base",
@@ -341,7 +657,7 @@ class ThreeDPrinterMCPServer {
341
657
  bambu_model: {
342
658
  type: "string",
343
659
  enum: ["p1s", "p1p", "x1c", "x1e", "a1", "a1mini", "h2d"],
344
- description: "Bambu Lab printer model — required when using bambustudio slicer to ensure correct G-code."
660
+ description: "Bambu Lab printer model - required when using Bambu project slicers to ensure correct G-code."
345
661
  },
346
662
  nozzle_diameter: {
347
663
  type: "string",
@@ -349,7 +665,8 @@ class ThreeDPrinterMCPServer {
349
665
  },
350
666
  slicer_type: {
351
667
  type: "string",
352
- description: "Type of slicer to use (prusaslicer, cura, slic3r, orcaslicer, bambustudio) (default: value from env)"
668
+ enum: [...SLICER_TYPE_ENUM],
669
+ description: "Type of slicer to use (prusaslicer, cura, slic3r, orcaslicer, orcaslicer-bambulab, bambustudio). Use orcaslicer-bambulab for the FULU fork."
353
670
  },
354
671
  slicer_path: {
355
672
  type: "string",
@@ -357,7 +674,11 @@ class ThreeDPrinterMCPServer {
357
674
  },
358
675
  slicer_profile: {
359
676
  type: "string",
360
- description: "Profile to use for slicing (default: value from env)"
677
+ description: "Profile to use for slicing (default: value from env). OrcaSlicer also accepts machine/process profiles separated with ';', optionally followed by '|filament.json'."
678
+ },
679
+ filament_profile: {
680
+ type: "string",
681
+ description: "OrcaSlicer filament profile path loaded with --load-filaments (default: FILAMENT_PROFILE/SLICER_FILAMENT_PROFILE env)."
361
682
  }
362
683
  },
363
684
  required: ["stl_path"]
@@ -422,6 +743,37 @@ class ThreeDPrinterMCPServer {
422
743
  api_key: {
423
744
  type: "string",
424
745
  description: "API key for authentication (default: value from env)"
746
+ },
747
+ bambu_model: {
748
+ type: "string",
749
+ enum: ["p1s", "p1p", "x1c", "x1e", "a1", "a1mini", "h2d"],
750
+ description: "Bambu Lab printer model. Required for Bambu print operations."
751
+ },
752
+ bed_type: {
753
+ type: "string",
754
+ enum: ["textured_plate", "cool_plate", "engineering_plate", "hot_plate"],
755
+ description: "Bed/plate type installed on the printer (default: textured_plate)."
756
+ },
757
+ nozzle_diameter: {
758
+ type: "string",
759
+ description: "Nozzle diameter in mm (default: 0.4)."
760
+ },
761
+ slicer_type: {
762
+ type: "string",
763
+ enum: [...SLICER_TYPE_ENUM],
764
+ description: "Type of slicer to use. Use orcaslicer-bambulab for FULU OrcaSlicer-bambulab."
765
+ },
766
+ slicer_path: {
767
+ type: "string",
768
+ description: "Path to the slicer executable (default: value from env)."
769
+ },
770
+ slicer_profile: {
771
+ type: "string",
772
+ description: "Profile to use for slicing (default: value from env). OrcaSlicer also accepts machine/process profiles separated with ';', optionally followed by '|filament.json'."
773
+ },
774
+ filament_profile: {
775
+ type: "string",
776
+ description: "OrcaSlicer filament profile path loaded with --load-filaments (default: FILAMENT_PROFILE/SLICER_FILAMENT_PROFILE env)."
425
777
  }
426
778
  },
427
779
  required: ["stl_path", "extension_inches"]
@@ -650,11 +1002,126 @@ class ThreeDPrinterMCPServer {
650
1002
  type: "object",
651
1003
  description: "Override AMS filament mapping (e.g., {\"Generic PLA\": 0, \"Generic PETG\": 1}).",
652
1004
  additionalProperties: { type: "number" }
1005
+ },
1006
+ slicer_type: {
1007
+ type: "string",
1008
+ enum: [...SLICER_TYPE_ENUM],
1009
+ description: "Slicer to use if the 3MF needs auto-slicing. Use orcaslicer-bambulab for FULU OrcaSlicer-bambulab."
1010
+ },
1011
+ slicer_path: {
1012
+ type: "string",
1013
+ description: "Path to the slicer executable if auto-slicing is needed."
1014
+ },
1015
+ slicer_profile: {
1016
+ type: "string",
1017
+ description: "Optional slicer settings/profile path for auto-slicing."
1018
+ },
1019
+ filament_profile: {
1020
+ type: "string",
1021
+ description: "Optional filament profile path for auto-slicing with OrcaSlicer/Bambu Studio."
1022
+ },
1023
+ use_ams: {
1024
+ type: "boolean",
1025
+ description: "Whether to use AMS for the print. Defaults from parsed 3MF mapping when present."
1026
+ },
1027
+ bed_leveling: {
1028
+ type: "boolean",
1029
+ description: "Override bed leveling flag for the Bambu print command."
1030
+ },
1031
+ flow_calibration: {
1032
+ type: "boolean",
1033
+ description: "Override flow calibration flag for the Bambu print command."
1034
+ },
1035
+ vibration_calibration: {
1036
+ type: "boolean",
1037
+ description: "Override vibration calibration flag for the Bambu print command."
1038
+ },
1039
+ layer_inspect: {
1040
+ type: "boolean",
1041
+ description: "Override layer inspection flag for the Bambu print command."
1042
+ },
1043
+ timelapse: {
1044
+ type: "boolean",
1045
+ description: "Override timelapse flag for the Bambu print command."
653
1046
  }
654
1047
  },
655
1048
  required: ["three_mf_path", "bambu_model"]
656
1049
  }
657
1050
  },
1051
+ {
1052
+ name: "check_fulu_orca_setup",
1053
+ description: "Inspect a FULU OrcaSlicer-bambulab install, platform runtime payload, setup commands, and optionally probe the BambuNetwork bridge.",
1054
+ inputSchema: {
1055
+ type: "object",
1056
+ properties: {
1057
+ slicer_path: {
1058
+ type: "string",
1059
+ description: "Path to the FULU OrcaSlicer executable. Defaults from SLICER_PATH/FULU_ORCA_PATH."
1060
+ },
1061
+ plugin_dir: {
1062
+ type: "string",
1063
+ description: "Directory containing the FULU Bambu runtime payload; on macOS this is usually OrcaSlicer.app/Contents/MacOS."
1064
+ },
1065
+ runtime_dir: {
1066
+ type: "string",
1067
+ description: "Installed runtime directory. On macOS this defaults to ~/Library/Application Support/OrcaSlicer/macos-bridge/runtime."
1068
+ },
1069
+ platform: {
1070
+ type: "string",
1071
+ enum: ["darwin", "macos", "win32", "windows", "linux"],
1072
+ description: "Platform to inspect. Defaults to the current Node.js platform."
1073
+ },
1074
+ bridge_command: {
1075
+ type: "string",
1076
+ description: "Command that starts the FULU BambuNetwork bridge host for probing."
1077
+ },
1078
+ run_bridge_probe: {
1079
+ type: "boolean",
1080
+ description: "When true, sends bridge.handshake, bridge.capabilities, and bridge.runtime_info to the bridge host."
1081
+ },
1082
+ probe_timeout_ms: {
1083
+ type: "number",
1084
+ description: "Bridge probe timeout in milliseconds (default: 5000)."
1085
+ }
1086
+ }
1087
+ }
1088
+ },
1089
+ {
1090
+ name: "fulu_bambu_network_rpc",
1091
+ description: "Advanced FULU bridge RPC for BambuNetwork diagnostics and development. Read-only methods are allowed by default; mutating methods require allow_mutating_method=true, and print methods require bambu_model.",
1092
+ inputSchema: {
1093
+ type: "object",
1094
+ properties: {
1095
+ bridge_command: {
1096
+ type: "string",
1097
+ description: "Command that starts the FULU BambuNetwork bridge host. Defaults to FULU_BAMBU_BRIDGE_COMMAND."
1098
+ },
1099
+ method: {
1100
+ type: "string",
1101
+ description: "FULU bridge method, e.g. bridge.handshake, bridge.runtime_info, net.get_user_print_info, net.start_print."
1102
+ },
1103
+ payload: {
1104
+ type: "object",
1105
+ description: "JSON payload sent to the FULU bridge method.",
1106
+ additionalProperties: true
1107
+ },
1108
+ timeout_ms: {
1109
+ type: "number",
1110
+ description: "Bridge request timeout in milliseconds (default: 5000)."
1111
+ },
1112
+ allow_mutating_method: {
1113
+ type: "boolean",
1114
+ description: "Required for methods outside the read-only bridge/net allowlist."
1115
+ },
1116
+ bambu_model: {
1117
+ type: "string",
1118
+ enum: ["p1s", "p1p", "x1c", "x1e", "a1", "a1mini", "h2d"],
1119
+ description: "Required when calling FULU print RPC methods; preserves the Bambu model safety gate."
1120
+ }
1121
+ },
1122
+ required: ["method"]
1123
+ }
1124
+ },
658
1125
  {
659
1126
  name: "merge_vertices",
660
1127
  description: "Merge vertices in an STL file that are closer than the specified tolerance.",
@@ -741,9 +1208,10 @@ class ThreeDPrinterMCPServer {
741
1208
  const apiKey = String(args?.api_key || DEFAULT_API_KEY);
742
1209
  const bambuSerial = String(args?.bambu_serial || DEFAULT_BAMBU_SERIAL);
743
1210
  const bambuToken = String(args?.bambu_token || DEFAULT_BAMBU_TOKEN);
744
- const slicerType = String(args?.slicer_type || DEFAULT_SLICER_TYPE);
1211
+ const slicerType = normalizeSlicerType(String(args?.slicer_type || DEFAULT_SLICER_TYPE));
745
1212
  const slicerPath = String(args?.slicer_path || DEFAULT_SLICER_PATH);
746
1213
  const slicerProfile = String(args?.slicer_profile || DEFAULT_SLICER_PROFILE);
1214
+ const filamentProfile = String(args?.filament_profile || DEFAULT_FILAMENT_PROFILE);
747
1215
  try {
748
1216
  let result;
749
1217
  switch (name) {
@@ -754,10 +1222,18 @@ class ThreeDPrinterMCPServer {
754
1222
  result = await this.getPrinterFiles(host, port, type, apiKey, bambuSerial, bambuToken);
755
1223
  break;
756
1224
  case "upload_gcode":
757
- if (!args?.filename || !args?.gcode) {
758
- throw new Error("Missing required parameters: filename and gcode");
1225
+ if (!args?.gcode && !args?.gcode_path) {
1226
+ throw new Error("Missing required parameter: gcode or gcode_path");
1227
+ }
1228
+ if (args?.gcode_path && !fs.existsSync(String(args.gcode_path))) {
1229
+ throw new Error(`G-code file not found: ${String(args.gcode_path)}`);
759
1230
  }
760
- result = await this.uploadGcode(host, port, type, apiKey, bambuSerial, bambuToken, String(args.filename), String(args.gcode), Boolean(args.print || false));
1231
+ if (!args?.filename && !args?.gcode_path) {
1232
+ throw new Error("Missing required parameter: filename");
1233
+ }
1234
+ const uploadFilename = String(args?.filename || path.basename(String(args.gcode_path)));
1235
+ const uploadGcodeSource = String(args?.gcode_path || args.gcode);
1236
+ result = await this.uploadGcode(host, port, type, apiKey, bambuSerial, bambuToken, uploadFilename, uploadGcodeSource, Boolean(args.print || false));
761
1237
  break;
762
1238
  case "start_print":
763
1239
  if (!args?.filename) {
@@ -787,13 +1263,13 @@ class ThreeDPrinterMCPServer {
787
1263
  }
788
1264
  // Resolve printer model for BambuStudio slicer
789
1265
  let slicePreset;
790
- if (slicerType === 'bambustudio') {
1266
+ if (isBambuProjectSlicer(slicerType)) {
791
1267
  const sliceModel = await this.resolveBambuModel(args?.bambu_model);
792
1268
  const nozzleDiam = String(args?.nozzle_diameter || DEFAULT_NOZZLE_DIAMETER);
793
1269
  slicePreset = BAMBU_MODEL_PRESETS[sliceModel]?.(nozzleDiam);
794
1270
  }
795
1271
  result = await this.stlManipulator.sliceSTL(String(args.stl_path), slicerType, slicerPath, slicerProfile || undefined, undefined, // progressCallback
796
- slicePreset);
1272
+ slicePreset, filamentProfile || undefined);
797
1273
  break;
798
1274
  }
799
1275
  case "confirm_temperatures":
@@ -817,12 +1293,50 @@ class ThreeDPrinterMCPServer {
817
1293
  const extendedStlPath = await this.stlManipulator.extendBase(String(args.stl_path), Number(args.extension_inches), processProgressCallback);
818
1294
  // 2. Slice the extended STL file
819
1295
  let processPreset;
820
- if (slicerType === 'bambustudio') {
1296
+ if (type.toLowerCase() === 'bambu' || isBambuProjectSlicer(slicerType)) {
821
1297
  const processModel = await this.resolveBambuModel(args?.bambu_model);
822
1298
  const processNozzle = String(args?.nozzle_diameter || DEFAULT_NOZZLE_DIAMETER);
823
- processPreset = BAMBU_MODEL_PRESETS[processModel]?.(processNozzle);
1299
+ if (isBambuProjectSlicer(slicerType)) {
1300
+ processPreset = BAMBU_MODEL_PRESETS[processModel]?.(processNozzle);
1301
+ }
1302
+ }
1303
+ const gcodePath = await this.stlManipulator.sliceSTL(extendedStlPath, slicerType, slicerPath, slicerProfile || undefined, processProgressCallback, processPreset, filamentProfile || undefined);
1304
+ if (type.toLowerCase() === 'bambu' && gcodePath.toLowerCase().endsWith(".3mf")) {
1305
+ if (!bambuSerial || !bambuToken) {
1306
+ throw new Error("Bambu serial number and access token are required for Bambu 3MF printing.");
1307
+ }
1308
+ const factoryImplementation = this.printerFactory.getImplementation('bambu');
1309
+ if (!(factoryImplementation instanceof BambuImplementation)) {
1310
+ throw new Error("Internal error: Could not get Bambu printer implementation.");
1311
+ }
1312
+ const printBedType = resolveBedType(args?.bed_type);
1313
+ let parsedProcess3MF;
1314
+ try {
1315
+ parsedProcess3MF = await parse3MF(gcodePath);
1316
+ }
1317
+ catch (error) {
1318
+ console.warn(`Could not parse AMS metadata from sliced 3MF ${gcodePath}:`, error.message);
1319
+ }
1320
+ const amsOptions = this.resolveAmsPrintOptions(parsedProcess3MF, args);
1321
+ result = await factoryImplementation.print3mf(host, bambuSerial, bambuToken, {
1322
+ projectName: path.basename(gcodePath).replace(/\.3mf$/i, ''),
1323
+ filePath: gcodePath,
1324
+ plateIndex: 0,
1325
+ ...amsOptions,
1326
+ bedType: printBedType,
1327
+ bedLeveling: args?.bed_leveling !== undefined ? Boolean(args.bed_leveling) : undefined,
1328
+ flowCalibration: args?.flow_calibration !== undefined ? Boolean(args.flow_calibration) : undefined,
1329
+ vibrationCalibration: args?.vibration_calibration !== undefined
1330
+ ? Boolean(args.vibration_calibration)
1331
+ : undefined,
1332
+ layerInspect: args?.layer_inspect !== undefined ? Boolean(args.layer_inspect) : undefined,
1333
+ timelapse: args?.timelapse !== undefined ? Boolean(args.timelapse) : undefined,
1334
+ });
1335
+ break;
1336
+ }
1337
+ if (gcodePath.toLowerCase().endsWith(".3mf")) {
1338
+ throw new Error("process_and_print_stl produced a sliced 3MF. Direct process-and-print for sliced 3MF is only supported for Bambu printers via print_3mf.");
824
1339
  }
825
- const gcodePath = await this.stlManipulator.sliceSTL(extendedStlPath, slicerType, slicerPath, slicerProfile || undefined, processProgressCallback, processPreset);
826
1340
  // 3. Confirm temperatures if specified
827
1341
  if (args.extruder_temp !== undefined || args.bed_temp !== undefined) {
828
1342
  const tempConfirmation = await this.stlManipulator.confirmTemperatures(gcodePath, {
@@ -1002,7 +1516,7 @@ class ThreeDPrinterMCPServer {
1002
1516
  if (!hasGcode) {
1003
1517
  console.log(`3MF has no gcode — auto-slicing with ${slicerType} for ${printModel}`);
1004
1518
  threeMFPath = await this.stlManipulator.sliceSTL(threeMFPath, slicerType, slicerPath, slicerProfile || undefined, undefined, // progressCallback
1005
- printPreset);
1519
+ printPreset, filamentProfile || undefined);
1006
1520
  console.log("Auto-sliced to: " + threeMFPath);
1007
1521
  }
1008
1522
  }
@@ -1013,61 +1527,12 @@ class ThreeDPrinterMCPServer {
1013
1527
  let implementation;
1014
1528
  let threeMfFilename;
1015
1529
  let projectName;
1016
- let finalAmsMapping;
1017
- let useAMS;
1018
1530
  let printOptions; // Use a more specific type later if possible
1019
1531
  try {
1020
1532
  // --- Parse 3MF ---
1021
1533
  const parsed3MFData = await parse3MF(threeMFPath);
1022
1534
  console.log(`Successfully parsed 3MF file: ${threeMFPath}`);
1023
- let parsedAmsMapping = undefined;
1024
- // ... (Extract default AMS mapping logic) ...
1025
- if (parsed3MFData.slicerConfig?.ams_mapping) {
1026
- const slots = Object.values(parsed3MFData.slicerConfig.ams_mapping)
1027
- .filter(v => typeof v === 'number');
1028
- if (slots.length > 0) {
1029
- parsedAmsMapping = slots.sort((a, b) => a - b);
1030
- console.log("Extracted default AMS mapping from 3MF:", parsedAmsMapping);
1031
- }
1032
- else {
1033
- console.log("AMS mapping found in 3MF, but no valid slots extracted.");
1034
- }
1035
- }
1036
- else {
1037
- console.log("No default AMS mapping found in 3MF slicer config.");
1038
- }
1039
- // --- Gather Overrides and Determine Final Options ---
1040
- finalAmsMapping = parsedAmsMapping; // Start with parsed
1041
- useAMS = args?.use_ams !== undefined ? Boolean(args.use_ams) : (!!finalAmsMapping && finalAmsMapping.length > 0);
1042
- // ... (Process user AMS mapping override logic) ...
1043
- if (args?.ams_mapping) {
1044
- let userMappingOverride = undefined;
1045
- if (Array.isArray(args.ams_mapping)) {
1046
- userMappingOverride = args.ams_mapping.filter(v => typeof v === 'number');
1047
- }
1048
- else if (typeof args.ams_mapping === 'object') {
1049
- userMappingOverride = Object.values(args.ams_mapping)
1050
- .filter(v => typeof v === 'number')
1051
- .sort((a, b) => a - b);
1052
- }
1053
- if (userMappingOverride && userMappingOverride.length > 0) {
1054
- console.log("Applying user AMS mapping override:", userMappingOverride);
1055
- finalAmsMapping = userMappingOverride;
1056
- useAMS = true; // Force useAMS if override provided
1057
- }
1058
- else {
1059
- console.warn("Received ams_mapping override, but it was empty or invalid.");
1060
- }
1061
- }
1062
- // ... (Handle explicit use_ams=false) ...
1063
- if (args?.use_ams === false) {
1064
- console.log("User explicitly disabled AMS.");
1065
- finalAmsMapping = undefined;
1066
- useAMS = false;
1067
- }
1068
- if (!finalAmsMapping || finalAmsMapping.length === 0) {
1069
- useAMS = false;
1070
- }
1535
+ const amsOptions = this.resolveAmsPrintOptions(parsed3MFData, args);
1071
1536
  // --- Prepare Implementation and Print Options ---
1072
1537
  const factoryImplementation = this.printerFactory.getImplementation('bambu');
1073
1538
  if (!(factoryImplementation instanceof BambuImplementation)) {
@@ -1077,8 +1542,7 @@ class ThreeDPrinterMCPServer {
1077
1542
  threeMfFilename = path.basename(threeMFPath); // Assign to outer scope variable
1078
1543
  projectName = threeMfFilename.replace(/\.3mf$/i, ''); // Assign to outer scope variable
1079
1544
  printOptions = {
1080
- useAMS: useAMS,
1081
- amsMapping: finalAmsMapping,
1545
+ ...amsOptions,
1082
1546
  bedType: printBedType,
1083
1547
  bedLeveling: args?.bed_leveling !== undefined ? Boolean(args.bed_leveling) : undefined,
1084
1548
  flowCalibration: args?.flow_calibration !== undefined ? Boolean(args.flow_calibration) : undefined,
@@ -1108,6 +1572,40 @@ class ThreeDPrinterMCPServer {
1108
1572
  }
1109
1573
  break;
1110
1574
  }
1575
+ case "check_fulu_orca_setup": {
1576
+ result = await inspectFuluOrcaSetup({
1577
+ slicerPath: args?.slicer_path !== undefined ? String(args.slicer_path) : undefined,
1578
+ pluginDir: args?.plugin_dir !== undefined ? String(args.plugin_dir) : undefined,
1579
+ runtimeDir: args?.runtime_dir !== undefined ? String(args.runtime_dir) : undefined,
1580
+ platform: args?.platform !== undefined ? String(args.platform) : undefined,
1581
+ bridgeCommand: args?.bridge_command !== undefined ? String(args.bridge_command) : undefined,
1582
+ runBridgeProbe: Boolean(args?.run_bridge_probe ?? false),
1583
+ probeTimeoutMs: args?.probe_timeout_ms !== undefined ? Number(args.probe_timeout_ms) : undefined,
1584
+ });
1585
+ break;
1586
+ }
1587
+ case "fulu_bambu_network_rpc": {
1588
+ if (!args?.method) {
1589
+ throw new Error("Missing required parameter: method");
1590
+ }
1591
+ const method = String(args.method);
1592
+ let rpcBambuModel;
1593
+ if (isFuluPrintRpcMethod(method)) {
1594
+ rpcBambuModel = await this.resolveBambuModel(args?.bambu_model);
1595
+ }
1596
+ const payload = typeof args.payload === "object" && args.payload !== null && !Array.isArray(args.payload)
1597
+ ? args.payload
1598
+ : undefined;
1599
+ result = await invokeFuluBridgeRpc({
1600
+ bridgeCommand: args?.bridge_command !== undefined ? String(args.bridge_command) : undefined,
1601
+ method,
1602
+ payload,
1603
+ timeoutMs: args?.timeout_ms !== undefined ? Number(args.timeout_ms) : undefined,
1604
+ allowMutatingMethod: Boolean(args?.allow_mutating_method ?? false),
1605
+ bambuModel: rpcBambuModel,
1606
+ });
1607
+ break;
1608
+ }
1111
1609
  case "merge_vertices":
1112
1610
  if (!args?.stl_path) {
1113
1611
  throw new Error("Missing required parameter: stl_path");
@@ -1193,9 +1691,12 @@ class ThreeDPrinterMCPServer {
1193
1691
  return implementation.getFile(host, port, apiKey, filename);
1194
1692
  }
1195
1693
  async uploadGcode(host, port, type, apiKey, bambuSerial, bambuToken, filename, gcode, print) {
1196
- const tempFilePath = path.join(TEMP_DIR, filename);
1197
- // Write gcode to temporary file
1198
- fs.writeFileSync(tempFilePath, gcode);
1694
+ const sourceIsFilePath = fs.existsSync(gcode) && fs.statSync(gcode).isFile();
1695
+ const tempFilePath = sourceIsFilePath ? gcode : path.join(TEMP_DIR, filename);
1696
+ if (!sourceIsFilePath) {
1697
+ fs.mkdirSync(TEMP_DIR, { recursive: true });
1698
+ fs.writeFileSync(tempFilePath, gcode);
1699
+ }
1199
1700
  try {
1200
1701
  const implementation = this.printerFactory.getImplementation(type);
1201
1702
  if (type.toLowerCase() === "bambu") {
@@ -1205,8 +1706,7 @@ class ThreeDPrinterMCPServer {
1205
1706
  return await implementation.uploadFile(host, port, apiKey, tempFilePath, filename, print);
1206
1707
  }
1207
1708
  finally {
1208
- // Clean up temporary file
1209
- if (fs.existsSync(tempFilePath)) {
1709
+ if (!sourceIsFilePath && fs.existsSync(tempFilePath)) {
1210
1710
  fs.unlinkSync(tempFilePath);
1211
1711
  }
1212
1712
  }