@polypad/mcp 0.1.2 → 0.1.7

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
@@ -6,15 +6,15 @@ The server runs on your own computer. Nothing is uploaded and there is no accoun
6
6
 
7
7
  ## Set up
8
8
 
9
- You need Node.js 22 or newer. Add the server to your AI client; `npx` downloads it on first use.
9
+ **Claude Desktop, one click (no Node.js or terminal needed):** download [polypad.mcpb](https://polypad.ink/downloads/polypad.mcpb) (also behind **Connect AI** on polypad.ink) and open it; Claude Desktop shows an Install button. It runs this same server with Claude Desktop's built-in Node.
10
10
 
11
- **Claude Code**
11
+ **Claude Code and Cursor:** you need Node.js 22 or newer. `npx` downloads the server on first use.
12
12
 
13
13
  ```sh
14
14
  claude mcp add polypad -- npx -y @polypad/mcp
15
15
  ```
16
16
 
17
- **Claude Desktop** (Settings, Developer, Edit Config, then restart)
17
+ **Cursor** (`.cursor/mcp.json` in a project, or `~/.cursor/mcp.json` for all projects):
18
18
 
19
19
  ```json
20
20
  {
@@ -27,7 +27,7 @@ claude mcp add polypad -- npx -y @polypad/mcp
27
27
  }
28
28
  ```
29
29
 
30
- **Cursor** (`.cursor/mcp.json` in a project, or `~/.cursor/mcp.json` for all projects): the same `mcpServers` block as above.
30
+ The same block works in Claude Desktop's `claude_desktop_config.json` (Settings, Developer, Edit Config, then restart) if you prefer it to the extension.
31
31
 
32
32
  ## Connect
33
33
 
@@ -44,7 +44,7 @@ You can add Polypad to Claude Code and Claude Desktop (or Cursor) and use them a
44
44
 
45
45
  ## Troubleshooting
46
46
 
47
- - **"Server not running"** in the dialog: your AI client has not started the server yet. Ask it something about Polypad, and check `npx -y @polypad/mcp --help` works (Node 22 or newer).
47
+ - **"Server not running"** in the dialog: your AI client has not started the server yet. Ask it something about Polypad, and check `npx -y @polypad/mcp --help` works (Node 22 or newer; the Claude Desktop extension needs neither).
48
48
  - **"Wrong code"**: enter the code from the latest start. It changes whenever the connection's owner restarts.
49
49
  - **"Port 41720 is already in use"** (the tools say so): a program that is not a Polypad MCP server (or an old 0.1.0 one) holds the port. Close it, or use `--port 41721` here and in the dialog's Advanced section. The server checks again every 2 seconds.
50
50
 
@@ -72,4 +72,8 @@ Lengths are in mm and angles in degrees. Sizes can be numbers or formulas over y
72
72
  - The assistant sees and edits only the project you have open. Disconnect in the dialog cuts it off at any time.
73
73
  - Polypad sends nothing to a server. What leaves your machine is what your AI client sends to its model provider: your prompts and the tool results.
74
74
 
75
+ ## Licenses
76
+
77
+ Polypad MCP is MIT licensed. `dist/index.js` bundles a few open-source packages (the MCP SDK, ws, zod and their dependencies); their names, versions and full license texts are in `THIRD_PARTY_NOTICES.txt`, shipped next to it in this package.
78
+
75
79
  More: [polypad.ink](https://polypad.ink). License: MIT.
@@ -0,0 +1,260 @@
1
+ THIRD-PARTY NOTICES for @polypad/mcp
2
+
3
+ dist/index.js bundles the open-source packages listed below. Each one is under its own license; the license
4
+ texts are reproduced in full as the packages ship them.
5
+
6
+ @modelcontextprotocol/sdk@1.32.1 (MIT)
7
+ ajv@8.20.0 (MIT)
8
+ ajv-formats@3.0.1 (MIT)
9
+ fast-deep-equal@3.1.3 (MIT)
10
+ fast-uri@3.1.8 (BSD-3-Clause)
11
+ json-schema-traverse@1.0.0 (MIT)
12
+ ws@8.22.0 (MIT)
13
+ zod@4.6.5 (MIT)
14
+ zod-to-json-schema@3.25.2 (ISC)
15
+
16
+ ==============================================================================
17
+ @modelcontextprotocol/sdk@1.32.1
18
+ License: MIT
19
+ Source: https://modelcontextprotocol.io
20
+
21
+ MIT License
22
+
23
+ Copyright (c) 2024 Anthropic, PBC
24
+
25
+ Permission is hereby granted, free of charge, to any person obtaining a copy
26
+ of this software and associated documentation files (the "Software"), to deal
27
+ in the Software without restriction, including without limitation the rights
28
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
29
+ copies of the Software, and to permit persons to whom the Software is
30
+ furnished to do so, subject to the following conditions:
31
+
32
+ The above copyright notice and this permission notice shall be included in all
33
+ copies or substantial portions of the Software.
34
+
35
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
36
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
37
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
38
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
39
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
40
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
41
+ SOFTWARE.
42
+
43
+ ==============================================================================
44
+ ajv@8.20.0
45
+ License: MIT
46
+ Source: https://ajv.js.org
47
+
48
+ The MIT License (MIT)
49
+
50
+ Copyright (c) 2015-2021 Evgeny Poberezkin
51
+
52
+ Permission is hereby granted, free of charge, to any person obtaining a copy
53
+ of this software and associated documentation files (the "Software"), to deal
54
+ in the Software without restriction, including without limitation the rights
55
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
56
+ copies of the Software, and to permit persons to whom the Software is
57
+ furnished to do so, subject to the following conditions:
58
+
59
+ The above copyright notice and this permission notice shall be included in all
60
+ copies or substantial portions of the Software.
61
+
62
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
63
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
64
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
65
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
66
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
67
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
68
+ SOFTWARE.
69
+
70
+ ==============================================================================
71
+ ajv-formats@3.0.1
72
+ License: MIT
73
+ Source: https://github.com/ajv-validator/ajv-formats#readme
74
+
75
+ MIT License
76
+
77
+ Copyright (c) 2020 Evgeny Poberezkin
78
+
79
+ Permission is hereby granted, free of charge, to any person obtaining a copy
80
+ of this software and associated documentation files (the "Software"), to deal
81
+ in the Software without restriction, including without limitation the rights
82
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
83
+ copies of the Software, and to permit persons to whom the Software is
84
+ furnished to do so, subject to the following conditions:
85
+
86
+ The above copyright notice and this permission notice shall be included in all
87
+ copies or substantial portions of the Software.
88
+
89
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
90
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
91
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
92
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
93
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
94
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
95
+ SOFTWARE.
96
+
97
+ ==============================================================================
98
+ fast-deep-equal@3.1.3
99
+ License: MIT
100
+ Source: https://github.com/epoberezkin/fast-deep-equal#readme
101
+
102
+ MIT License
103
+
104
+ Copyright (c) 2017 Evgeny Poberezkin
105
+
106
+ Permission is hereby granted, free of charge, to any person obtaining a copy
107
+ of this software and associated documentation files (the "Software"), to deal
108
+ in the Software without restriction, including without limitation the rights
109
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
110
+ copies of the Software, and to permit persons to whom the Software is
111
+ furnished to do so, subject to the following conditions:
112
+
113
+ The above copyright notice and this permission notice shall be included in all
114
+ copies or substantial portions of the Software.
115
+
116
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
117
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
118
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
119
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
120
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
121
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
122
+ SOFTWARE.
123
+
124
+ ==============================================================================
125
+ fast-uri@3.1.8
126
+ License: BSD-3-Clause
127
+ Source: https://github.com/fastify/fast-uri
128
+
129
+ Copyright (c) 2011-2021, Gary Court until https://github.com/garycourt/uri-js/commit/a1acf730b4bba3f1097c9f52e7d9d3aba8cdcaae
130
+ Copyright (c) 2021-present The Fastify team <https://github.com/fastify/fastify#team>
131
+ All rights reserved.
132
+
133
+ Redistribution and use in source and binary forms, with or without
134
+ modification, are permitted provided that the following conditions are met:
135
+ * Redistributions of source code must retain the above copyright
136
+ notice, this list of conditions and the following disclaimer.
137
+ * Redistributions in binary form must reproduce the above copyright
138
+ notice, this list of conditions and the following disclaimer in the
139
+ documentation and/or other materials provided with the distribution.
140
+ * The names of any contributors may not be used to endorse or promote
141
+ products derived from this software without specific prior written
142
+ permission.
143
+
144
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
145
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
146
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
147
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY
148
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
149
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
150
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
151
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
152
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
153
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
154
+
155
+ * * *
156
+
157
+ The complete list of contributors can be found at:
158
+ - https://github.com/garycourt/uri-js/graphs/contributors
159
+
160
+ ==============================================================================
161
+ json-schema-traverse@1.0.0
162
+ License: MIT
163
+ Source: https://github.com/epoberezkin/json-schema-traverse#readme
164
+
165
+ MIT License
166
+
167
+ Copyright (c) 2017 Evgeny Poberezkin
168
+
169
+ Permission is hereby granted, free of charge, to any person obtaining a copy
170
+ of this software and associated documentation files (the "Software"), to deal
171
+ in the Software without restriction, including without limitation the rights
172
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
173
+ copies of the Software, and to permit persons to whom the Software is
174
+ furnished to do so, subject to the following conditions:
175
+
176
+ The above copyright notice and this permission notice shall be included in all
177
+ copies or substantial portions of the Software.
178
+
179
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
180
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
181
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
182
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
183
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
184
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
185
+ SOFTWARE.
186
+
187
+ ==============================================================================
188
+ ws@8.22.0
189
+ License: MIT
190
+ Source: https://github.com/websockets/ws
191
+
192
+ Copyright (c) 2011 Einar Otto Stangvik <einaros@gmail.com>
193
+ Copyright (c) 2013 Arnout Kazemier and contributors
194
+ Copyright (c) 2016 Luigi Pinca and contributors
195
+
196
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
197
+ this software and associated documentation files (the "Software"), to deal in
198
+ the Software without restriction, including without limitation the rights to
199
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
200
+ the Software, and to permit persons to whom the Software is furnished to do so,
201
+ subject to the following conditions:
202
+
203
+ The above copyright notice and this permission notice shall be included in all
204
+ copies or substantial portions of the Software.
205
+
206
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
207
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
208
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
209
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
210
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
211
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
212
+
213
+ ==============================================================================
214
+ zod@4.6.5
215
+ License: MIT
216
+ Source: https://zod.dev
217
+
218
+ MIT License
219
+
220
+ Copyright (c) 2025 Colin McDonnell
221
+
222
+ Permission is hereby granted, free of charge, to any person obtaining a copy
223
+ of this software and associated documentation files (the "Software"), to deal
224
+ in the Software without restriction, including without limitation the rights
225
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
226
+ copies of the Software, and to permit persons to whom the Software is
227
+ furnished to do so, subject to the following conditions:
228
+
229
+ The above copyright notice and this permission notice shall be included in all
230
+ copies or substantial portions of the Software.
231
+
232
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
233
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
234
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
235
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
236
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
237
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
238
+ SOFTWARE.
239
+
240
+ ==============================================================================
241
+ zod-to-json-schema@3.25.2
242
+ License: ISC
243
+ Source: https://github.com/StefanTerdell/zod-to-json-schema
244
+
245
+ ISC License
246
+
247
+ Copyright (c) 2020, Stefan Terdell
248
+
249
+ Permission to use, copy, modify, and/or distribute this software for any
250
+ purpose with or without fee is hereby granted, provided that the above
251
+ copyright notice and this permission notice appear in all copies.
252
+
253
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
254
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
255
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
256
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
257
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
258
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
259
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
260
+
package/dist/index.js CHANGED
@@ -34241,7 +34241,7 @@ var rawSketchShape = { ...sketchShape, constraints: external_exports.array(RawSk
34241
34241
  var SketchSchema = external_exports.object(sketchShape).superRefine(validateSketch);
34242
34242
 
34243
34243
  // ../../packages/document/src/project.ts
34244
- var SCHEMA_VERSION = 16;
34244
+ var SCHEMA_VERSION = 18;
34245
34245
  var MAX_SNAPSHOT_NAME = 80;
34246
34246
  var MAX_PATTERN_COUNT = 100;
34247
34247
  var MAX_PATTERN_COPIES = 400;
@@ -34269,7 +34269,10 @@ function makeSchemas(v, constraint) {
34269
34269
  external_exports.object({ kind: external_exports.literal("box"), width: pos, depth: pos, height: pos }),
34270
34270
  external_exports.object({ kind: external_exports.literal("cylinder"), radius: pos, height: pos }),
34271
34271
  external_exports.object({ kind: external_exports.literal("sphere"), radius: pos }),
34272
- external_exports.object({ kind: external_exports.literal("cone"), radius: pos, topRadius: nonneg, height: pos })
34272
+ external_exports.object({ kind: external_exports.literal("cone"), radius: pos, topRadius: nonneg, height: pos }),
34273
+ external_exports.object({ kind: external_exports.literal("torus"), radius: pos, tubeRadius: pos }),
34274
+ external_exports.object({ kind: external_exports.literal("tube"), radius: pos, innerRadius: pos, height: pos }),
34275
+ external_exports.object({ kind: external_exports.literal("wedge"), width: pos, depth: pos, height: pos, topWidth: nonneg })
34273
34276
  ]);
34274
34277
  const PrimitiveFeatureSchema = external_exports.object({
34275
34278
  ...featureBase,
@@ -34333,37 +34336,43 @@ function makeSchemas(v, constraint) {
34333
34336
  });
34334
34337
  const direction = external_exports.object({ axis: external_exports.enum(["x", "y", "z"]), flip: external_exports.boolean().default(false) });
34335
34338
  const count = v(external_exports.number().int().min(2).max(MAX_PATTERN_COUNT));
34339
+ const LayoutSchema = external_exports.discriminatedUnion("kind", [
34340
+ external_exports.object({
34341
+ kind: external_exports.literal("linear"),
34342
+ direction,
34343
+ count,
34344
+ spacing: pos,
34345
+ second: external_exports.object({ direction, count, spacing: pos }).optional()
34346
+ }),
34347
+ external_exports.object({
34348
+ kind: external_exports.literal("circular"),
34349
+ axis: external_exports.enum(["x", "y", "z"]),
34350
+ centre: vec32,
34351
+ count,
34352
+ // 360 spreads the copies evenly around; less puts the last copy at this angle.
34353
+ angle: v(external_exports.number().gt(0).max(360)).default(360)
34354
+ })
34355
+ ]);
34356
+ const gridWithinLimit = (f) => {
34357
+ const layout = f.layout;
34358
+ if (layout.kind !== "linear" || !layout.second) return true;
34359
+ const [a, b] = [layout.count, layout.second.count];
34360
+ return typeof a !== "number" || typeof b !== "number" || a * b <= MAX_PATTERN_COPIES;
34361
+ };
34362
+ const gridIssue = { path: ["layout", "second", "count"], message: `A grid can have at most ${MAX_PATTERN_COPIES} copies (count x second count)` };
34336
34363
  const PatternFeatureSchema = external_exports.object({
34337
34364
  ...featureBase,
34338
34365
  type: external_exports.literal("pattern"),
34339
34366
  body: external_exports.string(),
34340
34367
  result: external_exports.enum(["join", "separate"]).default("join"),
34341
- layout: external_exports.discriminatedUnion("kind", [
34342
- external_exports.object({
34343
- kind: external_exports.literal("linear"),
34344
- direction,
34345
- count,
34346
- spacing: pos,
34347
- second: external_exports.object({ direction, count, spacing: pos }).optional()
34348
- }),
34349
- external_exports.object({
34350
- kind: external_exports.literal("circular"),
34351
- axis: external_exports.enum(["x", "y", "z"]),
34352
- centre: vec32,
34353
- count,
34354
- // 360 spreads the copies evenly around; less puts the last copy at this angle.
34355
- angle: v(external_exports.number().gt(0).max(360)).default(360)
34356
- })
34357
- ])
34358
- }).refine(
34359
- (f) => {
34360
- const layout = f.layout;
34361
- if (layout.kind !== "linear" || !layout.second) return true;
34362
- const [a, b] = [layout.count, layout.second.count];
34363
- return typeof a !== "number" || typeof b !== "number" || a * b <= MAX_PATTERN_COPIES;
34364
- },
34365
- { path: ["layout", "second", "count"], message: `A grid can have at most ${MAX_PATTERN_COPIES} copies (count x second count)` }
34366
- );
34368
+ layout: LayoutSchema
34369
+ }).refine(gridWithinLimit, gridIssue);
34370
+ const FeaturePatternSchema = external_exports.object({
34371
+ ...featureBase,
34372
+ type: external_exports.literal("featurePattern"),
34373
+ feature: external_exports.string(),
34374
+ layout: LayoutSchema
34375
+ }).refine(gridWithinLimit, gridIssue);
34367
34376
  const MirrorFeatureSchema = external_exports.object({
34368
34377
  ...featureBase,
34369
34378
  type: external_exports.literal("mirror"),
@@ -34429,6 +34438,7 @@ function makeSchemas(v, constraint) {
34429
34438
  HoleFeatureSchema,
34430
34439
  TransformFeatureSchema,
34431
34440
  PatternFeatureSchema,
34441
+ FeaturePatternSchema,
34432
34442
  MirrorFeatureSchema,
34433
34443
  CopyFeatureSchema,
34434
34444
  ImportFeatureSchema,
@@ -34449,6 +34459,15 @@ var modelShape = {
34449
34459
  features: external_exports.record(external_exports.string(), FeatureSchema),
34450
34460
  order: external_exports.array(external_exports.string())
34451
34461
  };
34462
+ function holePatternProblem(model, feature) {
34463
+ const source = model.features[feature.feature];
34464
+ if (!source) return "The hole this pattern repeats was deleted";
34465
+ if (source.type !== "hole") return `${source.name} is not a hole; a hole pattern repeats a hole`;
34466
+ const at = model.order.indexOf(feature.id);
34467
+ const from = model.order.indexOf(source.id);
34468
+ if (from < 0 || at >= 0 && from > at) return `${source.name} comes after this pattern; a hole pattern repeats an earlier hole`;
34469
+ return null;
34470
+ }
34452
34471
  function checkModel(model, ctx) {
34453
34472
  const ids = Object.keys(model.features);
34454
34473
  if (new Set(model.order).size !== model.order.length) {
@@ -34461,6 +34480,11 @@ function checkModel(model, ctx) {
34461
34480
  if (model.features[id].id !== id) {
34462
34481
  ctx.addIssue({ code: "custom", path: ["features", id], message: "Feature id mismatch" });
34463
34482
  }
34483
+ const feature = model.features[id];
34484
+ if (feature.type === "featurePattern") {
34485
+ const problem = holePatternProblem(model, feature);
34486
+ if (problem) ctx.addIssue({ code: "custom", path: ["features", id, "feature"], message: problem });
34487
+ }
34464
34488
  }
34465
34489
  }
34466
34490
  var SnapshotSchema = external_exports.object({
@@ -34654,11 +34678,35 @@ var patternDirection = external_exports.object({
34654
34678
  axis: external_exports.enum(["x", "y", "z"]).describe("World axis the copies step along"),
34655
34679
  flip: external_exports.boolean().default(false).describe("True steps along the negative axis")
34656
34680
  });
34681
+ var patternLayout = external_exports.discriminatedUnion("kind", [
34682
+ external_exports.object({
34683
+ kind: external_exports.literal("linear"),
34684
+ direction: patternDirection,
34685
+ count: external_exports.union([external_exports.number().int().min(2).max(100), external_exports.string().min(1)]).describe("Number of copies in a row, including the original (2 to 100)"),
34686
+ spacing: size("Distance between copies"),
34687
+ second: external_exports.object({
34688
+ direction: patternDirection,
34689
+ count: external_exports.union([external_exports.number().int().min(2).max(100), external_exports.string().min(1)]).describe("Rows in the second direction, including the original"),
34690
+ spacing: size("Distance between rows")
34691
+ }).optional().describe("A second direction makes a grid (at most 400 copies in all)")
34692
+ }),
34693
+ external_exports.object({
34694
+ kind: external_exports.literal("circular"),
34695
+ axis: external_exports.enum(["x", "y", "z"]).describe("World axis (through `centre`) the copies turn about"),
34696
+ centre: vec3("A point on the axis"),
34697
+ count: external_exports.union([external_exports.number().int().min(2).max(100), external_exports.string().min(1)]).describe("Number of copies including the original"),
34698
+ angle: external_exports.union([external_exports.number().gt(0).max(360), external_exports.string().min(1)]).default(360).describe("360 spreads the copies evenly around the full circle; less puts the last copy at this angle (degrees)")
34699
+ })
34700
+ ]).describe("How the copies are laid out. Counts include the original");
34657
34701
  var inputs = {
34658
34702
  new_project: external_exports.object({
34659
34703
  name: external_exports.string().trim().min(1).max(120).default("Untitled").describe("Name of the new project"),
34660
34704
  template: external_exports.enum(templateIds).optional().describe(`Start from a template (${PROJECT_TEMPLATES.map((t) => `${t.id}: ${t.description}`).join(" ")}). Omit for an empty project.`)
34661
34705
  }),
34706
+ list_projects: external_exports.object({}),
34707
+ open_project: external_exports.object({
34708
+ id: external_exports.string().min(1).describe("Id of the project to open (from list_projects)")
34709
+ }),
34662
34710
  set_parameter: external_exports.object({
34663
34711
  name: external_exports.string().min(1).describe("Parameter name: letters, digits and _, not starting with a digit. Created when it does not exist yet."),
34664
34712
  value: external_exports.union([external_exports.number(), external_exports.string().min(1)]).describe(`Value in mm (or degrees where it is used as an angle)${EXPR}. Other fields can refer to the parameter by name.`)
@@ -34673,8 +34721,28 @@ var inputs = {
34673
34721
  radius: size("Radius of the base"),
34674
34722
  topRadius: external_exports.union([external_exports.number().nonnegative(), external_exports.string().min(1)]).describe(`Radius of the top in mm, 0 for a point${EXPR}`),
34675
34723
  height: size("Height along Z")
34724
+ }),
34725
+ external_exports.object({
34726
+ kind: external_exports.literal("tube"),
34727
+ radius: size("Outer radius"),
34728
+ innerRadius: size("Inner radius, smaller than the outer radius"),
34729
+ height: size("Height along Z")
34730
+ }),
34731
+ external_exports.object({
34732
+ kind: external_exports.literal("torus"),
34733
+ radius: size("Distance from the centre to the middle of the ring's tube"),
34734
+ tubeRadius: size("Radius of the tube, smaller than the ring radius")
34735
+ }),
34736
+ external_exports.object({
34737
+ kind: external_exports.literal("wedge"),
34738
+ width: size("Size of the base along X"),
34739
+ depth: size("Size along Y"),
34740
+ height: size("Height along Z, at the tall side"),
34741
+ topWidth: external_exports.union([external_exports.number().nonnegative(), external_exports.string().min(1)]).describe(`Width of the flat top in mm, 0 for a sharp ramp edge, the width for a box${EXPR}`)
34676
34742
  })
34677
- ]).describe("The solid to add. A primitive stands on the XY plane: its base is centred on `position` and it grows upwards along Z."),
34743
+ ]).describe(
34744
+ "The solid to add. A primitive stands on the XY plane: its base is centred on `position` and it grows upwards along Z. A torus stands on the XY plane too (it spans z 0 to twice its tube radius). A wedge is tallest at its -X side and slopes down to +X; its top is `topWidth` wide."
34745
+ ),
34678
34746
  position: vec3("Where the centre of the base goes").default([0, 0, 0]),
34679
34747
  rotation: vec3("Rotation about X, then Y, then Z, around the base centre", "degrees").default([0, 0, 0])
34680
34748
  }),
@@ -34739,26 +34807,11 @@ var inputs = {
34739
34807
  pattern: external_exports.object({
34740
34808
  body: bodyId,
34741
34809
  result: external_exports.enum(["join", "separate"]).default("join").describe("join fuses the copies into the body; separate makes each copy its own body"),
34742
- layout: external_exports.discriminatedUnion("kind", [
34743
- external_exports.object({
34744
- kind: external_exports.literal("linear"),
34745
- direction: patternDirection,
34746
- count: external_exports.union([external_exports.number().int().min(2).max(100), external_exports.string().min(1)]).describe("Number of copies in a row, including the original (2 to 100)"),
34747
- spacing: size("Distance between copies"),
34748
- second: external_exports.object({
34749
- direction: patternDirection,
34750
- count: external_exports.union([external_exports.number().int().min(2).max(100), external_exports.string().min(1)]).describe("Rows in the second direction, including the original"),
34751
- spacing: size("Distance between rows")
34752
- }).optional().describe("A second direction makes a grid (at most 400 copies in all)")
34753
- }),
34754
- external_exports.object({
34755
- kind: external_exports.literal("circular"),
34756
- axis: external_exports.enum(["x", "y", "z"]).describe("World axis (through `centre`) the copies turn about"),
34757
- centre: vec3("A point on the axis"),
34758
- count: external_exports.union([external_exports.number().int().min(2).max(100), external_exports.string().min(1)]).describe("Number of copies including the original"),
34759
- angle: external_exports.union([external_exports.number().gt(0).max(360), external_exports.string().min(1)]).default(360).describe("360 spreads the copies evenly around the full circle; less puts the last copy at this angle (degrees)")
34760
- })
34761
- ]).describe("How the copies are laid out")
34810
+ layout: patternLayout
34811
+ }),
34812
+ pattern_hole: external_exports.object({
34813
+ hole: featureId.describe("Id of the hole to repeat (returned by hole, or listed by get_project). Its copies are cut into the same body"),
34814
+ layout: patternLayout
34762
34815
  }),
34763
34816
  mirror: external_exports.object({
34764
34817
  body: bodyId,
@@ -34808,9 +34861,11 @@ var inputs = {
34808
34861
  })
34809
34862
  };
34810
34863
  var descriptions = {
34811
- new_project: "Start a new, empty Polypad project (optionally from a template), replacing the open one. All lengths in Polypad are millimetres and angles are degrees; Z is up.",
34864
+ new_project: "Start a new, empty Polypad project (optionally from a template), after saving the open one (it stays in the projects list: list_projects, open_project). All lengths in Polypad are millimetres and angles are degrees; Z is up.",
34865
+ list_projects: "List the projects saved in this browser (name, id, number of features, when last saved, which one is open), newest first. Use open_project to go back to one.",
34866
+ open_project: "Open a project saved in this browser (its id is listed by list_projects), after saving the open one. Nothing is lost; call get_project afterwards to see the project that is open now.",
34812
34867
  set_parameter: "Create or change a named parameter (a number or an expression). Any length field of any feature can use the name, so one change resizes everything bound to it. Reports features that no longer build. Set the sizes up front, then use the names in fields.",
34813
- add_primitive: "Add a solid box, cylinder, sphere or cone as a new body. Returns the new feature's id, which is also the body id for later commands. Sizes in mm.",
34868
+ add_primitive: "Add a solid box, cylinder, tube, sphere, cone, torus or wedge as a new body. Returns the new feature's id, which is also the body id for later commands. Sizes in mm.",
34814
34869
  add_sketch: "Add a 2D sketch on a base plane or on a flat face and draw shapes in it (rectangle, circle, line, polyline, arc, slot, polygon). The sketch is solved and its remaining degrees of freedom reported. Use extrude or revolve to turn closed shapes into a solid.",
34815
34870
  extrude: "Extrude the closed shapes of a sketch into a solid: as a new body, or cut from / joined to an existing body. Returns the feature id (the new body's id when mode is new).",
34816
34871
  revolve: "Revolve the closed shapes of a sketch about an axis in the sketch plane to make a solid of revolution: new body, or cut / join with an existing body. Use it for round parts: draw half the outline beside the axis.",
@@ -34819,7 +34874,8 @@ var descriptions = {
34819
34874
  fillet: "Round the given edges of a body with a radius. Call list_edges first (narrow it with its filter) and pass the edge indices and their rebuildId. If it fails, try a smaller radius or fewer edges.",
34820
34875
  chamfer: "Bevel the given edges of a body. Call list_edges first (narrow it with its filter) and pass the edge indices and their rebuildId. If it fails, try a smaller distance or fewer edges.",
34821
34876
  shell: "Hollow out a body, leaving the given faces open, with walls of a given thickness. Call list_faces first and pass the face indices and their rebuildId. Use this for enclosures and cups instead of subtracting a smaller box; list_faces with filter normal finds the face to open.",
34822
- pattern: "Repeat a body in a row or grid, or around an axis, either fusing the copies into the body or making each copy a body of its own. Model one copy (or one cutter) and pattern it instead of adding copies by hand.",
34877
+ pattern: "Repeat a body in a row or grid, or around an axis, either fusing the copies into the body or making each copy a body of its own. Model one copy (or one cutter) and pattern it instead of adding copies by hand. To repeat a screw hole use pattern_hole instead.",
34878
+ pattern_hole: "Repeat a hole you already made: in a row or grid, or around an axis (a bolt circle, for example 6 holes around a flange). The copies are cut into the hole's body in one go with the hole's size, fit, head and depth, and follow later edits to the hole. The layout works in world axes: for a bolt circle on a top face use kind circular, axis z, centre a point on the axis (the middle of the circle) and the count; the hole itself is one of the copies, so place it on the bolt circle. A copy that lands off the face is skipped and reported.",
34823
34879
  mirror: "Mirror a whole body across a plane (yz, xz or xy, optionally moved along its normal), joining the copy into the body or making it a separate body. Model one side of a symmetric part, then mirror it.",
34824
34880
  move: "Move, rotate and/or scale a whole body (translate in mm, rotate in degrees about X, Y, Z around a pivot).",
34825
34881
  update_feature: "Change fields of an existing feature (for example an extrude's distance or a box's width); everything after it rebuilds. The change is refused, and the project left as it was, if the result is not a valid model. Prefer this over deleting and rebuilding a feature.",
@@ -35451,7 +35507,7 @@ var SERVER_INSTRUCTIONS = `Polypad is a parametric CAD editor. These tools build
35451
35507
  Units and axes: lengths are millimetres, angles degrees, Z is up. Any length field accepts a number or an expression over the project's parameters ("width / 2"); prefer set_parameter for sizes that belong together.
35452
35508
 
35453
35509
  Workflow:
35454
- 1. Start with get_project to see what is already there (the user may have work open). new_project replaces the open project: if it has features, ask the user first and never call it over their work; use it freely only when the project is empty.
35510
+ 1. Start with get_project to see what is already there (the user may have work open). new_project and open_project save the open project first, so nothing is lost: list_projects and open_project go back to earlier work. Still ask the user before switching away from a project they have open with features.
35455
35511
  2. Build with add_primitive, or add_sketch then extrude / revolve. Ids returned by a command are the feature and body ids for later calls.
35456
35512
  3. For hole, shell, fillet, chamfer and for sketching on a face, call list_faces or list_edges first. Pass the indices they return together with the same rebuildId. The rebuildId changes whenever the model changes, so after any change list again; a stale one is refused ("Call list_edges again").
35457
35513
  4. A feature that does not build stays in the project with its error: fix it with update_feature, or remove it with delete_feature or undo.
@@ -35460,8 +35516,10 @@ Workflow:
35460
35516
  Pick the tool by task:
35461
35517
  - Hollow part (enclosure, box, cup): shell the solid with the open face(s) removed (list_faces with filter normal "+z" finds the top); don't subtract a smaller box.
35462
35518
  - Screw holes, standoffs, inserts: hole (size, fit close/normal/loose/tap/insert, counterbore or countersink, blind depth), not cut circles.
35519
+ - Several of the same hole (a bolt circle on a flange, a row or grid of mounting holes): make one hole with hole, then pattern_hole with its id. For a bolt circle use layout kind circular, axis = the face normal axis (z for a top face), centre = a point on the circle's axis, count = number of holes; place the first hole on the circle itself (list_faces gives the face's frame and bounds). Copies that land off the face are skipped and reported.
35463
35520
  - Repeated parts (rows, grids, around a circle, lattices): model one body, or one cutter body, then pattern it (then combine subtract for a cutter). Symmetric parts: model one side, then mirror.
35464
35521
  - Round parts (knobs, turrets, finials, domes): add_sketch on XZ (sketch x = world X, sketch y = world Z), draw half the outline at x >= 0, then revolve with axis "y" (the world Z axis).
35522
+ - Pipes, spacers and rings: the tube primitive (a torus for an O-ring), not two subtracted cylinders.
35465
35523
  - Sizes that belong together or may change: set_parameter first, then use the names in fields.
35466
35524
  - Changing or fixing a feature: update_feature (or undo the last step); don't delete and rebuild.
35467
35525
  - Picking edges: use the list_edges filter (face, direction, kind) and the list_faces filter (normal, kind) instead of reading every edge. For the rim of the top: list_faces normal "+z", then list_edges face <index>.
@@ -35496,7 +35554,7 @@ function createPolypadMcp(options = {}) {
35496
35554
  }
35497
35555
 
35498
35556
  // src/index.ts
35499
- var VERSION = true ? "0.1.2" : "0.0.0-dev";
35557
+ var VERSION = true ? "0.1.7" : "0.0.0-dev";
35500
35558
  var err = (line2) => process.stderr.write(`${line2}
35501
35559
  `);
35502
35560
  async function main() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polypad/mcp",
3
- "version": "0.1.2",
3
+ "version": "0.1.7",
4
4
  "type": "module",
5
5
  "description": "MCP server that lets Claude, Cursor or another AI assistant build and inspect 3D models in your open Polypad tab (polypad.ink). Runs locally, nothing is uploaded.",
6
6
  "license": "MIT",
@@ -18,6 +18,7 @@
18
18
  },
19
19
  "files": [
20
20
  "dist/index.js",
21
+ "dist/THIRD_PARTY_NOTICES.txt",
21
22
  "README.md",
22
23
  "LICENSE"
23
24
  ],
@@ -32,9 +33,11 @@
32
33
  "prepublishOnly": "tsup",
33
34
  "typecheck": "tsc --noEmit",
34
35
  "test": "vitest run",
35
- "lint": "eslint ."
36
+ "lint": "eslint .",
37
+ "build:mcpb": "node mcpb/build.mjs"
36
38
  },
37
39
  "devDependencies": {
40
+ "@anthropic-ai/mcpb": "2.1.2",
38
41
  "@modelcontextprotocol/sdk": "^1.32.1",
39
42
  "@polypad/core": "workspace:*",
40
43
  "@polypad/document": "workspace:*",