mcp-3d-printer-server 0.1.0 → 0.2.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/src/index.ts CHANGED
@@ -14,7 +14,9 @@ import axios from "axios";
14
14
  import dotenv from "dotenv";
15
15
  import fs from "fs";
16
16
  import path from "path";
17
+ import * as THREE from 'three';
17
18
  import { PrinterFactory } from "./printers/printer-factory.js";
19
+ import { STLManipulator } from "./stl/stl-manipulator.js";
18
20
 
19
21
  // Load environment variables from .env file
20
22
  dotenv.config();
@@ -26,6 +28,11 @@ const DEFAULT_API_KEY = process.env.API_KEY || "";
26
28
  const DEFAULT_TYPE = process.env.PRINTER_TYPE || "octoprint"; // Default to OctoPrint
27
29
  const TEMP_DIR = process.env.TEMP_DIR || path.join(process.cwd(), "temp");
28
30
 
31
+ // Slicer configuration
32
+ const DEFAULT_SLICER_TYPE = process.env.SLICER_TYPE || "prusaslicer";
33
+ const DEFAULT_SLICER_PATH = process.env.SLICER_PATH || "";
34
+ const DEFAULT_SLICER_PROFILE = process.env.SLICER_PROFILE || "";
35
+
29
36
  // Bambu-specific default values
30
37
  const DEFAULT_BAMBU_SERIAL = process.env.BAMBU_SERIAL || "";
31
38
  const DEFAULT_BAMBU_TOKEN = process.env.BAMBU_TOKEN || "";
@@ -38,6 +45,7 @@ if (!fs.existsSync(TEMP_DIR)) {
38
45
  class ThreeDPrinterMCPServer {
39
46
  private server: Server;
40
47
  private printerFactory: PrinterFactory;
48
+ private stlManipulator: STLManipulator;
41
49
 
42
50
  constructor() {
43
51
  this.server = new Server(
@@ -54,6 +62,7 @@ class ThreeDPrinterMCPServer {
54
62
  );
55
63
 
56
64
  this.printerFactory = new PrinterFactory();
65
+ this.stlManipulator = new STLManipulator(TEMP_DIR);
57
66
 
58
67
  this.setupHandlers();
59
68
  this.setupErrorHandling();
@@ -198,6 +207,294 @@ class ThreeDPrinterMCPServer {
198
207
  }
199
208
  }
200
209
  },
210
+ // New STL manipulation tools
211
+ {
212
+ name: "extend_stl_base",
213
+ description: "Extend the base of an STL file by a specified amount",
214
+ inputSchema: {
215
+ type: "object",
216
+ properties: {
217
+ stl_path: {
218
+ type: "string",
219
+ description: "Path to the STL file to modify"
220
+ },
221
+ extension_inches: {
222
+ type: "number",
223
+ description: "Amount to extend the base in inches"
224
+ }
225
+ },
226
+ required: ["stl_path", "extension_inches"]
227
+ }
228
+ },
229
+ {
230
+ name: "slice_stl",
231
+ description: "Slice an STL file to generate G-code",
232
+ inputSchema: {
233
+ type: "object",
234
+ properties: {
235
+ stl_path: {
236
+ type: "string",
237
+ description: "Path to the STL file to slice"
238
+ },
239
+ slicer_type: {
240
+ type: "string",
241
+ description: "Type of slicer to use (prusaslicer, cura, slic3r) (default: value from env)"
242
+ },
243
+ slicer_path: {
244
+ type: "string",
245
+ description: "Path to the slicer executable (default: value from env)"
246
+ },
247
+ slicer_profile: {
248
+ type: "string",
249
+ description: "Profile to use for slicing (default: value from env)"
250
+ }
251
+ },
252
+ required: ["stl_path"]
253
+ }
254
+ },
255
+ {
256
+ name: "confirm_temperatures",
257
+ description: "Confirm temperature settings in a G-code file",
258
+ inputSchema: {
259
+ type: "object",
260
+ properties: {
261
+ gcode_path: {
262
+ type: "string",
263
+ description: "Path to the G-code file"
264
+ },
265
+ extruder_temp: {
266
+ type: "number",
267
+ description: "Expected extruder temperature"
268
+ },
269
+ bed_temp: {
270
+ type: "number",
271
+ description: "Expected bed temperature"
272
+ }
273
+ },
274
+ required: ["gcode_path"]
275
+ }
276
+ },
277
+ {
278
+ name: "process_and_print_stl",
279
+ description: "Process an STL file (extend base), slice it, confirm temperatures, and start printing",
280
+ inputSchema: {
281
+ type: "object",
282
+ properties: {
283
+ stl_path: {
284
+ type: "string",
285
+ description: "Path to the STL file to process"
286
+ },
287
+ extension_inches: {
288
+ type: "number",
289
+ description: "Amount to extend the base in inches"
290
+ },
291
+ extruder_temp: {
292
+ type: "number",
293
+ description: "Expected extruder temperature"
294
+ },
295
+ bed_temp: {
296
+ type: "number",
297
+ description: "Expected bed temperature"
298
+ },
299
+ host: {
300
+ type: "string",
301
+ description: "Hostname or IP address of the printer (default: value from env)"
302
+ },
303
+ port: {
304
+ type: "string",
305
+ description: "Port of the printer API (default: value from env)"
306
+ },
307
+ type: {
308
+ type: "string",
309
+ description: "Type of printer management system (default: value from env)"
310
+ },
311
+ api_key: {
312
+ type: "string",
313
+ description: "API key for authentication (default: value from env)"
314
+ }
315
+ },
316
+ required: ["stl_path", "extension_inches"]
317
+ }
318
+ },
319
+ // New STL manipulation tools
320
+ {
321
+ name: "get_stl_info",
322
+ description: "Get detailed information about an STL file",
323
+ inputSchema: {
324
+ type: "object",
325
+ properties: {
326
+ stl_path: {
327
+ type: "string",
328
+ description: "Path to the STL file"
329
+ }
330
+ },
331
+ required: ["stl_path"]
332
+ }
333
+ },
334
+ {
335
+ name: "scale_stl",
336
+ description: "Scale an STL model uniformly or along specific axes",
337
+ inputSchema: {
338
+ type: "object",
339
+ properties: {
340
+ stl_path: {
341
+ type: "string",
342
+ description: "Path to the STL file"
343
+ },
344
+ scale_factor: {
345
+ type: "number",
346
+ description: "Uniform scaling factor to apply"
347
+ },
348
+ scale_x: {
349
+ type: "number",
350
+ description: "X-axis scaling factor (overrides scale_factor for X axis)"
351
+ },
352
+ scale_y: {
353
+ type: "number",
354
+ description: "Y-axis scaling factor (overrides scale_factor for Y axis)"
355
+ },
356
+ scale_z: {
357
+ type: "number",
358
+ description: "Z-axis scaling factor (overrides scale_factor for Z axis)"
359
+ }
360
+ },
361
+ required: ["stl_path"]
362
+ }
363
+ },
364
+ {
365
+ name: "rotate_stl",
366
+ description: "Rotate an STL model around specific axes",
367
+ inputSchema: {
368
+ type: "object",
369
+ properties: {
370
+ stl_path: {
371
+ type: "string",
372
+ description: "Path to the STL file"
373
+ },
374
+ rotate_x: {
375
+ type: "number",
376
+ description: "Rotation around X-axis in degrees"
377
+ },
378
+ rotate_y: {
379
+ type: "number",
380
+ description: "Rotation around Y-axis in degrees"
381
+ },
382
+ rotate_z: {
383
+ type: "number",
384
+ description: "Rotation around Z-axis in degrees"
385
+ }
386
+ },
387
+ required: ["stl_path"]
388
+ }
389
+ },
390
+ {
391
+ name: "translate_stl",
392
+ description: "Move an STL model along specific axes",
393
+ inputSchema: {
394
+ type: "object",
395
+ properties: {
396
+ stl_path: {
397
+ type: "string",
398
+ description: "Path to the STL file"
399
+ },
400
+ translate_x: {
401
+ type: "number",
402
+ description: "Translation along X-axis in millimeters"
403
+ },
404
+ translate_y: {
405
+ type: "number",
406
+ description: "Translation along Y-axis in millimeters"
407
+ },
408
+ translate_z: {
409
+ type: "number",
410
+ description: "Translation along Z-axis in millimeters"
411
+ }
412
+ },
413
+ required: ["stl_path"]
414
+ }
415
+ },
416
+ {
417
+ name: "modify_stl_section",
418
+ description: "Apply a specific transformation to a selected section of an STL file",
419
+ inputSchema: {
420
+ type: "object",
421
+ properties: {
422
+ stl_path: {
423
+ type: "string",
424
+ description: "Path to the STL file"
425
+ },
426
+ section: {
427
+ type: "string",
428
+ description: "Section to modify: 'top', 'bottom', 'center', or custom bounds",
429
+ enum: ["top", "bottom", "center", "custom"]
430
+ },
431
+ transformation_type: {
432
+ type: "string",
433
+ description: "Type of transformation to apply",
434
+ enum: ["scale", "rotate", "translate"]
435
+ },
436
+ value_x: {
437
+ type: "number",
438
+ description: "Transformation value for X axis"
439
+ },
440
+ value_y: {
441
+ type: "number",
442
+ description: "Transformation value for Y axis"
443
+ },
444
+ value_z: {
445
+ type: "number",
446
+ description: "Transformation value for Z axis"
447
+ },
448
+ custom_min_x: {
449
+ type: "number",
450
+ description: "Minimum X for custom section bounds"
451
+ },
452
+ custom_min_y: {
453
+ type: "number",
454
+ description: "Minimum Y for custom section bounds"
455
+ },
456
+ custom_min_z: {
457
+ type: "number",
458
+ description: "Minimum Z for custom section bounds"
459
+ },
460
+ custom_max_x: {
461
+ type: "number",
462
+ description: "Maximum X for custom section bounds"
463
+ },
464
+ custom_max_y: {
465
+ type: "number",
466
+ description: "Maximum Y for custom section bounds"
467
+ },
468
+ custom_max_z: {
469
+ type: "number",
470
+ description: "Maximum Z for custom section bounds"
471
+ }
472
+ },
473
+ required: ["stl_path", "section", "transformation_type"]
474
+ }
475
+ },
476
+ {
477
+ name: "generate_stl_visualization",
478
+ description: "Generate an SVG visualization of an STL file from multiple angles",
479
+ inputSchema: {
480
+ type: "object",
481
+ properties: {
482
+ stl_path: {
483
+ type: "string",
484
+ description: "Path to the STL file"
485
+ },
486
+ width: {
487
+ type: "number",
488
+ description: "Width of each view in pixels (default: 300)"
489
+ },
490
+ height: {
491
+ type: "number",
492
+ description: "Height of each view in pixels (default: 300)"
493
+ }
494
+ },
495
+ required: ["stl_path"]
496
+ }
497
+ },
201
498
  // ...other tools with similar structure
202
499
  // Abbreviated for clarity, would include all other tools
203
500
  ]
@@ -215,6 +512,9 @@ class ThreeDPrinterMCPServer {
215
512
  const apiKey = String(args?.api_key || DEFAULT_API_KEY);
216
513
  const bambuSerial = String(args?.bambu_serial || DEFAULT_BAMBU_SERIAL);
217
514
  const bambuToken = String(args?.bambu_token || DEFAULT_BAMBU_TOKEN);
515
+ const slicerType = String(args?.slicer_type || DEFAULT_SLICER_TYPE) as 'prusaslicer' | 'cura' | 'slic3r';
516
+ const slicerPath = String(args?.slicer_path || DEFAULT_SLICER_PATH);
517
+ const slicerProfile = String(args?.slicer_profile || DEFAULT_SLICER_PROFILE);
218
518
 
219
519
  try {
220
520
  let result;
@@ -262,6 +562,291 @@ class ThreeDPrinterMCPServer {
262
562
  );
263
563
  break;
264
564
 
565
+ // New STL manipulation tools
566
+ case "extend_stl_base":
567
+ if (!args?.stl_path || args?.extension_inches === undefined) {
568
+ throw new Error("Missing required parameters: stl_path and extension_inches");
569
+ }
570
+ result = await this.stlManipulator.extendBase(
571
+ String(args.stl_path),
572
+ Number(args.extension_inches)
573
+ );
574
+ break;
575
+
576
+ case "slice_stl":
577
+ if (!args?.stl_path) {
578
+ throw new Error("Missing required parameter: stl_path");
579
+ }
580
+ result = await this.stlManipulator.sliceSTL(
581
+ String(args.stl_path),
582
+ slicerType,
583
+ slicerPath,
584
+ slicerProfile || undefined
585
+ );
586
+ break;
587
+
588
+ case "confirm_temperatures":
589
+ if (!args?.gcode_path) {
590
+ throw new Error("Missing required parameter: gcode_path");
591
+ }
592
+ result = await this.stlManipulator.confirmTemperatures(
593
+ String(args.gcode_path),
594
+ {
595
+ extruder: args.extruder_temp !== undefined ? Number(args.extruder_temp) : undefined,
596
+ bed: args.bed_temp !== undefined ? Number(args.bed_temp) : undefined
597
+ }
598
+ );
599
+ break;
600
+
601
+ case "process_and_print_stl":
602
+ if (!args?.stl_path || args?.extension_inches === undefined) {
603
+ throw new Error("Missing required parameters: stl_path and extension_inches");
604
+ }
605
+
606
+ // Define progress callback for UI updates
607
+ const processProgressCallback = (progress: number, message?: string) => {
608
+ console.log(`Process progress: ${progress}% - ${message || ''}`);
609
+ };
610
+
611
+ // 1. Extend the base of the STL file
612
+ const extendedStlPath = await this.stlManipulator.extendBase(
613
+ String(args.stl_path),
614
+ Number(args.extension_inches),
615
+ processProgressCallback
616
+ );
617
+
618
+ // 2. Slice the extended STL file
619
+ const gcodePath = await this.stlManipulator.sliceSTL(
620
+ extendedStlPath,
621
+ slicerType,
622
+ slicerPath,
623
+ slicerProfile || undefined,
624
+ processProgressCallback
625
+ );
626
+
627
+ // 3. Confirm temperatures if specified
628
+ if (args.extruder_temp !== undefined || args.bed_temp !== undefined) {
629
+ const tempConfirmation = await this.stlManipulator.confirmTemperatures(
630
+ gcodePath,
631
+ {
632
+ extruder: args.extruder_temp !== undefined ? Number(args.extruder_temp) : undefined,
633
+ bed: args.bed_temp !== undefined ? Number(args.bed_temp) : undefined
634
+ },
635
+ processProgressCallback
636
+ );
637
+
638
+ if (!tempConfirmation.match) {
639
+ console.warn("Temperature mismatch:", tempConfirmation);
640
+ }
641
+ }
642
+
643
+ // 4. Upload the G-code file to the printer
644
+ const gcodeContent = await fs.promises.readFile(gcodePath, 'utf8');
645
+ const filename = path.basename(gcodePath);
646
+
647
+ await this.uploadGcode(
648
+ host, port, type, apiKey, bambuSerial, bambuToken,
649
+ filename,
650
+ gcodeContent,
651
+ true // Start printing immediately
652
+ );
653
+
654
+ result = {
655
+ extended_stl_path: extendedStlPath,
656
+ gcode_path: gcodePath,
657
+ filename,
658
+ status: "Print job started"
659
+ };
660
+ break;
661
+
662
+ // New STL manipulation tool handlers
663
+ case "get_stl_info":
664
+ if (!args?.stl_path) {
665
+ throw new Error("Missing required parameter: stl_path");
666
+ }
667
+
668
+ result = await this.stlManipulator.getSTLInfo(String(args.stl_path));
669
+ break;
670
+
671
+ case "scale_stl":
672
+ if (!args?.stl_path) {
673
+ throw new Error("Missing required parameter: stl_path");
674
+ }
675
+
676
+ // Define progress callback for UI updates
677
+ const scaleProgressCallback = (progress: number, message?: string) => {
678
+ console.log(`Scale progress: ${progress}% - ${message || ''}`);
679
+ };
680
+
681
+ let scaleFactors: number | [number, number, number];
682
+
683
+ // Check if we have individual axis scaling factors
684
+ if (args.scale_x !== undefined || args.scale_y !== undefined || args.scale_z !== undefined) {
685
+ // Use individual axis scaling
686
+ scaleFactors = [
687
+ Number(args.scale_x ?? 1.0),
688
+ Number(args.scale_y ?? 1.0),
689
+ Number(args.scale_z ?? 1.0)
690
+ ];
691
+ } else {
692
+ // Use uniform scaling
693
+ scaleFactors = Number(args.scale_factor ?? 1.0);
694
+ }
695
+
696
+ result = await this.stlManipulator.scaleSTL(
697
+ String(args.stl_path),
698
+ scaleFactors,
699
+ scaleProgressCallback
700
+ );
701
+ break;
702
+
703
+ case "rotate_stl":
704
+ if (!args?.stl_path) {
705
+ throw new Error("Missing required parameter: stl_path");
706
+ }
707
+
708
+ // Define progress callback for UI updates
709
+ const rotateProgressCallback = (progress: number, message?: string) => {
710
+ console.log(`Rotate progress: ${progress}% - ${message || ''}`);
711
+ };
712
+
713
+ // Get rotation angles, defaulting to 0 for any undefined axis
714
+ const rotationAngles: [number, number, number] = [
715
+ Number(args.rotate_x ?? 0),
716
+ Number(args.rotate_y ?? 0),
717
+ Number(args.rotate_z ?? 0)
718
+ ];
719
+
720
+ result = await this.stlManipulator.rotateSTL(
721
+ String(args.stl_path),
722
+ rotationAngles,
723
+ rotateProgressCallback
724
+ );
725
+ break;
726
+
727
+ case "translate_stl":
728
+ if (!args?.stl_path) {
729
+ throw new Error("Missing required parameter: stl_path");
730
+ }
731
+
732
+ // Define progress callback for UI updates
733
+ const translateProgressCallback = (progress: number, message?: string) => {
734
+ console.log(`Translate progress: ${progress}% - ${message || ''}`);
735
+ };
736
+
737
+ // Get translation values, defaulting to 0 for any undefined axis
738
+ const translationValues: [number, number, number] = [
739
+ Number(args.translate_x ?? 0),
740
+ Number(args.translate_y ?? 0),
741
+ Number(args.translate_z ?? 0)
742
+ ];
743
+
744
+ result = await this.stlManipulator.translateSTL(
745
+ String(args.stl_path),
746
+ translationValues,
747
+ translateProgressCallback
748
+ );
749
+ break;
750
+
751
+ case "modify_stl_section":
752
+ if (!args?.stl_path || !args?.section || !args?.transformation_type) {
753
+ throw new Error("Missing required parameters: stl_path, section, and transformation_type");
754
+ }
755
+
756
+ // Define progress callback for UI updates
757
+ const modifySectionProgressCallback = (progress: number, message?: string) => {
758
+ console.log(`Modify section progress: ${progress}% - ${message || ''}`);
759
+ };
760
+
761
+ // Determine the section to modify
762
+ let sectionBox: THREE.Box3 | 'top' | 'bottom' | 'center';
763
+
764
+ if (args.section === 'custom') {
765
+ // Create a custom bounding box from the provided bounds
766
+ if (args.custom_min_x === undefined || args.custom_min_y === undefined ||
767
+ args.custom_min_z === undefined || args.custom_max_x === undefined ||
768
+ args.custom_max_y === undefined || args.custom_max_z === undefined) {
769
+ throw new Error("Custom section requires all min/max bounds to be specified");
770
+ }
771
+
772
+ sectionBox = new THREE.Box3(
773
+ new THREE.Vector3(
774
+ Number(args.custom_min_x),
775
+ Number(args.custom_min_y),
776
+ Number(args.custom_min_z)
777
+ ),
778
+ new THREE.Vector3(
779
+ Number(args.custom_max_x),
780
+ Number(args.custom_max_y),
781
+ Number(args.custom_max_z)
782
+ )
783
+ );
784
+ } else {
785
+ // Use a predefined section
786
+ sectionBox = String(args.section) as 'top' | 'bottom' | 'center';
787
+ }
788
+
789
+ // Determine the transformation to apply
790
+ const transformationType = String(args.transformation_type) as 'scale' | 'rotate' | 'translate';
791
+ let transformationValue: number | number[];
792
+
793
+ if (transformationType === 'scale') {
794
+ if (args.value_x !== undefined || args.value_y !== undefined || args.value_z !== undefined) {
795
+ transformationValue = [
796
+ Number(args.value_x ?? 1.0),
797
+ Number(args.value_y ?? 1.0),
798
+ Number(args.value_z ?? 1.0)
799
+ ];
800
+ } else {
801
+ transformationValue = 1.0; // Default scale factor
802
+ }
803
+ } else if (transformationType === 'rotate') {
804
+ transformationValue = [
805
+ Number(args.value_x ?? 0),
806
+ Number(args.value_y ?? 0),
807
+ Number(args.value_z ?? 0)
808
+ ];
809
+ } else { // translate
810
+ transformationValue = [
811
+ Number(args.value_x ?? 0),
812
+ Number(args.value_y ?? 0),
813
+ Number(args.value_z ?? 0)
814
+ ];
815
+ }
816
+
817
+ result = await this.stlManipulator.modifySection(
818
+ String(args.stl_path),
819
+ sectionBox,
820
+ {
821
+ type: transformationType,
822
+ value: transformationValue
823
+ },
824
+ modifySectionProgressCallback
825
+ );
826
+ break;
827
+
828
+ case "generate_stl_visualization":
829
+ if (!args?.stl_path) {
830
+ throw new Error("Missing required parameter: stl_path");
831
+ }
832
+
833
+ // Define progress callback for UI updates
834
+ const visualizationProgressCallback = (progress: number, message?: string) => {
835
+ console.log(`Visualization progress: ${progress}% - ${message || ''}`);
836
+ };
837
+
838
+ // Get width and height parameters, with defaults
839
+ const width = args.width !== undefined ? Number(args.width) : 300;
840
+ const height = args.height !== undefined ? Number(args.height) : 300;
841
+
842
+ result = await this.stlManipulator.generateVisualization(
843
+ String(args.stl_path),
844
+ width,
845
+ height,
846
+ visualizationProgressCallback
847
+ );
848
+ break;
849
+
265
850
  default:
266
851
  throw new Error(`Unknown tool: ${name}`);
267
852
  }