mcp-3d-printer-server 1.2.18 → 1.2.20
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/CHANGELOG.md +16 -0
- package/dist/ensure-web-crypto.d.ts +1 -0
- package/dist/ensure-web-crypto.js +12 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -0
- package/dist/stl/stl-manipulator.js +8 -7
- package/package.json +5 -2
- package/server.json +2 -2
- package/src/ensure-web-crypto.ts +13 -0
- package/src/index.ts +2 -0
- package/src/stl/stl-manipulator.ts +8 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.2.20 — 2026-10-30
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- Keep already-horizontal faces eligible when `lay_flat` chooses the largest flat face. Thin plates now rest on their broad face at Z=0 instead of standing on an edge, including after repeated calls. When up- and down-facing areas tie exactly, as for a plate with a raised block or lettering, keep the face that already points down so a seated part is not flipped. Validate with generated STL geometry in horizontal, upside-down, edge-on, and tilted orientations and with a raised block in either face order; no printer or live Blender scene is used.
|
|
8
|
+
|
|
9
|
+
### Tests
|
|
10
|
+
|
|
11
|
+
- Verify malformed HTTP JSON returns a structured 400 response while the active MCP session remains usable. Exercise loopback HTTP only; no real printer or live Blender scene is used.
|
|
12
|
+
|
|
13
|
+
## 1.2.19 — 2026-10-30
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- Restore Node.js 18 support removed in 1.2.18. The MCP SDK's streamable HTTP transport calls the global `crypto`, which Node.js 18 lacks and `@hono/node-server` 2.x no longer installs; the server now provides it from `node:crypto` when missing. The package again declares Node.js 18 or newer and CI tests 18 through 24. Fresh npm installs on Node.js 18 resolve `@hono/node-server` 1.19.17, as they did for 1.2.18 apart from an engine warning. That version is inside GHSA-rmxm-3fg6-px4f's range, which affects only `serveStatic`; this server does not use it. Node.js 20 or newer installs the patched 2.1.5, which the repository lock keeps, so Node.js 18 CI and clones run it outside its declared Node.js 20 range. A regression starts the HTTP and SSE entrypoints without the global; the full suite and packed-install test pass on Node.js 18.20.8 and 24. Offline tests only; no printer is contacted.
|
|
18
|
+
|
|
3
19
|
## 1.2.18 — 2026-10-30
|
|
4
20
|
|
|
5
21
|
### Changed
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Node.js 18 exposes Web Crypto only as node:crypto's webcrypto, but the MCP
|
|
2
|
+
// SDK's streamable HTTP transport calls the global crypto, which
|
|
3
|
+
// @hono/node-server 2.x no longer installs. Newer Node.js already provides it.
|
|
4
|
+
import { webcrypto } from "node:crypto";
|
|
5
|
+
if (typeof globalThis.crypto === "undefined") {
|
|
6
|
+
Object.defineProperty(globalThis, "crypto", {
|
|
7
|
+
value: webcrypto,
|
|
8
|
+
configurable: true,
|
|
9
|
+
enumerable: false,
|
|
10
|
+
writable: true,
|
|
11
|
+
});
|
|
12
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
2
|
+
import "./ensure-web-crypto.js";
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
// Must stay first: Node.js 18 needs the global crypto before any SDK transport code runs.
|
|
3
|
+
import "./ensure-web-crypto.js";
|
|
2
4
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
5
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
6
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
@@ -1194,22 +1194,23 @@ export class STLManipulator extends EventEmitter {
|
|
|
1194
1194
|
throw new Error("Operation cancelled");
|
|
1195
1195
|
if (progressCallback)
|
|
1196
1196
|
progressCallback(75, "Finding largest flat surface...");
|
|
1197
|
-
//
|
|
1197
|
+
// Include already-horizontal faces: excluding Z-aligned normals can
|
|
1198
|
+
// stand a flat model on its edge, including on a repeated layFlat call.
|
|
1198
1199
|
let largestArea = 0;
|
|
1199
1200
|
let targetNormal = null;
|
|
1200
|
-
const upVector = new THREE.Vector3(0, 1, 0); // Assuming Y is up for most models initially?
|
|
1201
1201
|
const downVector = new THREE.Vector3(0, 0, -1); // Target normal (pointing down)
|
|
1202
|
+
// Faces are grouped by direction, so a part whose flat faces are all
|
|
1203
|
+
// horizontal has equal up- and down-facing areas. On a tie, keep the
|
|
1204
|
+
// face that already points down so a seated part is not flipped.
|
|
1202
1205
|
for (const group of normalGroups.values()) {
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
if (Math.abs(group.representativeNormal.z) < 0.99 && group.totalArea > largestArea) {
|
|
1206
|
+
const tied = targetNormal !== null && Math.abs(group.totalArea - largestArea) <= largestArea * 1e-6;
|
|
1207
|
+
if (tied ? group.representativeNormal.z < targetNormal.z : group.totalArea > largestArea) {
|
|
1206
1208
|
largestArea = group.totalArea;
|
|
1207
1209
|
targetNormal = group.representativeNormal;
|
|
1208
1210
|
}
|
|
1209
1211
|
}
|
|
1210
1212
|
if (!targetNormal) {
|
|
1211
|
-
//
|
|
1212
|
-
// For now, throw an error or return unchanged.
|
|
1213
|
+
// Only a mesh without any face area reaches this.
|
|
1213
1214
|
console.warn("Could not find a suitable large flat face to lay down. No changes made.");
|
|
1214
1215
|
this.emit('operationComplete', { operationId, type: 'layFlat', success: true, output: stlFilePath, message: "No suitable flat face found." });
|
|
1215
1216
|
return stlFilePath;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcp-3d-printer-server",
|
|
3
3
|
"mcpName": "io.github.DMontgomery40/mcp-3d-printer-server",
|
|
4
|
-
"version": "1.2.
|
|
4
|
+
"version": "1.2.20",
|
|
5
5
|
"description": "MCP server for connecting Claude with 3D printer management systems",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"repository": "https://github.com/DMontgomery40/mcp-3D-printer-server",
|
|
@@ -41,6 +41,8 @@
|
|
|
41
41
|
"dist/test",
|
|
42
42
|
"dist/types.d.ts",
|
|
43
43
|
"dist/types.js",
|
|
44
|
+
"dist/ensure-web-crypto.d.ts",
|
|
45
|
+
"dist/ensure-web-crypto.js",
|
|
44
46
|
"src/3mf_parser.ts",
|
|
45
47
|
"src/fulu-orca.ts",
|
|
46
48
|
"src/http-server.ts",
|
|
@@ -55,6 +57,7 @@
|
|
|
55
57
|
"src/test/stl-manipulator-test.ts",
|
|
56
58
|
"src/test/tsconfig.json",
|
|
57
59
|
"src/types.ts",
|
|
60
|
+
"src/ensure-web-crypto.ts",
|
|
58
61
|
"README.md",
|
|
59
62
|
"LICENSE",
|
|
60
63
|
"dist/blender-mcp-bridge.js",
|
|
@@ -99,6 +102,6 @@
|
|
|
99
102
|
"typescript": "~5.9.3"
|
|
100
103
|
},
|
|
101
104
|
"engines": {
|
|
102
|
-
"node": ">=
|
|
105
|
+
"node": ">=18"
|
|
103
106
|
}
|
|
104
107
|
}
|
package/server.json
CHANGED
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|
"url": "https://github.com/DMontgomery40/mcp-3D-printer-server",
|
|
8
8
|
"source": "github"
|
|
9
9
|
},
|
|
10
|
-
"version": "1.2.
|
|
10
|
+
"version": "1.2.20",
|
|
11
11
|
"packages": [
|
|
12
12
|
{
|
|
13
13
|
"registryType": "npm",
|
|
14
14
|
"identifier": "mcp-3d-printer-server",
|
|
15
|
-
"version": "1.2.
|
|
15
|
+
"version": "1.2.20",
|
|
16
16
|
"runtimeHint": "npx",
|
|
17
17
|
"transport": {
|
|
18
18
|
"type": "stdio"
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Node.js 18 exposes Web Crypto only as node:crypto's webcrypto, but the MCP
|
|
2
|
+
// SDK's streamable HTTP transport calls the global crypto, which
|
|
3
|
+
// @hono/node-server 2.x no longer installs. Newer Node.js already provides it.
|
|
4
|
+
import { webcrypto } from "node:crypto";
|
|
5
|
+
|
|
6
|
+
if (typeof globalThis.crypto === "undefined") {
|
|
7
|
+
Object.defineProperty(globalThis, "crypto", {
|
|
8
|
+
value: webcrypto,
|
|
9
|
+
configurable: true,
|
|
10
|
+
enumerable: false,
|
|
11
|
+
writable: true,
|
|
12
|
+
});
|
|
13
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
// Must stay first: Node.js 18 needs the global crypto before any SDK transport code runs.
|
|
4
|
+
import "./ensure-web-crypto.js";
|
|
3
5
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
4
6
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
7
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
@@ -1503,24 +1503,25 @@ export class STLManipulator extends EventEmitter {
|
|
|
1503
1503
|
if (!this.activeOperations.get(operationId)) throw new Error("Operation cancelled");
|
|
1504
1504
|
if (progressCallback) progressCallback(75, "Finding largest flat surface...");
|
|
1505
1505
|
|
|
1506
|
-
//
|
|
1506
|
+
// Include already-horizontal faces: excluding Z-aligned normals can
|
|
1507
|
+
// stand a flat model on its edge, including on a repeated layFlat call.
|
|
1507
1508
|
let largestArea = 0;
|
|
1508
1509
|
let targetNormal: THREE.Vector3 | null = null;
|
|
1509
|
-
const upVector = new THREE.Vector3(0, 1, 0); // Assuming Y is up for most models initially?
|
|
1510
1510
|
const downVector = new THREE.Vector3(0, 0, -1); // Target normal (pointing down)
|
|
1511
1511
|
|
|
1512
|
+
// Faces are grouped by direction, so a part whose flat faces are all
|
|
1513
|
+
// horizontal has equal up- and down-facing areas. On a tie, keep the
|
|
1514
|
+
// face that already points down so a seated part is not flipped.
|
|
1512
1515
|
for (const group of normalGroups.values()) {
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
if (Math.abs(group.representativeNormal.z) < 0.99 && group.totalArea > largestArea) {
|
|
1516
|
+
const tied = targetNormal !== null && Math.abs(group.totalArea - largestArea) <= largestArea * 1e-6;
|
|
1517
|
+
if (tied ? group.representativeNormal.z < targetNormal!.z : group.totalArea > largestArea) {
|
|
1516
1518
|
largestArea = group.totalArea;
|
|
1517
1519
|
targetNormal = group.representativeNormal;
|
|
1518
1520
|
}
|
|
1519
1521
|
}
|
|
1520
1522
|
|
|
1521
1523
|
if (!targetNormal) {
|
|
1522
|
-
//
|
|
1523
|
-
// For now, throw an error or return unchanged.
|
|
1524
|
+
// Only a mesh without any face area reaches this.
|
|
1524
1525
|
console.warn("Could not find a suitable large flat face to lay down. No changes made.");
|
|
1525
1526
|
this.emit('operationComplete', { operationId, type: 'layFlat', success: true, output: stlFilePath, message: "No suitable flat face found." });
|
|
1526
1527
|
return stlFilePath;
|