openscad-mcp-server 1.0.4 → 1.0.5

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 CHANGED
@@ -1,6 +1,7 @@
1
1
  # OpenSCAD MCP Server
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/openscad-mcp-server.svg)](https://www.npmjs.com/package/openscad-mcp-server)
4
+ [![npm downloads](https://img.shields.io/npm/dt/openscad-mcp-server.svg)](https://www.npmjs.com/package/openscad-mcp-server)
4
5
  [![CI](https://github.com/fboldo/openscad-mcp-server/actions/workflows/ci.yml/badge.svg)](https://github.com/fboldo/openscad-mcp-server/actions/workflows/ci.yml)
5
6
 
6
7
  An MCP (Model Context Protocol) server that renders **PNG previews** and **STL geometry** from OpenSCAD (SCAD) source code. It is designed to support **iterative, agent-driven CAD workflows**, where models can be previewed visually and exported for downstream use (e.g. fabrication, simulation, or inspection).
@@ -33,6 +34,16 @@ This MCP server is currently in beta. Performance, APIs, and capabilities may ch
33
34
  - Input: `scadCode` (string), optional `filename` (string)
34
35
  - Output: MCP embedded resource (STL)
35
36
 
37
+ ## Skill
38
+
39
+ This repository also includes an [OpenSCAD iterative modeling skill](skills/openscad-iterative-modeling/SKILL.md) that demonstrates how to use this MCP server to support an iterative SCAD → PNG → critique → refine loop.
40
+
41
+ ## Limitations
42
+
43
+ - **Performance**: Rendering complex SCAD models can be slow, especially in a WASM environment.
44
+ - **Feature support**: Not all OpenSCAD features may be fully supported or may have limitations in the WASM version.
45
+ - **Fonts**: Text rendering is not currently supported. Support is planned for a future release.
46
+
36
47
  ## Installation
37
48
 
38
49
  The published package is intended to run over stdio. Configure it in your MCP client using `npx`:
@@ -48,6 +59,16 @@ The published package is intended to run over stdio. Configure it in your MCP cl
48
59
  }
49
60
  ```
50
61
 
62
+ ### Using the Skill
63
+
64
+ [Agents skills](https://github.com/agentskills/agentskills) are a simple, open format for giving agents new capabilities and expertise.
65
+
66
+ The most straightforward to use the OpenSCAD iterative modeling skill is to install it using the [skills CLI](https://github.com/vercel-labs/skills):
67
+
68
+ ```bash
69
+ npx skills add fboldo/openscad-mcp-server --skill openscad-iterative-modeling
70
+ ```
71
+
51
72
  ## Local development
52
73
 
53
74
  - Install deps: `bun install`
@@ -62,6 +83,9 @@ The published package is intended to run over stdio. Configure it in your MCP cl
62
83
  - [jhacksman/OpenSCAD-MCP-Server](https://github.com/jhacksman/OpenSCAD-MCP-Server)
63
84
  This project provides a different approach relying on generating images from user prompts, followed by 3D reconstruction and even 3D printer discovery. It's a very interesting project, and I recommend checking it out if you are interested in OpenSCAD and MCP servers.
64
85
 
86
+ - [petrijr/openscad-mcp](https://github.com/petrijr/openscad-mcp)
87
+ Similar to this project, but it uses a Python-based server and relies on the OpenSCAD CLI for rendering.
88
+
65
89
  ## Relevant Links
66
90
 
67
91
  - [OpenSCAD](https://openscad.org/)
package/dist/index.mjs CHANGED
@@ -58,24 +58,48 @@ const asTool = (fn) => {
58
58
 
59
59
  //#endregion
60
60
  //#region src/infra/openscad.ts
61
- const getOpenSCADInstance = async () => {
62
- const writeStderr = (line) => {
63
- try {
64
- process.stderr.write(line.endsWith("\n") ? line : `${line}\n`);
65
- } catch {
66
- console.error(line);
61
+ /**
62
+ * Executes a callback function with an OpenSCAD instance, capturing any errors and outputs produced during the execution.
63
+ *
64
+ * @param callback - An async function that receives an OpenSCAD instance to perform operations
65
+ * @returns A tuple containing the callback result (or error), an array of errors captured from OpenSCAD's stderr, and a set of outputs captured from OpenSCAD's stdout
66
+ */
67
+ const executeOpenSCAD = async (callback) => {
68
+ const outputs = [];
69
+ const response = await callback(await createOpenSCAD({
70
+ noInitialRun: true,
71
+ printErr: (text) => {
72
+ outputs.push(text);
73
+ },
74
+ print: (text) => {
75
+ outputs.push(text);
67
76
  }
68
- };
69
- return await createOpenSCAD({
70
- print: (text) => writeStderr(`[OpenSCAD]: ${text}`),
71
- printErr: (text) => writeStderr(`[OpenSCAD Error]: ${text}`)
77
+ })).catch((e) => {
78
+ return e;
72
79
  });
80
+ if (response.constructor.name.includes("Error")) {
81
+ const errors = outputs.filter((o) => o.toLowerCase().includes("error:"));
82
+ const errorMessage = errors.length > 0 ? [response.message, ...errors].filter(Boolean).join("\n") : response.message;
83
+ throw new Error(errorMessage);
84
+ }
85
+ return response;
73
86
  };
74
87
 
75
88
  //#endregion
76
89
  //#region src/app/tools/export-scad-stl/tool.ts
77
90
  const exportScadStlTool = async ({ filename = "model.stl", scadCode }) => {
78
- const stl = await (await getOpenSCADInstance()).renderToStl(scadCode);
91
+ const result = await executeOpenSCAD(async (instance) => {
92
+ const openscad = instance.getInstance();
93
+ openscad.FS.writeFile("input.scad", scadCode);
94
+ openscad.callMain([
95
+ "input.scad",
96
+ "-o",
97
+ "output.stl",
98
+ "--backend=manifold"
99
+ ]);
100
+ return openscad.FS.readFile("output.stl");
101
+ });
102
+ if (!result) throw new Error("Failed to generate STL from OpenSCAD");
79
103
  const assignedFilename = filename.endsWith(".stl") ? filename : `${filename}.stl`;
80
104
  const defaultFilename = `model-${(/* @__PURE__ */ new Date()).getTime()}.stl`;
81
105
  return {
@@ -83,7 +107,7 @@ const exportScadStlTool = async ({ filename = "model.stl", scadCode }) => {
83
107
  resource: {
84
108
  uri: `file://${assignedFilename ?? defaultFilename}`,
85
109
  mimeType: "model/stl",
86
- blob: Buffer.from(stl, "utf8").toString("base64")
110
+ blob: Buffer.from(result, "utf8").toString("base64")
87
111
  }
88
112
  };
89
113
  };
@@ -172,9 +196,21 @@ const resolveCameraPosition = (cameraPosition, cameraPreset) => {
172
196
  return CAMERA_PRESETS[cameraPreset];
173
197
  };
174
198
  const renderScadPngTool = async ({ scadCode, width, height, cameraPreset, cameraPosition }) => {
199
+ const result = await executeOpenSCAD(async (instance) => {
200
+ const openscad = instance.getInstance();
201
+ openscad.FS.writeFile("input.scad", scadCode);
202
+ openscad.callMain([
203
+ "input.scad",
204
+ "-o",
205
+ "output.stl",
206
+ "--backend=manifold"
207
+ ]);
208
+ return openscad.FS.readFile("output.stl");
209
+ });
210
+ if (!result) throw new Error("Failed to generate STL from OpenSCAD");
175
211
  return {
176
212
  type: "image",
177
- data: await createPngBase64FromStl(await (await getOpenSCADInstance()).renderToStl(scadCode), width, height, resolveCameraPosition(cameraPosition, cameraPreset)),
213
+ data: await createPngBase64FromStl(result, width, height, resolveCameraPosition(cameraPosition, cameraPreset)),
178
214
  mimeType: "image/png",
179
215
  _meta: {
180
216
  width,
@@ -208,20 +244,6 @@ const RenderScadPngToolInputSchema = z.object({
208
244
  cameraPreset: RenderScadPngCameraPreset.optional().describe("A named camera preset used when `cameraPosition` is not provided"),
209
245
  cameraPosition: RenderScadPngCamera.optional().describe("Camera position as { x,y,z }. Example: { x: 0, y: -25, z: 20 }")
210
246
  });
211
- const RenderScadPngToolOutputSchema = z.object({
212
- image: ImageContentSchema,
213
- metadata: z.object({
214
- width: z.number(),
215
- height: z.number(),
216
- cameraPreset: RenderScadPngCameraPreset.optional(),
217
- cameraPosition: RenderScadPngCamera.optional(),
218
- timingsMs: z.object({
219
- openscadToStl: z.number(),
220
- stlToPng: z.number(),
221
- total: z.number()
222
- })
223
- })
224
- });
225
247
 
226
248
  //#endregion
227
249
  //#region src/app/tools/render-scad-png/register.ts
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "module": "./dist/index.mjs",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
- "version": "1.0.4",
6
+ "version": "1.0.5",
7
7
  "mcpName": "io.github.fboldo/openscad-mcp-server",
8
8
  "description": "An MCP server that renders PNG previews and STL geometry from OpenSCAD (SCAD) source code.",
9
9
  "repository": {
package/server.json CHANGED
@@ -8,13 +8,13 @@
8
8
  "url": "https://github.com/fboldo/openscad-mcp-server",
9
9
  "source": "github"
10
10
  },
11
- "version": "1.0.4",
11
+ "version": "1.0.5",
12
12
  "packages": [
13
13
  {
14
14
  "registryType": "npm",
15
15
  "registryBaseUrl": "https://registry.npmjs.org",
16
16
  "identifier": "openscad-mcp-server",
17
- "version": "1.0.4",
17
+ "version": "1.0.5",
18
18
  "transport": {
19
19
  "type": "stdio"
20
20
  }