mcp-3d-printer-server 1.0.3 → 1.2.2

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.
@@ -1,93 +1,194 @@
1
+ import { isAxiosError } from "axios";
1
2
  import { PrinterImplementation } from "../types.js";
2
3
  import fs from "fs";
3
4
  import FormData from "form-data";
4
5
  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
6
+ buildAuthHeaders(apiKey) {
7
+ return {
8
+ "X-Api-Key": apiKey,
9
+ Authorization: `Bearer ${apiKey}`,
10
+ };
11
+ }
12
+ buildBaseUrl(host, port) {
13
+ return `http://${host}:${port}`;
14
+ }
15
+ isFallbackStatus(error) {
16
+ if (!isAxiosError(error)) {
17
+ return false;
18
+ }
19
+ const status = error.response?.status;
20
+ return status === 404 || status === 405 || status === 501;
21
+ }
22
+ async getWithFallback(host, port, apiKey, routes) {
23
+ const baseUrl = this.buildBaseUrl(host, port);
24
+ let lastError;
25
+ for (const route of routes) {
26
+ try {
27
+ return await this.apiClient.get(`${baseUrl}${route}`, {
28
+ headers: this.buildAuthHeaders(apiKey),
29
+ });
10
30
  }
11
- });
31
+ catch (error) {
32
+ lastError = error;
33
+ if (!this.isFallbackStatus(error)) {
34
+ throw error;
35
+ }
36
+ }
37
+ }
38
+ throw lastError ?? new Error("No compatible Prusa GET endpoint found.");
39
+ }
40
+ async postWithFallback(host, port, apiKey, candidates, config) {
41
+ const baseUrl = this.buildBaseUrl(host, port);
42
+ let lastError;
43
+ for (const candidate of candidates) {
44
+ try {
45
+ return await this.apiClient.post(`${baseUrl}${candidate.route}`, candidate.data, {
46
+ ...(config ?? {}),
47
+ headers: {
48
+ ...this.buildAuthHeaders(apiKey),
49
+ ...(config?.headers ?? {}),
50
+ },
51
+ });
52
+ }
53
+ catch (error) {
54
+ lastError = error;
55
+ if (!this.isFallbackStatus(error)) {
56
+ throw error;
57
+ }
58
+ }
59
+ }
60
+ throw lastError ?? new Error("No compatible Prusa POST endpoint found.");
61
+ }
62
+ async getStatus(host, port, apiKey) {
63
+ const response = await this.getWithFallback(host, port, apiKey, [
64
+ "/api/v1/printer",
65
+ "/api/printer",
66
+ ]);
12
67
  return response.data;
13
68
  }
14
69
  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
- });
70
+ const response = await this.getWithFallback(host, port, apiKey, [
71
+ "/api/v1/storage",
72
+ "/api/files",
73
+ "/api/files/local",
74
+ ]);
21
75
  return response.data;
22
76
  }
23
77
  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
- });
78
+ const encodedFile = encodeURIComponent(filename);
79
+ const response = await this.getWithFallback(host, port, apiKey, [
80
+ `/api/v1/storage/${encodedFile}`,
81
+ `/api/files/local/${encodedFile}`,
82
+ ]);
30
83
  return response.data;
31
84
  }
32
85
  async uploadFile(host, port, apiKey, filePath, filename, print) {
33
- const url = `http://${host}:${port}/api/v1/storage`;
34
86
  const formData = new FormData();
35
87
  formData.append("file", fs.createReadStream(filePath));
36
88
  formData.append("filename", filename);
37
- const response = await this.apiClient.post(url, formData, {
89
+ const response = await this.postWithFallback(host, port, apiKey, [
90
+ { route: "/api/v1/storage", data: formData },
91
+ { route: "/api/files/local", data: formData },
92
+ ], {
38
93
  headers: {
39
- "X-Api-Key": apiKey,
40
- ...formData.getHeaders()
41
- }
94
+ ...formData.getHeaders(),
95
+ },
42
96
  });
43
- if (print && response.data.success) {
97
+ if (print) {
44
98
  await this.startJob(host, port, apiKey, filename);
45
99
  }
46
100
  return response.data;
47
101
  }
48
102
  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
- });
103
+ const response = await this.postWithFallback(host, port, apiKey, [
104
+ {
105
+ route: "/api/v1/job",
106
+ data: {
107
+ command: "start",
108
+ file: filename,
109
+ },
110
+ },
111
+ {
112
+ route: "/api/v1/job",
113
+ data: {
114
+ command: "start",
115
+ path: filename,
116
+ },
117
+ },
118
+ {
119
+ route: "/api/job",
120
+ data: {
121
+ command: "start",
122
+ file: filename,
123
+ },
124
+ },
125
+ {
126
+ route: "/api/job",
127
+ data: {
128
+ command: "start",
129
+ path: filename,
130
+ },
131
+ },
132
+ ]);
59
133
  return response.data;
60
134
  }
61
135
  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
- });
136
+ const response = await this.postWithFallback(host, port, apiKey, [
137
+ {
138
+ route: "/api/v1/job",
139
+ data: {
140
+ command: "cancel",
141
+ },
142
+ },
143
+ {
144
+ route: "/api/job",
145
+ data: {
146
+ command: "cancel",
147
+ },
148
+ },
149
+ ]);
71
150
  return response.data;
72
151
  }
73
152
  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
153
+ const normalized = component.toLowerCase();
154
+ if (normalized === "bed") {
155
+ const response = await this.postWithFallback(host, port, apiKey, [
156
+ {
157
+ route: "/api/v1/printer/temperature",
158
+ data: {
159
+ command: "set",
160
+ target: { bed: temperature },
161
+ },
162
+ },
163
+ {
164
+ route: "/api/printer/bed",
165
+ data: {
166
+ command: "target",
167
+ target: temperature,
168
+ },
169
+ },
170
+ ]);
171
+ return response.data;
81
172
  }
82
- else {
83
- throw new Error(`Unsupported component: ${component}`);
173
+ if (normalized.startsWith("extruder") || normalized === "nozzle" || normalized === "tool0") {
174
+ const response = await this.postWithFallback(host, port, apiKey, [
175
+ {
176
+ route: "/api/v1/printer/temperature",
177
+ data: {
178
+ command: "set",
179
+ target: { tool0: temperature },
180
+ },
181
+ },
182
+ {
183
+ route: "/api/printer/tool",
184
+ data: {
185
+ command: "target",
186
+ targets: { tool0: temperature },
187
+ },
188
+ },
189
+ ]);
190
+ return response.data;
84
191
  }
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;
192
+ throw new Error(`Unsupported component: ${component}`);
92
193
  }
93
194
  }
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env node
2
+ declare class ThreeDPrinterMCPServerSSE {
3
+ private server;
4
+ private printerFactory;
5
+ private stlManipulator;
6
+ private app;
7
+ private sessions;
8
+ constructor();
9
+ private setupExpressApp;
10
+ private startSessionCleanup;
11
+ setupErrorHandling(): void;
12
+ setupHandlers(): void;
13
+ setupResourceHandlers(): void;
14
+ setupToolHandlers(): void;
15
+ getPrinterStatus(host: string, port?: string, type?: string, apiKey?: string, bambuSerial?: string, bambuToken?: string): Promise<any>;
16
+ getPrinterFiles(host: string, port?: string, type?: string, apiKey?: string, bambuSerial?: string, bambuToken?: string): Promise<any>;
17
+ getPrinterFile(host: string, filename: string, port?: string, type?: string, apiKey?: string, bambuSerial?: string, bambuToken?: string): Promise<any>;
18
+ run(): Promise<void>;
19
+ }
20
+ export default ThreeDPrinterMCPServerSSE;
@@ -0,0 +1,318 @@
1
+ #!/usr/bin/env node
2
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
+ import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
4
+ import express from 'express';
5
+ import cors from 'cors';
6
+ import { v4 as uuidv4 } from 'uuid';
7
+ import { ListResourcesRequestSchema, ReadResourceRequestSchema, ErrorCode, McpError } from "@modelcontextprotocol/sdk/types.js";
8
+ import axios from "axios";
9
+ import dotenv from "dotenv";
10
+ import fs from "fs";
11
+ import path from "path";
12
+ import { PrinterFactory } from "./printers/printer-factory.js";
13
+ import { STLManipulator } from "./stl/stl-manipulator.js";
14
+ // Load environment variables from .env file
15
+ dotenv.config();
16
+ // Default values
17
+ const DEFAULT_HOST = process.env.PRINTER_HOST || "localhost";
18
+ const DEFAULT_PORT = process.env.PRINTER_PORT || "80";
19
+ const DEFAULT_API_KEY = process.env.API_KEY || "";
20
+ const DEFAULT_TYPE = process.env.PRINTER_TYPE || "octoprint";
21
+ const TEMP_DIR = process.env.TEMP_DIR || path.join(process.cwd(), "temp");
22
+ // SSE Server configuration
23
+ const SSE_PORT = parseInt(process.env.SSE_PORT || "3000");
24
+ // Slicer configuration
25
+ const DEFAULT_SLICER_TYPE = process.env.SLICER_TYPE || "prusaslicer";
26
+ const DEFAULT_SLICER_PATH = process.env.SLICER_PATH || "";
27
+ const DEFAULT_SLICER_PROFILE = process.env.SLICER_PROFILE || "";
28
+ // Bambu-specific default values
29
+ const DEFAULT_BAMBU_SERIAL = process.env.BAMBU_SERIAL || "";
30
+ const DEFAULT_BAMBU_TOKEN = process.env.BAMBU_TOKEN || "";
31
+ // Ensure temp directory exists
32
+ if (!fs.existsSync(TEMP_DIR)) {
33
+ fs.mkdirSync(TEMP_DIR, { recursive: true });
34
+ }
35
+ class ThreeDPrinterMCPServerSSE {
36
+ constructor() {
37
+ this.sessions = new Map();
38
+ this.server = new Server({
39
+ name: "mcp-3d-printer-server",
40
+ version: "1.0.0"
41
+ }, {
42
+ capabilities: {
43
+ resources: {},
44
+ tools: {}
45
+ }
46
+ });
47
+ this.printerFactory = new PrinterFactory();
48
+ this.stlManipulator = new STLManipulator(TEMP_DIR);
49
+ this.app = express();
50
+ this.setupExpressApp();
51
+ this.setupHandlers();
52
+ this.setupErrorHandling();
53
+ this.startSessionCleanup();
54
+ }
55
+ setupExpressApp() {
56
+ // Enable CORS for all origins
57
+ this.app.use(cors({
58
+ origin: '*',
59
+ methods: ['GET', 'POST', 'OPTIONS'],
60
+ allowedHeaders: ['Content-Type', 'Accept', 'Last-Event-ID'],
61
+ credentials: true
62
+ }));
63
+ // Parse JSON bodies
64
+ this.app.use(express.json({ limit: '50mb' }));
65
+ // SSE endpoint - establishes the event stream
66
+ this.app.get('/sse', async (req, res) => {
67
+ const sessionId = uuidv4();
68
+ const messagesPath = `/messages?session_id=${sessionId}`;
69
+ // Set SSE headers
70
+ res.setHeader('Content-Type', 'text/event-stream');
71
+ res.setHeader('Cache-Control', 'no-cache');
72
+ res.setHeader('Connection', 'keep-alive');
73
+ res.setHeader('X-Accel-Buffering', 'no');
74
+ // Send initial endpoint event
75
+ res.write(`event: endpoint\ndata: ${messagesPath}\n\n`);
76
+ // Create transport for this session
77
+ const transport = new SSEServerTransport(messagesPath);
78
+ this.sessions.set(sessionId, { transport, lastActivity: Date.now() });
79
+ // Send periodic ping events to keep connection alive
80
+ const pingInterval = setInterval(() => {
81
+ if (!res.writableEnded) {
82
+ res.write(`event: ping\ndata: ${new Date().toISOString()}\n\n`);
83
+ }
84
+ }, 30000);
85
+ // Handle client disconnect
86
+ req.on('close', () => {
87
+ clearInterval(pingInterval);
88
+ this.sessions.delete(sessionId);
89
+ res.end();
90
+ });
91
+ // Handle transport errors
92
+ transport.onerror = (error) => {
93
+ console.error(`SSE Transport error for session ${sessionId}:`, error);
94
+ clearInterval(pingInterval);
95
+ this.sessions.delete(sessionId);
96
+ res.end();
97
+ };
98
+ });
99
+ // Messages endpoint - handles JSON-RPC messages
100
+ this.app.post('/messages', async (req, res) => {
101
+ const sessionId = req.query.session_id;
102
+ if (!sessionId || !this.sessions.has(sessionId)) {
103
+ res.status(400).json({
104
+ jsonrpc: "2.0",
105
+ error: {
106
+ code: -32600,
107
+ message: "Invalid or missing session ID"
108
+ }
109
+ });
110
+ return;
111
+ }
112
+ const session = this.sessions.get(sessionId);
113
+ session.lastActivity = Date.now();
114
+ try {
115
+ // Handle the message through the transport
116
+ const result = await session.transport.handleMessage(req.body);
117
+ if (result) {
118
+ res.json(result);
119
+ }
120
+ else {
121
+ res.status(202).send(); // Accepted, no response needed
122
+ }
123
+ }
124
+ catch (error) {
125
+ console.error('Error handling message:', error);
126
+ res.status(500).json({
127
+ jsonrpc: "2.0",
128
+ error: {
129
+ code: -32603,
130
+ message: "Internal error",
131
+ data: error instanceof Error ? error.message : String(error)
132
+ }
133
+ });
134
+ }
135
+ });
136
+ // Health check endpoint
137
+ this.app.get('/health', (req, res) => {
138
+ res.json({
139
+ status: 'ok',
140
+ server: 'mcp-3d-printer-server',
141
+ transport: 'sse',
142
+ sessions: this.sessions.size
143
+ });
144
+ });
145
+ // Root endpoint
146
+ this.app.get('/', (req, res) => {
147
+ res.json({
148
+ name: 'MCP 3D Printer Server (SSE)',
149
+ version: '1.0.0',
150
+ endpoints: {
151
+ sse: '/sse',
152
+ messages: '/messages',
153
+ health: '/health'
154
+ },
155
+ transport: 'sse',
156
+ documentation: 'https://github.com/DMontgomery40/mcp-3D-printer-server'
157
+ });
158
+ });
159
+ }
160
+ startSessionCleanup() {
161
+ // Clean up inactive sessions every 5 minutes
162
+ setInterval(() => {
163
+ const now = Date.now();
164
+ const timeout = 10 * 60 * 1000; // 10 minutes
165
+ for (const [sessionId, session] of this.sessions.entries()) {
166
+ if (now - session.lastActivity > timeout) {
167
+ console.log(`Cleaning up inactive session: ${sessionId}`);
168
+ this.sessions.delete(sessionId);
169
+ }
170
+ }
171
+ }, 5 * 60 * 1000);
172
+ }
173
+ setupErrorHandling() {
174
+ this.server.onerror = (error) => {
175
+ console.error("[MCP Error]", error);
176
+ };
177
+ process.on("SIGINT", async () => {
178
+ console.log("\nShutting down SSE server...");
179
+ // Disconnect all printers
180
+ await this.printerFactory.disconnectAll();
181
+ // Close all sessions
182
+ for (const [sessionId, _] of this.sessions.entries()) {
183
+ this.sessions.delete(sessionId);
184
+ }
185
+ await this.server.close();
186
+ process.exit(0);
187
+ });
188
+ }
189
+ setupHandlers() {
190
+ this.setupResourceHandlers();
191
+ this.setupToolHandlers();
192
+ }
193
+ setupResourceHandlers() {
194
+ // List available resources
195
+ this.server.setRequestHandler(ListResourcesRequestSchema, async () => {
196
+ return {
197
+ resources: [
198
+ {
199
+ uri: `printer://${DEFAULT_HOST}/status`,
200
+ name: "3D Printer Status",
201
+ mimeType: "application/json",
202
+ description: "Current status of the 3D printer including temperatures, print progress, and more"
203
+ },
204
+ {
205
+ uri: `printer://${DEFAULT_HOST}/files`,
206
+ name: "3D Printer Files",
207
+ mimeType: "application/json",
208
+ description: "List of files available on the 3D printer"
209
+ }
210
+ ],
211
+ templates: [
212
+ {
213
+ uriTemplate: "printer://{host}/status",
214
+ name: "3D Printer Status",
215
+ mimeType: "application/json"
216
+ },
217
+ {
218
+ uriTemplate: "printer://{host}/files",
219
+ name: "3D Printer Files",
220
+ mimeType: "application/json"
221
+ },
222
+ {
223
+ uriTemplate: "printer://{host}/file/{filename}",
224
+ name: "3D Printer File Content",
225
+ mimeType: "application/gcode"
226
+ }
227
+ ]
228
+ };
229
+ });
230
+ // Read resource
231
+ this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
232
+ const uri = request.params.uri;
233
+ const match = uri.match(/^printer:\/\/([^\/]+)\/(.+)$/);
234
+ if (!match) {
235
+ throw new McpError(ErrorCode.InvalidRequest, `Invalid resource URI: ${uri}`);
236
+ }
237
+ const [, host, resource] = match;
238
+ let content;
239
+ try {
240
+ if (resource === "status") {
241
+ content = await this.getPrinterStatus(host);
242
+ }
243
+ else if (resource === "files") {
244
+ content = await this.getPrinterFiles(host);
245
+ }
246
+ else if (resource.startsWith("file/")) {
247
+ const filename = resource.substring(5);
248
+ content = await this.getPrinterFile(host, filename);
249
+ }
250
+ else {
251
+ throw new McpError(ErrorCode.InvalidRequest, `Unknown resource: ${resource}`);
252
+ }
253
+ return {
254
+ contents: [
255
+ {
256
+ uri,
257
+ mimeType: resource.startsWith("file/") ? "application/gcode" : "application/json",
258
+ text: typeof content === "string" ? content : JSON.stringify(content, null, 2)
259
+ }
260
+ ]
261
+ };
262
+ }
263
+ catch (error) {
264
+ if (axios.isAxiosError(error)) {
265
+ throw new McpError(ErrorCode.InternalError, `API error: ${error.response?.data?.error || error.message}`);
266
+ }
267
+ throw error;
268
+ }
269
+ });
270
+ }
271
+ setupToolHandlers() {
272
+ // Tool handlers remain the same as in the stdio version
273
+ // ... (copy all tool handlers from the original index.ts)
274
+ }
275
+ // Delegating methods to printer implementations (same as original)
276
+ async getPrinterStatus(host, port = DEFAULT_PORT, type = DEFAULT_TYPE, apiKey = DEFAULT_API_KEY, bambuSerial = DEFAULT_BAMBU_SERIAL, bambuToken = DEFAULT_BAMBU_TOKEN) {
277
+ const implementation = this.printerFactory.getImplementation(type);
278
+ if (type.toLowerCase() === "bambu") {
279
+ const bambuApiKey = `${bambuSerial}:${bambuToken}`;
280
+ return implementation.getStatus(host, port, bambuApiKey);
281
+ }
282
+ return implementation.getStatus(host, port, apiKey);
283
+ }
284
+ async getPrinterFiles(host, port = DEFAULT_PORT, type = DEFAULT_TYPE, apiKey = DEFAULT_API_KEY, bambuSerial = DEFAULT_BAMBU_SERIAL, bambuToken = DEFAULT_BAMBU_TOKEN) {
285
+ const implementation = this.printerFactory.getImplementation(type);
286
+ if (type.toLowerCase() === "bambu") {
287
+ const bambuApiKey = `${bambuSerial}:${bambuToken}`;
288
+ return implementation.getFiles(host, port, bambuApiKey);
289
+ }
290
+ return implementation.getFiles(host, port, apiKey);
291
+ }
292
+ async getPrinterFile(host, filename, port = DEFAULT_PORT, type = DEFAULT_TYPE, apiKey = DEFAULT_API_KEY, bambuSerial = DEFAULT_BAMBU_SERIAL, bambuToken = DEFAULT_BAMBU_TOKEN) {
293
+ const implementation = this.printerFactory.getImplementation(type);
294
+ if (type.toLowerCase() === "bambu") {
295
+ return implementation.getFile(host, port, apiKey, bambuSerial, bambuToken, filename);
296
+ }
297
+ return implementation.getFile(host, port, apiKey, filename);
298
+ }
299
+ // ... (copy all other delegating methods from original index.ts)
300
+ async run() {
301
+ // Connect SSE transports to the server
302
+ for (const [sessionId, session] of this.sessions.entries()) {
303
+ await this.server.connect(session.transport);
304
+ }
305
+ // Start Express server
306
+ this.app.listen(SSE_PORT, () => {
307
+ console.log(`🚀 MCP 3D Printer Server (SSE) running on http://localhost:${SSE_PORT}`);
308
+ console.log(`📡 SSE endpoint: http://localhost:${SSE_PORT}/sse`);
309
+ console.log(`💬 Messages endpoint: http://localhost:${SSE_PORT}/messages`);
310
+ });
311
+ }
312
+ }
313
+ // Check if running as main module
314
+ if (import.meta.url === `file://${process.argv[1]}`) {
315
+ const server = new ThreeDPrinterMCPServerSSE();
316
+ server.run().catch(console.error);
317
+ }
318
+ export default ThreeDPrinterMCPServerSSE;
@@ -90,15 +90,15 @@ export declare class STLManipulator extends EventEmitter {
90
90
  */
91
91
  extendBase(stlFilePath: string, extensionInches: number, progressCallback?: ProgressCallback): Promise<string>;
92
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)
93
+ * Slice an STL or 3MF file using the specified slicer
94
+ * @param stlFilePath Path to the input STL or 3MF file
95
+ * @param slicerType Type of slicer (prusaslicer, cura, slic3r, orcaslicer, bambustudio)
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
- * @returns Path to the generated G-code file
99
+ * @returns Path to the generated G-code or sliced 3MF file
100
100
  */
101
- sliceSTL(stlFilePath: string, slicerType: 'prusaslicer' | 'cura' | 'slic3r' | 'orcaslicer', slicerPath: string, slicerProfile?: string, progressCallback?: ProgressCallback): Promise<string>;
101
+ sliceSTL(stlFilePath: string, slicerType: 'prusaslicer' | 'cura' | 'slic3r' | 'orcaslicer' | 'bambustudio', slicerPath: string, slicerProfile?: string, progressCallback?: ProgressCallback, printerPreset?: string): Promise<string>;
102
102
  /**
103
103
  * Enhanced version of confirmTemperatures with better error handling
104
104
  * @param gcodePath Path to the G-code file