mcp-3d-printer-server 1.2.1 → 1.2.4
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/README.md +257 -75
- package/dist/fulu-orca.d.ts +20 -0
- package/dist/fulu-orca.js +453 -0
- package/dist/index.js +572 -72
- package/dist/printers/prusa.d.ts +3 -0
- package/dist/printers/prusa.js +33 -1
- package/dist/stl/stl-manipulator.d.ts +11 -2
- package/dist/stl/stl-manipulator.js +162 -26
- package/package.json +23 -3
- package/src/fulu-orca.ts +602 -0
- package/src/index.ts +642 -72
- package/src/printers/prusa.ts +38 -1
- package/src/stl/stl-manipulator.ts +217 -29
- package/dist/http-server.d.ts +0 -16
- package/dist/http-server.js +0 -268
- package/dist/sse-server.d.ts +0 -20
- package/dist/sse-server.js +0 -318
- package/src/http-server.ts +0 -328
- package/src/sse-server.ts +0 -409
- package/src/test/Tape+Card+55mm+Ring.stl +0 -0
package/dist/http-server.js
DELETED
|
@@ -1,268 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
|
-
import express from 'express';
|
|
4
|
-
import cors from 'cors';
|
|
5
|
-
import dotenv from "dotenv";
|
|
6
|
-
import fs from "fs";
|
|
7
|
-
import path from "path";
|
|
8
|
-
import { PrinterFactory } from "./printers/printer-factory.js";
|
|
9
|
-
import { STLManipulator } from "./stl/stl-manipulator.js";
|
|
10
|
-
// Load environment variables from .env file
|
|
11
|
-
dotenv.config();
|
|
12
|
-
// Default values
|
|
13
|
-
const DEFAULT_HOST = process.env.PRINTER_HOST || "localhost";
|
|
14
|
-
const DEFAULT_PORT = process.env.PRINTER_PORT || "80";
|
|
15
|
-
const DEFAULT_API_KEY = process.env.API_KEY || "";
|
|
16
|
-
const DEFAULT_TYPE = process.env.PRINTER_TYPE || "octoprint";
|
|
17
|
-
const TEMP_DIR = process.env.TEMP_DIR || path.join(process.cwd(), "temp");
|
|
18
|
-
// HTTP Server configuration
|
|
19
|
-
const HTTP_PORT = parseInt(process.env.HTTP_PORT || "3001");
|
|
20
|
-
// Slicer configuration
|
|
21
|
-
const DEFAULT_SLICER_TYPE = process.env.SLICER_TYPE || "prusaslicer";
|
|
22
|
-
const DEFAULT_SLICER_PATH = process.env.SLICER_PATH || "";
|
|
23
|
-
const DEFAULT_SLICER_PROFILE = process.env.SLICER_PROFILE || "";
|
|
24
|
-
// Bambu-specific default values
|
|
25
|
-
const DEFAULT_BAMBU_SERIAL = process.env.BAMBU_SERIAL || "";
|
|
26
|
-
const DEFAULT_BAMBU_TOKEN = process.env.BAMBU_TOKEN || "";
|
|
27
|
-
// Ensure temp directory exists
|
|
28
|
-
if (!fs.existsSync(TEMP_DIR)) {
|
|
29
|
-
fs.mkdirSync(TEMP_DIR, { recursive: true });
|
|
30
|
-
}
|
|
31
|
-
class ThreeDPrinterMCPServerHTTP {
|
|
32
|
-
constructor() {
|
|
33
|
-
this.server = new Server({
|
|
34
|
-
name: "mcp-3d-printer-server",
|
|
35
|
-
version: "1.0.0"
|
|
36
|
-
}, {
|
|
37
|
-
capabilities: {
|
|
38
|
-
resources: {},
|
|
39
|
-
tools: {},
|
|
40
|
-
prompts: {}
|
|
41
|
-
}
|
|
42
|
-
});
|
|
43
|
-
this.printerFactory = new PrinterFactory();
|
|
44
|
-
this.stlManipulator = new STLManipulator(TEMP_DIR);
|
|
45
|
-
this.app = express();
|
|
46
|
-
this.setupExpressApp();
|
|
47
|
-
this.setupHandlers();
|
|
48
|
-
this.setupErrorHandling();
|
|
49
|
-
}
|
|
50
|
-
setupExpressApp() {
|
|
51
|
-
// Enable CORS for all origins
|
|
52
|
-
this.app.use(cors({
|
|
53
|
-
origin: '*',
|
|
54
|
-
methods: ['GET', 'POST', 'OPTIONS'],
|
|
55
|
-
allowedHeaders: ['Content-Type', 'Accept'],
|
|
56
|
-
credentials: true
|
|
57
|
-
}));
|
|
58
|
-
// Parse JSON bodies
|
|
59
|
-
this.app.use(express.json({ limit: '50mb' }));
|
|
60
|
-
// Main MCP endpoint - supports both standard and streaming responses
|
|
61
|
-
this.app.post('/mcp', async (req, res) => {
|
|
62
|
-
const acceptHeader = req.get('Accept') || '';
|
|
63
|
-
const wantsStream = acceptHeader.includes('text/event-stream');
|
|
64
|
-
try {
|
|
65
|
-
const requests = Array.isArray(req.body) ? req.body : [req.body];
|
|
66
|
-
const responses = [];
|
|
67
|
-
for (const request of requests) {
|
|
68
|
-
const response = await this.handleJSONRPCRequest(request);
|
|
69
|
-
if (response) {
|
|
70
|
-
responses.push(response);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
// If client wants streaming and we have responses, use SSE format
|
|
74
|
-
if (wantsStream && responses.length > 0) {
|
|
75
|
-
res.setHeader('Content-Type', 'text/event-stream');
|
|
76
|
-
res.setHeader('Cache-Control', 'no-cache');
|
|
77
|
-
res.setHeader('Connection', 'keep-alive');
|
|
78
|
-
for (const response of responses) {
|
|
79
|
-
res.write(`data: ${JSON.stringify(response)}\n\n`);
|
|
80
|
-
}
|
|
81
|
-
res.end();
|
|
82
|
-
}
|
|
83
|
-
else if (responses.length > 0) {
|
|
84
|
-
// Standard JSON response
|
|
85
|
-
res.json(responses.length === 1 ? responses[0] : responses);
|
|
86
|
-
}
|
|
87
|
-
else {
|
|
88
|
-
// No responses (notifications)
|
|
89
|
-
res.status(202).send();
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
catch (error) {
|
|
93
|
-
console.error('Error handling request:', error);
|
|
94
|
-
res.status(500).json({
|
|
95
|
-
jsonrpc: "2.0",
|
|
96
|
-
error: {
|
|
97
|
-
code: -32603,
|
|
98
|
-
message: "Internal error",
|
|
99
|
-
data: error instanceof Error ? error.message : String(error)
|
|
100
|
-
}
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
});
|
|
104
|
-
// GET endpoint for establishing SSE stream (Streamable HTTP)
|
|
105
|
-
this.app.get('/mcp', async (req, res) => {
|
|
106
|
-
const acceptHeader = req.get('Accept') || '';
|
|
107
|
-
if (!acceptHeader.includes('text/event-stream')) {
|
|
108
|
-
res.status(405).json({
|
|
109
|
-
error: 'Method not allowed. Use POST for non-streaming requests.'
|
|
110
|
-
});
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
// Set SSE headers
|
|
114
|
-
res.setHeader('Content-Type', 'text/event-stream');
|
|
115
|
-
res.setHeader('Cache-Control', 'no-cache');
|
|
116
|
-
res.setHeader('Connection', 'keep-alive');
|
|
117
|
-
res.setHeader('X-Accel-Buffering', 'no');
|
|
118
|
-
// Send initial connection event
|
|
119
|
-
res.write(`event: open\ndata: {"type": "connection", "status": "connected"}\n\n`);
|
|
120
|
-
// Keep connection alive with periodic pings
|
|
121
|
-
const pingInterval = setInterval(() => {
|
|
122
|
-
if (!res.writableEnded) {
|
|
123
|
-
res.write(`event: ping\ndata: ${new Date().toISOString()}\n\n`);
|
|
124
|
-
}
|
|
125
|
-
}, 30000);
|
|
126
|
-
// Handle client disconnect
|
|
127
|
-
req.on('close', () => {
|
|
128
|
-
clearInterval(pingInterval);
|
|
129
|
-
res.end();
|
|
130
|
-
});
|
|
131
|
-
});
|
|
132
|
-
// Health check endpoint
|
|
133
|
-
this.app.get('/health', (req, res) => {
|
|
134
|
-
res.json({
|
|
135
|
-
status: 'ok',
|
|
136
|
-
server: 'mcp-3d-printer-server',
|
|
137
|
-
transport: 'streamable-http',
|
|
138
|
-
version: '1.0.0'
|
|
139
|
-
});
|
|
140
|
-
});
|
|
141
|
-
// Root endpoint
|
|
142
|
-
this.app.get('/', (req, res) => {
|
|
143
|
-
res.json({
|
|
144
|
-
name: 'MCP 3D Printer Server (HTTP)',
|
|
145
|
-
version: '1.0.0',
|
|
146
|
-
endpoints: {
|
|
147
|
-
mcp: '/mcp',
|
|
148
|
-
health: '/health'
|
|
149
|
-
},
|
|
150
|
-
transport: 'streamable-http',
|
|
151
|
-
features: ['stdio', 'sse', 'http'],
|
|
152
|
-
documentation: 'https://github.com/DMontgomery40/mcp-3D-printer-server'
|
|
153
|
-
});
|
|
154
|
-
});
|
|
155
|
-
}
|
|
156
|
-
async handleJSONRPCRequest(request) {
|
|
157
|
-
try {
|
|
158
|
-
// Route based on method
|
|
159
|
-
switch (request.method) {
|
|
160
|
-
case 'initialize':
|
|
161
|
-
return {
|
|
162
|
-
jsonrpc: "2.0",
|
|
163
|
-
id: request.id,
|
|
164
|
-
result: {
|
|
165
|
-
protocolVersion: "2025-03-26",
|
|
166
|
-
capabilities: this.server.capabilities,
|
|
167
|
-
serverInfo: {
|
|
168
|
-
name: "mcp-3d-printer-server",
|
|
169
|
-
version: "1.0.0"
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
};
|
|
173
|
-
case 'resources/list':
|
|
174
|
-
const resourcesResult = await this.server.handleRequest(request);
|
|
175
|
-
return {
|
|
176
|
-
jsonrpc: "2.0",
|
|
177
|
-
id: request.id,
|
|
178
|
-
result: resourcesResult
|
|
179
|
-
};
|
|
180
|
-
case 'resources/read':
|
|
181
|
-
const readResult = await this.server.handleRequest(request);
|
|
182
|
-
return {
|
|
183
|
-
jsonrpc: "2.0",
|
|
184
|
-
id: request.id,
|
|
185
|
-
result: readResult
|
|
186
|
-
};
|
|
187
|
-
case 'tools/list':
|
|
188
|
-
const toolsResult = await this.server.handleRequest(request);
|
|
189
|
-
return {
|
|
190
|
-
jsonrpc: "2.0",
|
|
191
|
-
id: request.id,
|
|
192
|
-
result: toolsResult
|
|
193
|
-
};
|
|
194
|
-
case 'tools/call':
|
|
195
|
-
const callResult = await this.server.handleRequest(request);
|
|
196
|
-
return {
|
|
197
|
-
jsonrpc: "2.0",
|
|
198
|
-
id: request.id,
|
|
199
|
-
result: callResult
|
|
200
|
-
};
|
|
201
|
-
default:
|
|
202
|
-
if (request.id) {
|
|
203
|
-
return {
|
|
204
|
-
jsonrpc: "2.0",
|
|
205
|
-
id: request.id,
|
|
206
|
-
error: {
|
|
207
|
-
code: -32601,
|
|
208
|
-
message: `Method not found: ${request.method}`
|
|
209
|
-
}
|
|
210
|
-
};
|
|
211
|
-
}
|
|
212
|
-
return null; // Notification
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
catch (error) {
|
|
216
|
-
if (request.id) {
|
|
217
|
-
return {
|
|
218
|
-
jsonrpc: "2.0",
|
|
219
|
-
id: request.id,
|
|
220
|
-
error: {
|
|
221
|
-
code: -32603,
|
|
222
|
-
message: "Internal error",
|
|
223
|
-
data: error instanceof Error ? error.message : String(error)
|
|
224
|
-
}
|
|
225
|
-
};
|
|
226
|
-
}
|
|
227
|
-
return null;
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
setupErrorHandling() {
|
|
231
|
-
this.server.onerror = (error) => {
|
|
232
|
-
console.error("[MCP Error]", error);
|
|
233
|
-
};
|
|
234
|
-
process.on("SIGINT", async () => {
|
|
235
|
-
console.log("\nShutting down HTTP server...");
|
|
236
|
-
await this.printerFactory.disconnectAll();
|
|
237
|
-
await this.server.close();
|
|
238
|
-
process.exit(0);
|
|
239
|
-
});
|
|
240
|
-
}
|
|
241
|
-
setupHandlers() {
|
|
242
|
-
this.setupResourceHandlers();
|
|
243
|
-
this.setupToolHandlers();
|
|
244
|
-
}
|
|
245
|
-
// Copy all handlers from original index.ts
|
|
246
|
-
setupResourceHandlers() {
|
|
247
|
-
// ... (copy from original)
|
|
248
|
-
}
|
|
249
|
-
setupToolHandlers() {
|
|
250
|
-
// ... (copy from original)
|
|
251
|
-
}
|
|
252
|
-
// Copy all delegating methods from original index.ts
|
|
253
|
-
// ... (copy all printer methods)
|
|
254
|
-
async run() {
|
|
255
|
-
// Start Express server
|
|
256
|
-
this.app.listen(HTTP_PORT, () => {
|
|
257
|
-
console.log(`🚀 MCP 3D Printer Server (HTTP) running on http://localhost:${HTTP_PORT}`);
|
|
258
|
-
console.log(`📡 MCP endpoint: http://localhost:${HTTP_PORT}/mcp`);
|
|
259
|
-
console.log(`✨ Supports both standard HTTP and Server-Sent Events`);
|
|
260
|
-
});
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
// Check if running as main module
|
|
264
|
-
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
265
|
-
const server = new ThreeDPrinterMCPServerHTTP();
|
|
266
|
-
server.run().catch(console.error);
|
|
267
|
-
}
|
|
268
|
-
export default ThreeDPrinterMCPServerHTTP;
|
package/dist/sse-server.d.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
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;
|
package/dist/sse-server.js
DELETED
|
@@ -1,318 +0,0 @@
|
|
|
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;
|