mcp-3d-printer-server 1.0.2 → 1.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/README.md +105 -29
- package/dist/http-server.d.ts +16 -0
- package/dist/http-server.js +268 -0
- package/dist/index.js +462 -37
- package/dist/printers/bambu.d.ts +23 -12
- package/dist/printers/bambu.js +319 -232
- package/dist/printers/printer-factory.d.ts +0 -1
- package/dist/printers/printer-factory.js +6 -4
- package/dist/printers/prusa.d.ts +5 -0
- package/dist/printers/prusa.js +160 -59
- package/dist/sse-server.d.ts +20 -0
- package/dist/sse-server.js +318 -0
- package/dist/stl/stl-manipulator.d.ts +5 -5
- package/dist/stl/stl-manipulator.js +32 -7
- package/dist/types.d.ts +0 -6
- package/dist/types.js +0 -20
- package/package.json +10 -4
- package/src/http-server.ts +328 -0
- package/src/index.ts +582 -50
- package/src/printers/bambu.ts +452 -299
- package/src/printers/printer-factory.ts +7 -4
- package/src/printers/prusa.ts +218 -73
- package/src/sse-server.ts +409 -0
- package/src/stl/stl-manipulator.ts +38 -8
- package/src/test/Tape+Card+55mm+Ring.stl +0 -0
- package/src/types.ts +0 -19
|
@@ -39,7 +39,8 @@ export class STLManipulator extends EventEmitter {
|
|
|
39
39
|
progressCallback(30, "Parsing STL data...");
|
|
40
40
|
// Load the STL data into a Three.js geometry
|
|
41
41
|
const loader = new STLLoader();
|
|
42
|
-
const
|
|
42
|
+
const rawArrayBuffer = stlData.buffer.slice(stlData.byteOffset, stlData.byteOffset + stlData.byteLength);
|
|
43
|
+
const geometry = loader.parse(rawArrayBuffer);
|
|
43
44
|
// Create a mesh from the geometry
|
|
44
45
|
const material = new THREE.MeshStandardMaterial();
|
|
45
46
|
const mesh = new THREE.Mesh(geometry, material);
|
|
@@ -643,15 +644,15 @@ export class STLManipulator extends EventEmitter {
|
|
|
643
644
|
}
|
|
644
645
|
}
|
|
645
646
|
/**
|
|
646
|
-
* Slice an STL file using the specified slicer
|
|
647
|
-
* @param stlFilePath Path to the input STL file
|
|
648
|
-
* @param slicerType Type of slicer (prusaslicer, cura, slic3r, orcaslicer)
|
|
647
|
+
* Slice an STL or 3MF file using the specified slicer
|
|
648
|
+
* @param stlFilePath Path to the input STL or 3MF file
|
|
649
|
+
* @param slicerType Type of slicer (prusaslicer, cura, slic3r, orcaslicer, bambustudio)
|
|
649
650
|
* @param slicerPath Path to the slicer executable
|
|
650
651
|
* @param slicerProfile Optional path to the slicer profile/config file
|
|
651
652
|
* @param progressCallback Optional callback for progress updates
|
|
652
|
-
* @returns Path to the generated G-code file
|
|
653
|
+
* @returns Path to the generated G-code or sliced 3MF file
|
|
653
654
|
*/
|
|
654
|
-
async sliceSTL(stlFilePath, slicerType, slicerPath, slicerProfile, progressCallback) {
|
|
655
|
+
async sliceSTL(stlFilePath, slicerType, slicerPath, slicerProfile, progressCallback, printerPreset) {
|
|
655
656
|
const operationId = this.generateOperationId();
|
|
656
657
|
this.activeOperations.set(operationId, true);
|
|
657
658
|
if (!fs.existsSync(slicerPath)) {
|
|
@@ -662,7 +663,7 @@ export class STLManipulator extends EventEmitter {
|
|
|
662
663
|
// Allow proceeding without profile, slicer might handle it
|
|
663
664
|
}
|
|
664
665
|
const outputFileName = path.basename(stlFilePath, '.stl') + '.gcode';
|
|
665
|
-
|
|
666
|
+
let outputFilePath = path.join(this.tempDir, outputFileName);
|
|
666
667
|
let args = [];
|
|
667
668
|
try {
|
|
668
669
|
if (progressCallback)
|
|
@@ -711,6 +712,30 @@ export class STLManipulator extends EventEmitter {
|
|
|
711
712
|
args.push('-j', slicerProfile); // Load settings from profile definition file
|
|
712
713
|
}
|
|
713
714
|
break;
|
|
715
|
+
case 'bambustudio':
|
|
716
|
+
// Bambu Studio CLI: slice and export as 3MF with embedded gcode
|
|
717
|
+
// For 3MF input: slice in place and export sliced 3MF
|
|
718
|
+
// For STL input: slice and export as 3MF
|
|
719
|
+
{
|
|
720
|
+
const is3mf = stlFilePath.toLowerCase().endsWith('.3mf');
|
|
721
|
+
const outputBase = path.basename(stlFilePath, is3mf ? '.3mf' : '.stl');
|
|
722
|
+
const bambuOutputPath = path.join(this.tempDir, outputBase + '_sliced.3mf');
|
|
723
|
+
args = [
|
|
724
|
+
'--slice', '0', // Slice all plates
|
|
725
|
+
'--export-3mf', bambuOutputPath,
|
|
726
|
+
];
|
|
727
|
+
// --load-machine ensures correct G-code for the specific printer model
|
|
728
|
+
if (printerPreset && !slicerProfile) {
|
|
729
|
+
args.push('--load-machine', printerPreset);
|
|
730
|
+
}
|
|
731
|
+
if (slicerProfile) {
|
|
732
|
+
args.push('--load-settings', slicerProfile);
|
|
733
|
+
}
|
|
734
|
+
args.push(stlFilePath);
|
|
735
|
+
// Override outputFilePath for bambustudio since it produces 3MF, not gcode
|
|
736
|
+
outputFilePath = bambuOutputPath;
|
|
737
|
+
}
|
|
738
|
+
break;
|
|
714
739
|
default:
|
|
715
740
|
throw new Error(`Unsupported slicer type: ${slicerType}`);
|
|
716
741
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { AxiosInstance } from "axios";
|
|
2
|
-
import { BambuPrinter } from "bambu-js";
|
|
3
2
|
export type BambuFTP = {
|
|
4
3
|
readDir: (path: string) => Promise<string[]>;
|
|
5
4
|
sendFile: (sourcePath: string, destinationPath: string, progressCallback?: (progress: number) => void) => Promise<void>;
|
|
@@ -16,11 +15,6 @@ export declare abstract class PrinterImplementation {
|
|
|
16
15
|
abstract cancelJob(host: string, port: string, apiKey: string): Promise<any>;
|
|
17
16
|
abstract setTemperature(host: string, port: string, apiKey: string, component: string, temperature: number): Promise<any>;
|
|
18
17
|
}
|
|
19
|
-
export declare class BambuPrinterStore {
|
|
20
|
-
private printers;
|
|
21
|
-
get(host: string, serial: string, token: string): InstanceType<typeof BambuPrinter>;
|
|
22
|
-
disconnectAll(): Promise<void>;
|
|
23
|
-
}
|
|
24
18
|
export interface SectionBounds {
|
|
25
19
|
minX: number;
|
|
26
20
|
minY: number;
|
package/dist/types.js
CHANGED
|
@@ -1,26 +1,6 @@
|
|
|
1
|
-
import { BambuPrinter } from "bambu-js";
|
|
2
1
|
// Base class for printer implementations
|
|
3
2
|
export class PrinterImplementation {
|
|
4
3
|
constructor(apiClient) {
|
|
5
4
|
this.apiClient = apiClient;
|
|
6
5
|
}
|
|
7
6
|
}
|
|
8
|
-
// Store for Bambu printers
|
|
9
|
-
export class BambuPrinterStore {
|
|
10
|
-
constructor() {
|
|
11
|
-
this.printers = new Map();
|
|
12
|
-
}
|
|
13
|
-
get(host, serial, token) {
|
|
14
|
-
const key = `${host}-${serial}`;
|
|
15
|
-
if (!this.printers.has(key)) {
|
|
16
|
-
const printer = new BambuPrinter(host, serial, token);
|
|
17
|
-
this.printers.set(key, printer);
|
|
18
|
-
}
|
|
19
|
-
return this.printers.get(key);
|
|
20
|
-
}
|
|
21
|
-
async disconnectAll() {
|
|
22
|
-
for (const printer of this.printers.values()) {
|
|
23
|
-
await printer.disconnect();
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
}
|
package/package.json
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcp-3d-printer-server",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "MCP server for connecting Claude with 3D printer management systems",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"repository": "https://github.com/DMontgomery40/mcp-3D-printer-server",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"scripts": {
|
|
9
9
|
"start": "node dist/index.js",
|
|
10
|
+
"start:stdio": "MCP_TRANSPORT=stdio node dist/index.js",
|
|
11
|
+
"start:http": "MCP_TRANSPORT=streamable-http node dist/index.js",
|
|
10
12
|
"dev": "nodemon src/index.ts --exec \"npm run build && npm start\"",
|
|
11
13
|
"build": "tsc",
|
|
12
|
-
"test": "npm run build && node dist/test/stl-manipulator-test.js"
|
|
14
|
+
"test": "npm run build && node dist/test/stl-manipulator-test.js && node --test tests/**/*.test.mjs",
|
|
15
|
+
"inspect": "npm run build && npx @modelcontextprotocol/inspector dist/index.js"
|
|
13
16
|
},
|
|
14
17
|
"bin": {
|
|
15
18
|
"mcp-3d-printer-server": "./dist/index.js"
|
|
@@ -35,14 +38,17 @@
|
|
|
35
38
|
"author": "David Montgomery",
|
|
36
39
|
"license": "GPL-2.0",
|
|
37
40
|
"dependencies": {
|
|
38
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
41
|
+
"@modelcontextprotocol/sdk": "^1.27.0",
|
|
39
42
|
"axios": "^1.6.2",
|
|
40
43
|
"bambu-js": "^2.2.2",
|
|
44
|
+
"bambu-node": "^3.22.21",
|
|
45
|
+
"basic-ftp": "^5.2.0",
|
|
41
46
|
"dotenv": "^16.3.1",
|
|
42
47
|
"form-data": "^4.0.0",
|
|
43
48
|
"jszip": "^3.10.1",
|
|
44
49
|
"three": "^0.173.0",
|
|
45
|
-
"xml2js": "^0.6.2"
|
|
50
|
+
"xml2js": "^0.6.2",
|
|
51
|
+
"zod": "^3.25.76"
|
|
46
52
|
},
|
|
47
53
|
"devDependencies": {
|
|
48
54
|
"@types/jszip": "^3.4.0",
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
4
|
+
import express, { Request, Response } from 'express';
|
|
5
|
+
import cors from 'cors';
|
|
6
|
+
import {
|
|
7
|
+
ListResourcesRequestSchema,
|
|
8
|
+
ReadResourceRequestSchema,
|
|
9
|
+
ListToolsRequestSchema,
|
|
10
|
+
CallToolRequestSchema,
|
|
11
|
+
ErrorCode,
|
|
12
|
+
McpError,
|
|
13
|
+
JSONRPCRequest,
|
|
14
|
+
JSONRPCResponse,
|
|
15
|
+
JSONRPCError
|
|
16
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
17
|
+
import axios from "axios";
|
|
18
|
+
import dotenv from "dotenv";
|
|
19
|
+
import fs from "fs";
|
|
20
|
+
import path from "path";
|
|
21
|
+
import * as THREE from 'three';
|
|
22
|
+
import { PrinterFactory } from "./printers/printer-factory.js";
|
|
23
|
+
import { STLManipulator } from "./stl/stl-manipulator.js";
|
|
24
|
+
import { parse3MF, ThreeMFData } from './3mf_parser.js';
|
|
25
|
+
import { BambuImplementation } from "./printers/bambu.js";
|
|
26
|
+
|
|
27
|
+
// Load environment variables from .env file
|
|
28
|
+
dotenv.config();
|
|
29
|
+
|
|
30
|
+
// Default values
|
|
31
|
+
const DEFAULT_HOST = process.env.PRINTER_HOST || "localhost";
|
|
32
|
+
const DEFAULT_PORT = process.env.PRINTER_PORT || "80";
|
|
33
|
+
const DEFAULT_API_KEY = process.env.API_KEY || "";
|
|
34
|
+
const DEFAULT_TYPE = process.env.PRINTER_TYPE || "octoprint";
|
|
35
|
+
const TEMP_DIR = process.env.TEMP_DIR || path.join(process.cwd(), "temp");
|
|
36
|
+
|
|
37
|
+
// HTTP Server configuration
|
|
38
|
+
const HTTP_PORT = parseInt(process.env.HTTP_PORT || "3001");
|
|
39
|
+
|
|
40
|
+
// Slicer configuration
|
|
41
|
+
const DEFAULT_SLICER_TYPE = process.env.SLICER_TYPE || "prusaslicer";
|
|
42
|
+
const DEFAULT_SLICER_PATH = process.env.SLICER_PATH || "";
|
|
43
|
+
const DEFAULT_SLICER_PROFILE = process.env.SLICER_PROFILE || "";
|
|
44
|
+
|
|
45
|
+
// Bambu-specific default values
|
|
46
|
+
const DEFAULT_BAMBU_SERIAL = process.env.BAMBU_SERIAL || "";
|
|
47
|
+
const DEFAULT_BAMBU_TOKEN = process.env.BAMBU_TOKEN || "";
|
|
48
|
+
|
|
49
|
+
// Ensure temp directory exists
|
|
50
|
+
if (!fs.existsSync(TEMP_DIR)) {
|
|
51
|
+
fs.mkdirSync(TEMP_DIR, { recursive: true });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
class ThreeDPrinterMCPServerHTTP {
|
|
55
|
+
private server: Server;
|
|
56
|
+
private printerFactory: PrinterFactory;
|
|
57
|
+
private stlManipulator: STLManipulator;
|
|
58
|
+
private app: express.Application;
|
|
59
|
+
|
|
60
|
+
constructor() {
|
|
61
|
+
this.server = new Server(
|
|
62
|
+
{
|
|
63
|
+
name: "mcp-3d-printer-server",
|
|
64
|
+
version: "1.0.0"
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
capabilities: {
|
|
68
|
+
resources: {},
|
|
69
|
+
tools: {},
|
|
70
|
+
prompts: {}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
this.printerFactory = new PrinterFactory();
|
|
76
|
+
this.stlManipulator = new STLManipulator(TEMP_DIR);
|
|
77
|
+
|
|
78
|
+
this.app = express();
|
|
79
|
+
this.setupExpressApp();
|
|
80
|
+
this.setupHandlers();
|
|
81
|
+
this.setupErrorHandling();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private setupExpressApp() {
|
|
85
|
+
// Enable CORS for all origins
|
|
86
|
+
this.app.use(cors({
|
|
87
|
+
origin: '*',
|
|
88
|
+
methods: ['GET', 'POST', 'OPTIONS'],
|
|
89
|
+
allowedHeaders: ['Content-Type', 'Accept'],
|
|
90
|
+
credentials: true
|
|
91
|
+
}));
|
|
92
|
+
|
|
93
|
+
// Parse JSON bodies
|
|
94
|
+
this.app.use(express.json({ limit: '50mb' }));
|
|
95
|
+
|
|
96
|
+
// Main MCP endpoint - supports both standard and streaming responses
|
|
97
|
+
this.app.post('/mcp', async (req: Request, res: Response) => {
|
|
98
|
+
const acceptHeader = req.get('Accept') || '';
|
|
99
|
+
const wantsStream = acceptHeader.includes('text/event-stream');
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
const requests = Array.isArray(req.body) ? req.body : [req.body];
|
|
103
|
+
const responses: JSONRPCResponse[] = [];
|
|
104
|
+
|
|
105
|
+
for (const request of requests) {
|
|
106
|
+
const response = await this.handleJSONRPCRequest(request);
|
|
107
|
+
if (response) {
|
|
108
|
+
responses.push(response);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// If client wants streaming and we have responses, use SSE format
|
|
113
|
+
if (wantsStream && responses.length > 0) {
|
|
114
|
+
res.setHeader('Content-Type', 'text/event-stream');
|
|
115
|
+
res.setHeader('Cache-Control', 'no-cache');
|
|
116
|
+
res.setHeader('Connection', 'keep-alive');
|
|
117
|
+
|
|
118
|
+
for (const response of responses) {
|
|
119
|
+
res.write(`data: ${JSON.stringify(response)}\n\n`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
res.end();
|
|
123
|
+
} else if (responses.length > 0) {
|
|
124
|
+
// Standard JSON response
|
|
125
|
+
res.json(responses.length === 1 ? responses[0] : responses);
|
|
126
|
+
} else {
|
|
127
|
+
// No responses (notifications)
|
|
128
|
+
res.status(202).send();
|
|
129
|
+
}
|
|
130
|
+
} catch (error) {
|
|
131
|
+
console.error('Error handling request:', error);
|
|
132
|
+
res.status(500).json({
|
|
133
|
+
jsonrpc: "2.0",
|
|
134
|
+
error: {
|
|
135
|
+
code: -32603,
|
|
136
|
+
message: "Internal error",
|
|
137
|
+
data: error instanceof Error ? error.message : String(error)
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// GET endpoint for establishing SSE stream (Streamable HTTP)
|
|
144
|
+
this.app.get('/mcp', async (req: Request, res: Response) => {
|
|
145
|
+
const acceptHeader = req.get('Accept') || '';
|
|
146
|
+
|
|
147
|
+
if (!acceptHeader.includes('text/event-stream')) {
|
|
148
|
+
res.status(405).json({
|
|
149
|
+
error: 'Method not allowed. Use POST for non-streaming requests.'
|
|
150
|
+
});
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Set SSE headers
|
|
155
|
+
res.setHeader('Content-Type', 'text/event-stream');
|
|
156
|
+
res.setHeader('Cache-Control', 'no-cache');
|
|
157
|
+
res.setHeader('Connection', 'keep-alive');
|
|
158
|
+
res.setHeader('X-Accel-Buffering', 'no');
|
|
159
|
+
|
|
160
|
+
// Send initial connection event
|
|
161
|
+
res.write(`event: open\ndata: {"type": "connection", "status": "connected"}\n\n`);
|
|
162
|
+
|
|
163
|
+
// Keep connection alive with periodic pings
|
|
164
|
+
const pingInterval = setInterval(() => {
|
|
165
|
+
if (!res.writableEnded) {
|
|
166
|
+
res.write(`event: ping\ndata: ${new Date().toISOString()}\n\n`);
|
|
167
|
+
}
|
|
168
|
+
}, 30000);
|
|
169
|
+
|
|
170
|
+
// Handle client disconnect
|
|
171
|
+
req.on('close', () => {
|
|
172
|
+
clearInterval(pingInterval);
|
|
173
|
+
res.end();
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// Health check endpoint
|
|
178
|
+
this.app.get('/health', (req: Request, res: Response) => {
|
|
179
|
+
res.json({
|
|
180
|
+
status: 'ok',
|
|
181
|
+
server: 'mcp-3d-printer-server',
|
|
182
|
+
transport: 'streamable-http',
|
|
183
|
+
version: '1.0.0'
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
// Root endpoint
|
|
188
|
+
this.app.get('/', (req: Request, res: Response) => {
|
|
189
|
+
res.json({
|
|
190
|
+
name: 'MCP 3D Printer Server (HTTP)',
|
|
191
|
+
version: '1.0.0',
|
|
192
|
+
endpoints: {
|
|
193
|
+
mcp: '/mcp',
|
|
194
|
+
health: '/health'
|
|
195
|
+
},
|
|
196
|
+
transport: 'streamable-http',
|
|
197
|
+
features: ['stdio', 'sse', 'http'],
|
|
198
|
+
documentation: 'https://github.com/DMontgomery40/mcp-3D-printer-server'
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
private async handleJSONRPCRequest(request: JSONRPCRequest): Promise<JSONRPCResponse | null> {
|
|
204
|
+
try {
|
|
205
|
+
// Route based on method
|
|
206
|
+
switch (request.method) {
|
|
207
|
+
case 'initialize':
|
|
208
|
+
return {
|
|
209
|
+
jsonrpc: "2.0",
|
|
210
|
+
id: request.id,
|
|
211
|
+
result: {
|
|
212
|
+
protocolVersion: "2025-03-26",
|
|
213
|
+
capabilities: this.server.capabilities,
|
|
214
|
+
serverInfo: {
|
|
215
|
+
name: "mcp-3d-printer-server",
|
|
216
|
+
version: "1.0.0"
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
case 'resources/list':
|
|
222
|
+
const resourcesResult = await this.server.handleRequest(request);
|
|
223
|
+
return {
|
|
224
|
+
jsonrpc: "2.0",
|
|
225
|
+
id: request.id,
|
|
226
|
+
result: resourcesResult
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
case 'resources/read':
|
|
230
|
+
const readResult = await this.server.handleRequest(request);
|
|
231
|
+
return {
|
|
232
|
+
jsonrpc: "2.0",
|
|
233
|
+
id: request.id,
|
|
234
|
+
result: readResult
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
case 'tools/list':
|
|
238
|
+
const toolsResult = await this.server.handleRequest(request);
|
|
239
|
+
return {
|
|
240
|
+
jsonrpc: "2.0",
|
|
241
|
+
id: request.id,
|
|
242
|
+
result: toolsResult
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
case 'tools/call':
|
|
246
|
+
const callResult = await this.server.handleRequest(request);
|
|
247
|
+
return {
|
|
248
|
+
jsonrpc: "2.0",
|
|
249
|
+
id: request.id,
|
|
250
|
+
result: callResult
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
default:
|
|
254
|
+
if (request.id) {
|
|
255
|
+
return {
|
|
256
|
+
jsonrpc: "2.0",
|
|
257
|
+
id: request.id,
|
|
258
|
+
error: {
|
|
259
|
+
code: -32601,
|
|
260
|
+
message: `Method not found: ${request.method}`
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
return null; // Notification
|
|
265
|
+
}
|
|
266
|
+
} catch (error) {
|
|
267
|
+
if (request.id) {
|
|
268
|
+
return {
|
|
269
|
+
jsonrpc: "2.0",
|
|
270
|
+
id: request.id,
|
|
271
|
+
error: {
|
|
272
|
+
code: -32603,
|
|
273
|
+
message: "Internal error",
|
|
274
|
+
data: error instanceof Error ? error.message : String(error)
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
setupErrorHandling() {
|
|
283
|
+
this.server.onerror = (error) => {
|
|
284
|
+
console.error("[MCP Error]", error);
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
process.on("SIGINT", async () => {
|
|
288
|
+
console.log("\nShutting down HTTP server...");
|
|
289
|
+
await this.printerFactory.disconnectAll();
|
|
290
|
+
await this.server.close();
|
|
291
|
+
process.exit(0);
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
setupHandlers() {
|
|
296
|
+
this.setupResourceHandlers();
|
|
297
|
+
this.setupToolHandlers();
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Copy all handlers from original index.ts
|
|
301
|
+
setupResourceHandlers() {
|
|
302
|
+
// ... (copy from original)
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
setupToolHandlers() {
|
|
306
|
+
// ... (copy from original)
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Copy all delegating methods from original index.ts
|
|
310
|
+
// ... (copy all printer methods)
|
|
311
|
+
|
|
312
|
+
async run() {
|
|
313
|
+
// Start Express server
|
|
314
|
+
this.app.listen(HTTP_PORT, () => {
|
|
315
|
+
console.log(`🚀 MCP 3D Printer Server (HTTP) running on http://localhost:${HTTP_PORT}`);
|
|
316
|
+
console.log(`📡 MCP endpoint: http://localhost:${HTTP_PORT}/mcp`);
|
|
317
|
+
console.log(`✨ Supports both standard HTTP and Server-Sent Events`);
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Check if running as main module
|
|
323
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
324
|
+
const server = new ThreeDPrinterMCPServerHTTP();
|
|
325
|
+
server.run().catch(console.error);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
export default ThreeDPrinterMCPServerHTTP;
|