mcp-3d-printer-server 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.
Files changed (41) hide show
  1. package/dist/3mf_parser.d.ts +3 -0
  2. package/dist/3mf_parser.js +216 -0
  3. package/dist/index.d.ts +2 -0
  4. package/dist/index.js +1018 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/printers/bambu.d.ts +42 -0
  7. package/dist/printers/bambu.js +278 -0
  8. package/dist/printers/creality.d.ts +10 -0
  9. package/dist/printers/creality.js +90 -0
  10. package/dist/printers/duet.d.ts +10 -0
  11. package/dist/printers/duet.js +66 -0
  12. package/dist/printers/klipper.d.ts +10 -0
  13. package/dist/printers/klipper.js +62 -0
  14. package/dist/printers/octoprint.d.ts +10 -0
  15. package/dist/printers/octoprint.js +97 -0
  16. package/dist/printers/printer-factory.d.ts +9 -0
  17. package/dist/printers/printer-factory.js +33 -0
  18. package/dist/printers/prusa.d.ts +10 -0
  19. package/dist/printers/prusa.js +93 -0
  20. package/dist/printers/repetier.d.ts +10 -0
  21. package/dist/printers/repetier.js +59 -0
  22. package/dist/stl/stl-manipulator.d.ts +140 -0
  23. package/dist/stl/stl-manipulator.js +1081 -0
  24. package/dist/test/stl-manipulator-test.d.ts +2 -0
  25. package/dist/test/stl-manipulator-test.js +91 -0
  26. package/dist/types.d.ts +75 -0
  27. package/dist/types.js +26 -0
  28. package/package.json +9 -3
  29. package/.dockerignore +0 -52
  30. package/.env.example +0 -17
  31. package/Dockerfile +0 -31
  32. package/docker-compose.yml +0 -31
  33. package/scripts/docker-run.sh +0 -25
  34. package/scripts/mcp-inspector.js +0 -49
  35. package/test/sample_cube.stl +0 -86
  36. package/test/sample_cube_extended.stl +0 -172
  37. package/test/sample_cube_modified.stl +0 -86
  38. package/test/sample_cube_rotated.stl +0 -86
  39. package/test/sample_cube_scaled.stl +0 -86
  40. package/test/sample_cube_translated.stl +0 -86
  41. package/tsconfig.json +0 -16
@@ -0,0 +1,97 @@
1
+ import { PrinterImplementation } from "../types.js";
2
+ import fs from "fs";
3
+ import FormData from "form-data";
4
+ export class OctoPrintImplementation extends PrinterImplementation {
5
+ async getStatus(host, port, apiKey) {
6
+ const url = `http://${host}:${port}/api/printer`;
7
+ const response = await this.apiClient.get(url, {
8
+ headers: {
9
+ "X-Api-Key": apiKey
10
+ }
11
+ });
12
+ return response.data;
13
+ }
14
+ async getFiles(host, port, apiKey) {
15
+ const url = `http://${host}:${port}/api/files`;
16
+ const response = await this.apiClient.get(url, {
17
+ headers: {
18
+ "X-Api-Key": apiKey
19
+ }
20
+ });
21
+ return response.data;
22
+ }
23
+ async getFile(host, port, apiKey, filename) {
24
+ const url = `http://${host}:${port}/api/files/local/${filename}`;
25
+ const response = await this.apiClient.get(url, {
26
+ headers: {
27
+ "X-Api-Key": apiKey
28
+ }
29
+ });
30
+ return response.data;
31
+ }
32
+ async uploadFile(host, port, apiKey, filePath, filename, print) {
33
+ const url = `http://${host}:${port}/api/files/local`;
34
+ const formData = new FormData();
35
+ formData.append("file", fs.createReadStream(filePath));
36
+ formData.append("filename", filename);
37
+ if (print) {
38
+ formData.append("print", "true");
39
+ }
40
+ const response = await this.apiClient.post(url, formData, {
41
+ headers: {
42
+ "X-Api-Key": apiKey,
43
+ ...formData.getHeaders()
44
+ }
45
+ });
46
+ return response.data;
47
+ }
48
+ async startJob(host, port, apiKey, filename) {
49
+ const url = `http://${host}:${port}/api/files/local/${filename}`;
50
+ const response = await this.apiClient.post(url, {
51
+ command: "select",
52
+ print: true
53
+ }, {
54
+ headers: {
55
+ "X-Api-Key": apiKey,
56
+ "Content-Type": "application/json"
57
+ }
58
+ });
59
+ return response.data;
60
+ }
61
+ async cancelJob(host, port, apiKey) {
62
+ const url = `http://${host}:${port}/api/job`;
63
+ const response = await this.apiClient.post(url, {
64
+ command: "cancel"
65
+ }, {
66
+ headers: {
67
+ "X-Api-Key": apiKey,
68
+ "Content-Type": "application/json"
69
+ }
70
+ });
71
+ return response.data;
72
+ }
73
+ async setTemperature(host, port, apiKey, component, temperature) {
74
+ let url = `http://${host}:${port}/api/printer/tool`;
75
+ const data = {};
76
+ if (component === "bed") {
77
+ data.command = "target";
78
+ data.target = temperature;
79
+ url = `http://${host}:${port}/api/printer/bed`;
80
+ }
81
+ else if (component.startsWith("extruder")) {
82
+ data.command = "target";
83
+ data.targets = {};
84
+ data.targets[component] = temperature;
85
+ }
86
+ else {
87
+ throw new Error(`Unsupported component: ${component}`);
88
+ }
89
+ const response = await this.apiClient.post(url, data, {
90
+ headers: {
91
+ "X-Api-Key": apiKey,
92
+ "Content-Type": "application/json"
93
+ }
94
+ });
95
+ return response.data;
96
+ }
97
+ }
@@ -0,0 +1,9 @@
1
+ import { PrinterImplementation } from "../types.js";
2
+ export declare class PrinterFactory {
3
+ private implementations;
4
+ private bambuPrinterStore;
5
+ private apiClient;
6
+ constructor();
7
+ getImplementation(type: string): PrinterImplementation;
8
+ disconnectAll(): Promise<void>;
9
+ }
@@ -0,0 +1,33 @@
1
+ import { BambuPrinterStore } from "../types.js";
2
+ import { OctoPrintImplementation } from "./octoprint.js";
3
+ import { KlipperImplementation } from "./klipper.js";
4
+ import { DuetImplementation } from "./duet.js";
5
+ import { RepetierImplementation } from "./repetier.js";
6
+ import { BambuImplementation } from "./bambu.js";
7
+ import { PrusaImplementation } from "./prusa.js";
8
+ import { CrealityImplementation } from "./creality.js";
9
+ import axios from "axios";
10
+ export class PrinterFactory {
11
+ constructor() {
12
+ this.implementations = new Map();
13
+ this.bambuPrinterStore = new BambuPrinterStore();
14
+ this.apiClient = axios.create({ timeout: 10000 });
15
+ this.implementations.set("octoprint", new OctoPrintImplementation(this.apiClient));
16
+ this.implementations.set("klipper", new KlipperImplementation(this.apiClient));
17
+ this.implementations.set("duet", new DuetImplementation(this.apiClient));
18
+ this.implementations.set("repetier", new RepetierImplementation(this.apiClient));
19
+ this.implementations.set("bambu", new BambuImplementation(this.apiClient, this.bambuPrinterStore));
20
+ this.implementations.set("prusa", new PrusaImplementation(this.apiClient));
21
+ this.implementations.set("creality", new CrealityImplementation(this.apiClient));
22
+ }
23
+ getImplementation(type) {
24
+ const implementation = this.implementations.get(type.toLowerCase());
25
+ if (!implementation) {
26
+ throw new Error(`Unsupported printer type: ${type}`);
27
+ }
28
+ return implementation;
29
+ }
30
+ async disconnectAll() {
31
+ await this.bambuPrinterStore.disconnectAll();
32
+ }
33
+ }
@@ -0,0 +1,10 @@
1
+ import { PrinterImplementation } from "../types.js";
2
+ export declare class PrusaImplementation extends PrinterImplementation {
3
+ getStatus(host: string, port: string, apiKey: string): Promise<any>;
4
+ getFiles(host: string, port: string, apiKey: string): Promise<any>;
5
+ getFile(host: string, port: string, apiKey: string, filename: string): Promise<any>;
6
+ uploadFile(host: string, port: string, apiKey: string, filePath: string, filename: string, print: boolean): Promise<any>;
7
+ startJob(host: string, port: string, apiKey: string, filename: string): Promise<any>;
8
+ cancelJob(host: string, port: string, apiKey: string): Promise<any>;
9
+ setTemperature(host: string, port: string, apiKey: string, component: string, temperature: number): Promise<any>;
10
+ }
@@ -0,0 +1,93 @@
1
+ import { PrinterImplementation } from "../types.js";
2
+ import fs from "fs";
3
+ import FormData from "form-data";
4
+ export class PrusaImplementation extends PrinterImplementation {
5
+ async getStatus(host, port, apiKey) {
6
+ const url = `http://${host}:${port}/api/v1/printer`;
7
+ const response = await this.apiClient.get(url, {
8
+ headers: {
9
+ "X-Api-Key": apiKey
10
+ }
11
+ });
12
+ return response.data;
13
+ }
14
+ async getFiles(host, port, apiKey) {
15
+ const url = `http://${host}:${port}/api/v1/storage`;
16
+ const response = await this.apiClient.get(url, {
17
+ headers: {
18
+ "X-Api-Key": apiKey
19
+ }
20
+ });
21
+ return response.data;
22
+ }
23
+ async getFile(host, port, apiKey, filename) {
24
+ const url = `http://${host}:${port}/api/v1/storage/${encodeURIComponent(filename)}`;
25
+ const response = await this.apiClient.get(url, {
26
+ headers: {
27
+ "X-Api-Key": apiKey
28
+ }
29
+ });
30
+ return response.data;
31
+ }
32
+ async uploadFile(host, port, apiKey, filePath, filename, print) {
33
+ const url = `http://${host}:${port}/api/v1/storage`;
34
+ const formData = new FormData();
35
+ formData.append("file", fs.createReadStream(filePath));
36
+ formData.append("filename", filename);
37
+ const response = await this.apiClient.post(url, formData, {
38
+ headers: {
39
+ "X-Api-Key": apiKey,
40
+ ...formData.getHeaders()
41
+ }
42
+ });
43
+ if (print && response.data.success) {
44
+ await this.startJob(host, port, apiKey, filename);
45
+ }
46
+ return response.data;
47
+ }
48
+ async startJob(host, port, apiKey, filename) {
49
+ const url = `http://${host}:${port}/api/v1/job`;
50
+ const response = await this.apiClient.post(url, {
51
+ command: "start",
52
+ file: filename
53
+ }, {
54
+ headers: {
55
+ "X-Api-Key": apiKey,
56
+ "Content-Type": "application/json"
57
+ }
58
+ });
59
+ return response.data;
60
+ }
61
+ async cancelJob(host, port, apiKey) {
62
+ const url = `http://${host}:${port}/api/v1/job`;
63
+ const response = await this.apiClient.post(url, {
64
+ command: "cancel"
65
+ }, {
66
+ headers: {
67
+ "X-Api-Key": apiKey,
68
+ "Content-Type": "application/json"
69
+ }
70
+ });
71
+ return response.data;
72
+ }
73
+ async setTemperature(host, port, apiKey, component, temperature) {
74
+ const url = `http://${host}:${port}/api/v1/printer/temperature`;
75
+ let data = { command: "set" };
76
+ if (component === "bed") {
77
+ data.target = { bed: temperature };
78
+ }
79
+ else if (component.startsWith("extruder")) {
80
+ data.target = { tool0: temperature }; // Prusa typically uses tool0 for the extruder
81
+ }
82
+ else {
83
+ throw new Error(`Unsupported component: ${component}`);
84
+ }
85
+ const response = await this.apiClient.post(url, data, {
86
+ headers: {
87
+ "X-Api-Key": apiKey,
88
+ "Content-Type": "application/json"
89
+ }
90
+ });
91
+ return response.data;
92
+ }
93
+ }
@@ -0,0 +1,10 @@
1
+ import { PrinterImplementation } from "../types.js";
2
+ export declare class RepetierImplementation extends PrinterImplementation {
3
+ getStatus(host: string, port: string, apiKey: string): Promise<any>;
4
+ getFiles(host: string, port: string, apiKey: string): Promise<any>;
5
+ getFile(host: string, port: string, apiKey: string, filename: string): Promise<any>;
6
+ uploadFile(host: string, port: string, apiKey: string, filePath: string, filename: string, print: boolean): Promise<any>;
7
+ startJob(host: string, port: string, apiKey: string, filename: string): Promise<any>;
8
+ cancelJob(host: string, port: string, apiKey: string): Promise<any>;
9
+ setTemperature(host: string, port: string, apiKey: string, component: string, temperature: number): Promise<any>;
10
+ }
@@ -0,0 +1,59 @@
1
+ import { PrinterImplementation } from "../types.js";
2
+ import fs from "fs";
3
+ import FormData from "form-data";
4
+ export class RepetierImplementation extends PrinterImplementation {
5
+ async getStatus(host, port, apiKey) {
6
+ const url = `http://${host}:${port}/printer/api/?a=getPrinterInfo&apikey=${apiKey}`;
7
+ const response = await this.apiClient.get(url);
8
+ return response.data;
9
+ }
10
+ async getFiles(host, port, apiKey) {
11
+ const url = `http://${host}:${port}/printer/api/?a=ls&apikey=${apiKey}`;
12
+ const response = await this.apiClient.get(url);
13
+ return response.data;
14
+ }
15
+ async getFile(host, port, apiKey, filename) {
16
+ const url = `http://${host}:${port}/printer/api/?a=getFileInfo&apikey=${apiKey}&filename=${encodeURIComponent(filename)}`;
17
+ const response = await this.apiClient.get(url);
18
+ return response.data;
19
+ }
20
+ async uploadFile(host, port, apiKey, filePath, filename, print) {
21
+ const url = `http://${host}:${port}/printer/api/`;
22
+ const formData = new FormData();
23
+ formData.append("a", "upload");
24
+ formData.append("apikey", apiKey);
25
+ formData.append("filename", filename);
26
+ formData.append("print", print ? "1" : "0");
27
+ formData.append("file", fs.createReadStream(filePath));
28
+ const response = await this.apiClient.post(url, formData, {
29
+ headers: {
30
+ ...formData.getHeaders()
31
+ }
32
+ });
33
+ return response.data;
34
+ }
35
+ async startJob(host, port, apiKey, filename) {
36
+ const url = `http://${host}:${port}/printer/api/?a=startJob&apikey=${apiKey}&filename=${encodeURIComponent(filename)}`;
37
+ const response = await this.apiClient.get(url);
38
+ return response.data;
39
+ }
40
+ async cancelJob(host, port, apiKey) {
41
+ const url = `http://${host}:${port}/printer/api/?a=stopJob&apikey=${apiKey}`;
42
+ const response = await this.apiClient.get(url);
43
+ return response.data;
44
+ }
45
+ async setTemperature(host, port, apiKey, component, temperature) {
46
+ let url;
47
+ if (component === "bed") {
48
+ url = `http://${host}:${port}/printer/api/?a=setBedTemp&apikey=${apiKey}&temp=${temperature}`;
49
+ }
50
+ else if (component === "extruder") {
51
+ url = `http://${host}:${port}/printer/api/?a=setExtruderTemp&apikey=${apiKey}&temp=${temperature}`;
52
+ }
53
+ else {
54
+ throw new Error(`Unsupported component: ${component}`);
55
+ }
56
+ const response = await this.apiClient.get(url);
57
+ return response.data;
58
+ }
59
+ }
@@ -0,0 +1,140 @@
1
+ import * as THREE from 'three';
2
+ import { EventEmitter } from 'events';
3
+ export type ProgressCallback = (progress: number, message?: string) => void;
4
+ export type OperationResult = {
5
+ success: boolean;
6
+ filePath?: string;
7
+ error?: string;
8
+ operationId: string;
9
+ };
10
+ export type TransformationType = 'scale' | 'rotate' | 'translate' | 'extendBase' | 'customModify';
11
+ export type TransformationAxis = 'x' | 'y' | 'z' | 'all';
12
+ export type BoundingBox = {
13
+ min: THREE.Vector3;
14
+ max: THREE.Vector3;
15
+ center: THREE.Vector3;
16
+ dimensions: THREE.Vector3;
17
+ };
18
+ export type TransformationParams = {
19
+ type: TransformationType;
20
+ axis?: TransformationAxis;
21
+ value: number | number[];
22
+ relative?: boolean;
23
+ selectionBounds?: THREE.Box3;
24
+ };
25
+ export declare class STLManipulator extends EventEmitter {
26
+ private tempDir;
27
+ private activeOperations;
28
+ constructor(tempDir?: string);
29
+ /**
30
+ * Generate a unique operation ID
31
+ */
32
+ private generateOperationId;
33
+ /**
34
+ * Load STL file and return geometry and bounding box
35
+ */
36
+ private loadSTL;
37
+ /**
38
+ * Save a geometry to STL file
39
+ */
40
+ private saveSTL;
41
+ /**
42
+ * Get comprehensive information about an STL file
43
+ */
44
+ getSTLInfo(stlFilePath: string): Promise<{
45
+ filePath: string;
46
+ fileName: string;
47
+ fileSize: number;
48
+ boundingBox: BoundingBox;
49
+ vertexCount: number;
50
+ faceCount: number;
51
+ }>;
52
+ /**
53
+ * Scale an STL model uniformly or along specific axes
54
+ */
55
+ scaleSTL(stlFilePath: string, scaleFactors: number | [number, number, number], progressCallback?: ProgressCallback): Promise<string>;
56
+ /**
57
+ * Rotate an STL model around specific axes
58
+ */
59
+ rotateSTL(stlFilePath: string, rotationAngles: [number, number, number], // [x, y, z] in degrees
60
+ progressCallback?: ProgressCallback): Promise<string>;
61
+ /**
62
+ * Translate (move) an STL model along specific axes
63
+ */
64
+ translateSTL(stlFilePath: string, translationValues: [number, number, number], // [x, y, z] in mm
65
+ progressCallback?: ProgressCallback): Promise<string>;
66
+ /**
67
+ * Cancel an ongoing operation
68
+ */
69
+ cancelOperation(operationId: string): boolean;
70
+ /**
71
+ * Generate an SVG visualization of an STL file from multiple angles
72
+ * @param stlFilePath Path to the STL file
73
+ * @param width Width of each view in pixels
74
+ * @param height Height of each view in pixels
75
+ * @param progressCallback Optional callback for progress updates
76
+ * @returns Path to the generated SVG file
77
+ */
78
+ generateVisualization(stlFilePath: string, width?: number, height?: number, progressCallback?: ProgressCallback): Promise<string>;
79
+ /**
80
+ * Apply a specific transformation to a selected section of an STL file
81
+ * This allows for targeted modifications of specific parts of a model
82
+ */
83
+ modifySection(stlFilePath: string, selection: THREE.Box3 | 'top' | 'bottom' | 'center', transformation: TransformationParams, progressCallback?: ProgressCallback): Promise<string>;
84
+ /**
85
+ * Enhanced version of extendBase with progress reporting
86
+ * @param stlFilePath Path to the input STL file
87
+ * @param extensionInches Amount to extend base in inches
88
+ * @param progressCallback Optional callback for progress updates
89
+ * @returns Path to the modified STL file
90
+ */
91
+ extendBase(stlFilePath: string, extensionInches: number, progressCallback?: ProgressCallback): Promise<string>;
92
+ /**
93
+ * Slice an STL file using the specified slicer
94
+ * @param stlFilePath Path to the input STL file
95
+ * @param slicerType Type of slicer (prusaslicer, cura, slic3r, orcaslicer)
96
+ * @param slicerPath Path to the slicer executable
97
+ * @param slicerProfile Optional path to the slicer profile/config file
98
+ * @param progressCallback Optional callback for progress updates
99
+ * @returns Path to the generated G-code file
100
+ */
101
+ sliceSTL(stlFilePath: string, slicerType: 'prusaslicer' | 'cura' | 'slic3r' | 'orcaslicer', slicerPath: string, slicerProfile?: string, progressCallback?: ProgressCallback): Promise<string>;
102
+ /**
103
+ * Enhanced version of confirmTemperatures with better error handling
104
+ * @param gcodePath Path to the G-code file
105
+ * @param expected Expected temperature settings
106
+ * @param progressCallback Optional callback for progress updates
107
+ * @returns Object with comparison results
108
+ */
109
+ confirmTemperatures(gcodePath: string, expected: {
110
+ extruder?: number;
111
+ bed?: number;
112
+ }, progressCallback?: ProgressCallback): Promise<{
113
+ match: boolean;
114
+ actual: {
115
+ extruder?: number;
116
+ bed?: number;
117
+ };
118
+ expected: {
119
+ extruder?: number;
120
+ bed?: number;
121
+ };
122
+ allTemperatures: {
123
+ extruder: number[];
124
+ bed: number[];
125
+ };
126
+ }>;
127
+ /**
128
+ * Merge vertices within a specified tolerance.
129
+ */
130
+ mergeVertices(stlFilePath: string, tolerance?: number, // Default tolerance in mm
131
+ progressCallback?: ProgressCallback): Promise<string>;
132
+ /**
133
+ * Center the model at the origin (0,0,0).
134
+ */
135
+ centerModel(stlFilePath: string, progressCallback?: ProgressCallback): Promise<string>;
136
+ /**
137
+ * Rotate the model so its largest flat face lies on the XY plane (Z=0).
138
+ */
139
+ layFlat(stlFilePath: string, progressCallback?: ProgressCallback): Promise<string>;
140
+ }