@vcad/mcp 0.9.4-main.29 → 0.9.4-main.31
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 +1 -1
- package/index.mjs +165 -77
- package/package.json +3 -3
- package/vcad_kernel_wasm_bg.wasm +0 -0
package/README.md
CHANGED
|
@@ -6,4 +6,4 @@ vcad's MCP server as a self-contained bundle (server + BRep kernel WASM).
|
|
|
6
6
|
{ "mcpServers": { "vcad": { "command": "npx", "args": ["-y", "@vcad/mcp"] } } }
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
-
Built from
|
|
9
|
+
Built from ab04c2e57eeea3736ac3421fabbc01e89959f953 at 2026-07-26T01:25:19.323Z. Source: https://github.com/ecto/vcad
|
package/index.mjs
CHANGED
|
@@ -10873,11 +10873,11 @@ var init_analyze = __esm({
|
|
|
10873
10873
|
type: "module"
|
|
10874
10874
|
});
|
|
10875
10875
|
this.worker = worker;
|
|
10876
|
-
this.ready = new Promise((
|
|
10876
|
+
this.ready = new Promise((resolve4, reject) => {
|
|
10877
10877
|
const onMessage = (e) => {
|
|
10878
10878
|
const { type, id, message, analysis } = e.data;
|
|
10879
10879
|
if (type === "ready") {
|
|
10880
|
-
|
|
10880
|
+
resolve4();
|
|
10881
10881
|
return;
|
|
10882
10882
|
}
|
|
10883
10883
|
if (type === "error" && id === null) {
|
|
@@ -10901,9 +10901,9 @@ var init_analyze = __esm({
|
|
|
10901
10901
|
return this.ready;
|
|
10902
10902
|
}
|
|
10903
10903
|
request(msg, transfer = []) {
|
|
10904
|
-
return this.ensureWorker().then(() => new Promise((
|
|
10904
|
+
return this.ensureWorker().then(() => new Promise((resolve4, reject) => {
|
|
10905
10905
|
const id = this.nextId++;
|
|
10906
|
-
this.pending.set(id, { resolve:
|
|
10906
|
+
this.pending.set(id, { resolve: resolve4, reject });
|
|
10907
10907
|
this.worker.postMessage({ ...msg, id }, transfer);
|
|
10908
10908
|
}));
|
|
10909
10909
|
}
|
|
@@ -11463,11 +11463,11 @@ var init_dist = __esm({
|
|
|
11463
11463
|
return;
|
|
11464
11464
|
try {
|
|
11465
11465
|
const worker = new Worker(new URL("./eval-worker.js", import.meta.url), { type: "module" });
|
|
11466
|
-
this.workerReady = new Promise((
|
|
11466
|
+
this.workerReady = new Promise((resolve4, reject) => {
|
|
11467
11467
|
const onMessage = (e) => {
|
|
11468
11468
|
if (e.data.type === "ready") {
|
|
11469
11469
|
worker.removeEventListener("message", onMessage);
|
|
11470
|
-
|
|
11470
|
+
resolve4();
|
|
11471
11471
|
} else if (e.data.type === "error" && e.data.id === null) {
|
|
11472
11472
|
worker.removeEventListener("message", onMessage);
|
|
11473
11473
|
console.warn("[ENGINE] Worker WASM init failed:", e.data.message);
|
|
@@ -11513,6 +11513,7 @@ var init_dist = __esm({
|
|
|
11513
11513
|
createDetailView: wasmModule7.createDetailView,
|
|
11514
11514
|
evaluateDocument: wasmModule7.evaluateDocument,
|
|
11515
11515
|
evalVcadSource: wasmModule7.evalVcadSource,
|
|
11516
|
+
evalVcadSourceWithModules: wasmModule7.evalVcadSourceWithModules,
|
|
11516
11517
|
documentParameterGradient: wasmModule7.documentParameterGradient,
|
|
11517
11518
|
getPartsManifest: wasmModule7.getPartsManifest,
|
|
11518
11519
|
buildPart: wasmModule7.buildPart,
|
|
@@ -11656,7 +11657,7 @@ var init_dist = __esm({
|
|
|
11656
11657
|
const worker = this.worker;
|
|
11657
11658
|
const id = Math.random().toString(36).slice(2);
|
|
11658
11659
|
const skipClash = options.skipClashDetection ?? false;
|
|
11659
|
-
return new Promise((
|
|
11660
|
+
return new Promise((resolve4, reject) => {
|
|
11660
11661
|
const onMessage = (e) => {
|
|
11661
11662
|
if (e.data.id !== id)
|
|
11662
11663
|
return;
|
|
@@ -11666,7 +11667,7 @@ var init_dist = __esm({
|
|
|
11666
11667
|
if (scene.timing && _Engine.DEV) {
|
|
11667
11668
|
_Engine.logTiming(scene.timing, e.data.workerTotalMs);
|
|
11668
11669
|
}
|
|
11669
|
-
|
|
11670
|
+
resolve4(scene);
|
|
11670
11671
|
} else if (e.data.type === "error") {
|
|
11671
11672
|
reject(new Error(e.data.message));
|
|
11672
11673
|
}
|
|
@@ -12185,6 +12186,21 @@ var init_dist = __esm({
|
|
|
12185
12186
|
const json = this.kernel.evalVcadSource(source);
|
|
12186
12187
|
return JSON.parse(json);
|
|
12187
12188
|
}
|
|
12189
|
+
/**
|
|
12190
|
+
* Evaluate loon source whose `[use ...]` resolves against an in-memory
|
|
12191
|
+
* `name -> source` map — the browser's stand-in for a filesystem.
|
|
12192
|
+
*
|
|
12193
|
+
* With an empty map this is exactly {@link evalVcadSource}. Returns null
|
|
12194
|
+
* if the kernel doesn't support module-aware loon evaluation.
|
|
12195
|
+
*/
|
|
12196
|
+
evalVcadSourceWithModules(source, modules) {
|
|
12197
|
+
if (!Object.keys(modules).length)
|
|
12198
|
+
return this.evalVcadSource(source);
|
|
12199
|
+
if (!this.kernel.evalVcadSourceWithModules)
|
|
12200
|
+
return null;
|
|
12201
|
+
const json = this.kernel.evalVcadSourceWithModules(source, JSON.stringify(modules));
|
|
12202
|
+
return JSON.parse(json);
|
|
12203
|
+
}
|
|
12188
12204
|
/** Evaluate a preview extrusion without adding to document */
|
|
12189
12205
|
evaluateExtrudePreview(origin, xDir, yDir, segments, direction) {
|
|
12190
12206
|
if (segments.length === 0)
|
|
@@ -17849,12 +17865,12 @@ var init_stdio2 = __esm({
|
|
|
17849
17865
|
this.onclose?.();
|
|
17850
17866
|
}
|
|
17851
17867
|
send(message) {
|
|
17852
|
-
return new Promise((
|
|
17868
|
+
return new Promise((resolve4) => {
|
|
17853
17869
|
const json = serializeMessage(message);
|
|
17854
17870
|
if (this._stdout.write(json)) {
|
|
17855
|
-
|
|
17871
|
+
resolve4();
|
|
17856
17872
|
} else {
|
|
17857
|
-
this._stdout.once("drain",
|
|
17873
|
+
this._stdout.once("drain", resolve4);
|
|
17858
17874
|
}
|
|
17859
17875
|
});
|
|
17860
17876
|
}
|
|
@@ -18898,7 +18914,7 @@ var init_protocol = __esm({
|
|
|
18898
18914
|
return;
|
|
18899
18915
|
}
|
|
18900
18916
|
const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1e3;
|
|
18901
|
-
await new Promise((
|
|
18917
|
+
await new Promise((resolve4) => setTimeout(resolve4, pollInterval));
|
|
18902
18918
|
options?.signal?.throwIfAborted();
|
|
18903
18919
|
}
|
|
18904
18920
|
} catch (error2) {
|
|
@@ -18915,7 +18931,7 @@ var init_protocol = __esm({
|
|
|
18915
18931
|
*/
|
|
18916
18932
|
request(request, resultSchema, options) {
|
|
18917
18933
|
const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
|
|
18918
|
-
return new Promise((
|
|
18934
|
+
return new Promise((resolve4, reject) => {
|
|
18919
18935
|
const earlyReject = (error2) => {
|
|
18920
18936
|
reject(error2);
|
|
18921
18937
|
};
|
|
@@ -18993,7 +19009,7 @@ var init_protocol = __esm({
|
|
|
18993
19009
|
if (!parseResult.success) {
|
|
18994
19010
|
reject(parseResult.error);
|
|
18995
19011
|
} else {
|
|
18996
|
-
|
|
19012
|
+
resolve4(parseResult.data);
|
|
18997
19013
|
}
|
|
18998
19014
|
} catch (error2) {
|
|
18999
19015
|
reject(error2);
|
|
@@ -19254,12 +19270,12 @@ var init_protocol = __esm({
|
|
|
19254
19270
|
}
|
|
19255
19271
|
} catch {
|
|
19256
19272
|
}
|
|
19257
|
-
return new Promise((
|
|
19273
|
+
return new Promise((resolve4, reject) => {
|
|
19258
19274
|
if (signal.aborted) {
|
|
19259
19275
|
reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
|
|
19260
19276
|
return;
|
|
19261
19277
|
}
|
|
19262
|
-
const timeoutId = setTimeout(
|
|
19278
|
+
const timeoutId = setTimeout(resolve4, interval);
|
|
19263
19279
|
signal.addEventListener("abort", () => {
|
|
19264
19280
|
clearTimeout(timeoutId);
|
|
19265
19281
|
reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
|
|
@@ -22286,7 +22302,7 @@ var require_compile = __commonJS({
|
|
|
22286
22302
|
const schOrFunc = root.refs[ref];
|
|
22287
22303
|
if (schOrFunc)
|
|
22288
22304
|
return schOrFunc;
|
|
22289
|
-
let _sch =
|
|
22305
|
+
let _sch = resolve4.call(this, root, ref);
|
|
22290
22306
|
if (_sch === void 0) {
|
|
22291
22307
|
const schema = (_a = root.localRefs) === null || _a === void 0 ? void 0 : _a[ref];
|
|
22292
22308
|
const { schemaId } = this.opts;
|
|
@@ -22313,7 +22329,7 @@ var require_compile = __commonJS({
|
|
|
22313
22329
|
function sameSchemaEnv(s1, s2) {
|
|
22314
22330
|
return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
|
|
22315
22331
|
}
|
|
22316
|
-
function
|
|
22332
|
+
function resolve4(root, ref) {
|
|
22317
22333
|
let sch;
|
|
22318
22334
|
while (typeof (sch = this.refs[ref]) == "string")
|
|
22319
22335
|
ref = sch;
|
|
@@ -22888,7 +22904,7 @@ var require_fast_uri = __commonJS({
|
|
|
22888
22904
|
}
|
|
22889
22905
|
return uri;
|
|
22890
22906
|
}
|
|
22891
|
-
function
|
|
22907
|
+
function resolve4(baseURI, relativeURI, options) {
|
|
22892
22908
|
const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
|
|
22893
22909
|
const resolved = resolveComponent(parse4(baseURI, schemelessOptions), parse4(relativeURI, schemelessOptions), schemelessOptions, true);
|
|
22894
22910
|
schemelessOptions.skipEscape = true;
|
|
@@ -23115,7 +23131,7 @@ var require_fast_uri = __commonJS({
|
|
|
23115
23131
|
var fastUri = {
|
|
23116
23132
|
SCHEMES,
|
|
23117
23133
|
normalize,
|
|
23118
|
-
resolve:
|
|
23134
|
+
resolve: resolve4,
|
|
23119
23135
|
resolveComponent,
|
|
23120
23136
|
equal,
|
|
23121
23137
|
serialize,
|
|
@@ -29268,21 +29284,21 @@ var require_react_development = __commonJS({
|
|
|
29268
29284
|
);
|
|
29269
29285
|
actScopeDepth = prevActScopeDepth;
|
|
29270
29286
|
}
|
|
29271
|
-
function recursivelyFlushAsyncActWork(returnValue,
|
|
29287
|
+
function recursivelyFlushAsyncActWork(returnValue, resolve4, reject) {
|
|
29272
29288
|
var queue = ReactSharedInternals.actQueue;
|
|
29273
29289
|
if (null !== queue)
|
|
29274
29290
|
if (0 !== queue.length)
|
|
29275
29291
|
try {
|
|
29276
29292
|
flushActQueue(queue);
|
|
29277
29293
|
enqueueTask(function() {
|
|
29278
|
-
return recursivelyFlushAsyncActWork(returnValue,
|
|
29294
|
+
return recursivelyFlushAsyncActWork(returnValue, resolve4, reject);
|
|
29279
29295
|
});
|
|
29280
29296
|
return;
|
|
29281
29297
|
} catch (error2) {
|
|
29282
29298
|
ReactSharedInternals.thrownErrors.push(error2);
|
|
29283
29299
|
}
|
|
29284
29300
|
else ReactSharedInternals.actQueue = null;
|
|
29285
|
-
0 < ReactSharedInternals.thrownErrors.length ? (queue = aggregateErrors(ReactSharedInternals.thrownErrors), ReactSharedInternals.thrownErrors.length = 0, reject(queue)) :
|
|
29301
|
+
0 < ReactSharedInternals.thrownErrors.length ? (queue = aggregateErrors(ReactSharedInternals.thrownErrors), ReactSharedInternals.thrownErrors.length = 0, reject(queue)) : resolve4(returnValue);
|
|
29286
29302
|
}
|
|
29287
29303
|
function flushActQueue(queue) {
|
|
29288
29304
|
if (!isFlushing) {
|
|
@@ -29469,7 +29485,7 @@ var require_react_development = __commonJS({
|
|
|
29469
29485
|
));
|
|
29470
29486
|
});
|
|
29471
29487
|
return {
|
|
29472
|
-
then: function(
|
|
29488
|
+
then: function(resolve4, reject) {
|
|
29473
29489
|
didAwaitActCall = true;
|
|
29474
29490
|
thenable.then(
|
|
29475
29491
|
function(returnValue) {
|
|
@@ -29479,7 +29495,7 @@ var require_react_development = __commonJS({
|
|
|
29479
29495
|
flushActQueue(queue), enqueueTask(function() {
|
|
29480
29496
|
return recursivelyFlushAsyncActWork(
|
|
29481
29497
|
returnValue,
|
|
29482
|
-
|
|
29498
|
+
resolve4,
|
|
29483
29499
|
reject
|
|
29484
29500
|
);
|
|
29485
29501
|
});
|
|
@@ -29493,7 +29509,7 @@ var require_react_development = __commonJS({
|
|
|
29493
29509
|
ReactSharedInternals.thrownErrors.length = 0;
|
|
29494
29510
|
reject(_thrownError);
|
|
29495
29511
|
}
|
|
29496
|
-
} else
|
|
29512
|
+
} else resolve4(returnValue);
|
|
29497
29513
|
},
|
|
29498
29514
|
function(error2) {
|
|
29499
29515
|
popActScope(prevActQueue, prevActScopeDepth);
|
|
@@ -29515,15 +29531,15 @@ var require_react_development = __commonJS({
|
|
|
29515
29531
|
if (0 < ReactSharedInternals.thrownErrors.length)
|
|
29516
29532
|
throw callback = aggregateErrors(ReactSharedInternals.thrownErrors), ReactSharedInternals.thrownErrors.length = 0, callback;
|
|
29517
29533
|
return {
|
|
29518
|
-
then: function(
|
|
29534
|
+
then: function(resolve4, reject) {
|
|
29519
29535
|
didAwaitActCall = true;
|
|
29520
29536
|
0 === prevActScopeDepth ? (ReactSharedInternals.actQueue = queue, enqueueTask(function() {
|
|
29521
29537
|
return recursivelyFlushAsyncActWork(
|
|
29522
29538
|
returnValue$jscomp$0,
|
|
29523
|
-
|
|
29539
|
+
resolve4,
|
|
29524
29540
|
reject
|
|
29525
29541
|
);
|
|
29526
|
-
})) :
|
|
29542
|
+
})) : resolve4(returnValue$jscomp$0);
|
|
29527
29543
|
}
|
|
29528
29544
|
};
|
|
29529
29545
|
};
|
|
@@ -41084,6 +41100,22 @@ var init_CHANGELOG = __esm({
|
|
|
41084
41100
|
"checkpoint_document"
|
|
41085
41101
|
]
|
|
41086
41102
|
},
|
|
41103
|
+
{
|
|
41104
|
+
id: "2026-07-25-loon-modules-everywhere",
|
|
41105
|
+
version: "0.9.4",
|
|
41106
|
+
date: "2026-07-25",
|
|
41107
|
+
category: "feat",
|
|
41108
|
+
title: "Multi-file loon projects, everywhere loon runs",
|
|
41109
|
+
summary: "[use ...] now resolves in the browser and over MCP, not just on disk: pass modules by value or point create_cad_loon at a base_dir, and pub, aliasing and selective imports all work.",
|
|
41110
|
+
features: [
|
|
41111
|
+
"loon",
|
|
41112
|
+
"mcp",
|
|
41113
|
+
"modules"
|
|
41114
|
+
],
|
|
41115
|
+
mcpTools: [
|
|
41116
|
+
"create_cad_loon"
|
|
41117
|
+
]
|
|
41118
|
+
},
|
|
41087
41119
|
{
|
|
41088
41120
|
id: "2026-07-25-kicad-export-design-rules",
|
|
41089
41121
|
version: "0.9.4",
|
|
@@ -49566,6 +49598,8 @@ var init_loon_macros = __esm({
|
|
|
49566
49598
|
});
|
|
49567
49599
|
|
|
49568
49600
|
// src/tools/loon.ts
|
|
49601
|
+
import { readFileSync as readFileSync3 } from "node:fs";
|
|
49602
|
+
import { isAbsolute as isAbsolute2, join as join2, resolve as resolve2, sep as sep2 } from "node:path";
|
|
49569
49603
|
function composeLoonProgram(input) {
|
|
49570
49604
|
const { source, use_loons, loons } = input;
|
|
49571
49605
|
const names = [
|
|
@@ -49577,14 +49611,56 @@ function composeLoonProgram(input) {
|
|
|
49577
49611
|
|
|
49578
49612
|
${source}`;
|
|
49579
49613
|
}
|
|
49614
|
+
function importedNames(source) {
|
|
49615
|
+
const names = [];
|
|
49616
|
+
const re = /\[\s*use\s+([A-Za-z_][\w.-]*)/g;
|
|
49617
|
+
for (let m = re.exec(source); m; m = re.exec(source)) names.push(m[1]);
|
|
49618
|
+
return names;
|
|
49619
|
+
}
|
|
49620
|
+
function readModule(base, name) {
|
|
49621
|
+
if (isAbsolute2(name) || name.split(".").includes("..")) return null;
|
|
49622
|
+
const root = resolve2(base);
|
|
49623
|
+
for (const ext of [".loon", ".oo"]) {
|
|
49624
|
+
const file = resolve2(join2(root, ...name.split(".")) + ext);
|
|
49625
|
+
if (file !== root && !file.startsWith(root + sep2)) continue;
|
|
49626
|
+
try {
|
|
49627
|
+
return readFileSync3(file, "utf8");
|
|
49628
|
+
} catch {
|
|
49629
|
+
}
|
|
49630
|
+
}
|
|
49631
|
+
return null;
|
|
49632
|
+
}
|
|
49633
|
+
function composeLoonModules(input) {
|
|
49634
|
+
const { source, loons, modules, base_dir } = input;
|
|
49635
|
+
const map = {};
|
|
49636
|
+
for (const m of loons ?? []) map[m.name] = m.source;
|
|
49637
|
+
Object.assign(map, modules ?? {});
|
|
49638
|
+
if (base_dir) {
|
|
49639
|
+
const pending2 = [
|
|
49640
|
+
...importedNames(source),
|
|
49641
|
+
...Object.values(map).flatMap(importedNames)
|
|
49642
|
+
];
|
|
49643
|
+
const seen = /* @__PURE__ */ new Set();
|
|
49644
|
+
while (pending2.length) {
|
|
49645
|
+
const name = pending2.pop();
|
|
49646
|
+
if (seen.has(name) || map[name]) continue;
|
|
49647
|
+
seen.add(name);
|
|
49648
|
+
const src = readModule(base_dir, name);
|
|
49649
|
+
if (src === null) continue;
|
|
49650
|
+
map[name] = src;
|
|
49651
|
+
pending2.push(...importedNames(src));
|
|
49652
|
+
}
|
|
49653
|
+
}
|
|
49654
|
+
return map;
|
|
49655
|
+
}
|
|
49580
49656
|
function createCadLoon(input, engine) {
|
|
49581
49657
|
const { format = "vcode" } = input;
|
|
49582
49658
|
const source = composeLoonProgram(input);
|
|
49583
|
-
const
|
|
49659
|
+
const modules = composeLoonModules(input);
|
|
49660
|
+
const doc = engine.evalVcadSourceWithModules(source, modules);
|
|
49584
49661
|
if (!doc) {
|
|
49585
|
-
|
|
49586
|
-
|
|
49587
|
-
};
|
|
49662
|
+
const text2 = Object.keys(modules).length ? "Error: this kernel build cannot resolve loon modules ([use ...]) \u2014 update the kernel, or inline the modules into `source`" : "Error: Loon evaluation not supported by this engine build";
|
|
49663
|
+
return { content: [{ type: "text", text: text2 }] };
|
|
49588
49664
|
}
|
|
49589
49665
|
const text = format === "json" ? JSON.stringify(doc, null, 2) : toVCode2(doc);
|
|
49590
49666
|
return {
|
|
@@ -49629,6 +49705,15 @@ var init_loon = __esm({
|
|
|
49629
49705
|
}
|
|
49630
49706
|
}
|
|
49631
49707
|
},
|
|
49708
|
+
modules: {
|
|
49709
|
+
type: "object",
|
|
49710
|
+
description: 'Multi-file projects, by value: a { "<module name>": "<loon source>" } map that `[use <name>]` in `source` resolves against. `pub` controls what a module exports; `[use m :as alias]` and `[use m [a b]]` work as in the language. Entries passed via `loons` are importable by name too.',
|
|
49711
|
+
additionalProperties: { type: "string" }
|
|
49712
|
+
},
|
|
49713
|
+
base_dir: {
|
|
49714
|
+
type: "string",
|
|
49715
|
+
description: "Server-side directory that `[use <name>]` resolves against \u2014 the server reads <base_dir>/<name>.loon (dots are path separators) and hands the sources to the kernel, following nested imports. Reads are confined to this directory. Explicit `modules` entries win."
|
|
49716
|
+
},
|
|
49632
49717
|
format: {
|
|
49633
49718
|
type: "string",
|
|
49634
49719
|
enum: ["vcode", "json"],
|
|
@@ -49641,7 +49726,7 @@ var init_loon = __esm({
|
|
|
49641
49726
|
{
|
|
49642
49727
|
name: "create_cad_loon",
|
|
49643
49728
|
pack: null,
|
|
49644
|
-
description: 'The preferred authoring tool for whole parts and multi-feature models \u2014 one call, full vocabulary. Create a CAD document from loon source code. Loon is a Lisp-like language for parametric CAD \u2014 the FULL modeling vocabulary (patterns, sketches, extrude/revolve/sweep/loft, assemblies) is available here even where no dedicated MCP tool exists. For incremental single-node edits to an open session, use create/update/delete instead.\n\nPrimitives: [cube x y z], [cylinder r h], [sphere r], [cone r-bottom r-top h]\nBooleans (subject-last): [difference tool subject], [union other subject], [intersection other subject]\nTransforms (subject-last): [translate x y z s], [rotate rx ry rz s], [scale sx sy sz s]\nFeatures: [fillet r s], [chamfer d s], [shell t s]\nPatterns (subject-last): [linear-pattern dx dy dz count spacing s], [circular-pattern ox oy oz ax ay az count angle s] \u2014 e.g. a bolt circle is [circular-pattern 0 0 0 0 0 1 6 360 bolt-hole]\nSketches: [sketch ox oy oz xx xy xz yx yy yz #[segments]] with [line x1 y1 x2 y2] and [arc x1 y1 x2 y2 cx cy ccw]\nSketch ops (sketch-last): [extrude dx dy dz sk], [revolve aox aoy aoz adx ady adz angle sk], [sweep-line sx sy sz ex ey ez sk], [sweep-helix radius pitch height turns sk], [loft #[sk1 sk2 \u2026]]\nAssemblies: [assembly #[parts] #[instances] #[joints] ground-id] with [part name solid "material"], [instance name part-name x y z], [revolute-joint \u2026], [prismatic-joint \u2026], [fixed-joint \u2026], [ball-joint \u2026]\nPipe: [pipe [cube 50 30 5] [difference [cylinder 3 10]] [fillet 1.0]]\nLet bindings: [let body [cube 50 30 5]]\nScene: [root solid "material-name"]',
|
|
49729
|
+
description: 'The preferred authoring tool for whole parts and multi-feature models \u2014 one call, full vocabulary. Create a CAD document from loon source code. Loon is a Lisp-like language for parametric CAD \u2014 the FULL modeling vocabulary (patterns, sketches, extrude/revolve/sweep/loft, assemblies) is available here even where no dedicated MCP tool exists. For incremental single-node edits to an open session, use create/update/delete instead.\n\nPrimitives: [cube x y z], [cylinder r h], [sphere r], [cone r-bottom r-top h]\nBooleans (subject-last): [difference tool subject], [union other subject], [intersection other subject]\nTransforms (subject-last): [translate x y z s], [rotate rx ry rz s], [scale sx sy sz s]\nFeatures: [fillet r s], [chamfer d s], [shell t s]\nPatterns (subject-last): [linear-pattern dx dy dz count spacing s], [circular-pattern ox oy oz ax ay az count angle s] \u2014 e.g. a bolt circle is [circular-pattern 0 0 0 0 0 1 6 360 bolt-hole]\nSketches: [sketch ox oy oz xx xy xz yx yy yz #[segments]] with [line x1 y1 x2 y2] and [arc x1 y1 x2 y2 cx cy ccw]\nSketch ops (sketch-last): [extrude dx dy dz sk], [revolve aox aoy aoz adx ady adz angle sk], [sweep-line sx sy sz ex ey ez sk], [sweep-helix radius pitch height turns sk], [loft #[sk1 sk2 \u2026]]\nAssemblies: [assembly #[parts] #[instances] #[joints] ground-id] with [part name solid "material"], [instance name part-name x y z], [revolute-joint \u2026], [prismatic-joint \u2026], [fixed-joint \u2026], [ball-joint \u2026]\nPipe: [pipe [cube 50 30 5] [difference [cylinder 3 10]] [fillet 1.0]]\nLet bindings: [let body [cube 50 30 5]]\nScene: [root solid "material-name"]\nModules: [use bracket] then [bracket.plate] \u2014 multi-file projects work here, with sources passed in `modules` (or read from `base_dir`); [use bracket :as b] aliases, [use bracket [plate]] imports selectively, and `pub` in a module picks what it exports',
|
|
49645
49730
|
inputSchema: createCadLoonSchema,
|
|
49646
49731
|
handler: async (args, ctx) => {
|
|
49647
49732
|
const useLoons = Array.isArray(args.use_loons) ? args.use_loons : void 0;
|
|
@@ -49653,7 +49738,10 @@ var init_loon = __esm({
|
|
|
49653
49738
|
const targetId = typeof args.document_id === "string" ? args.document_id : null;
|
|
49654
49739
|
if (targetId) getSession(targetId);
|
|
49655
49740
|
try {
|
|
49656
|
-
const doc = ctx.engine.
|
|
49741
|
+
const doc = ctx.engine.evalVcadSourceWithModules(
|
|
49742
|
+
composeLoonProgram(args),
|
|
49743
|
+
composeLoonModules(args)
|
|
49744
|
+
);
|
|
49657
49745
|
if (doc) {
|
|
49658
49746
|
if (targetId) documents.set(targetId, doc);
|
|
49659
49747
|
const integrity = computeIntegrity(doc, ctx.engine);
|
|
@@ -52928,7 +53016,7 @@ function normalizeOp(op, index) {
|
|
|
52928
53016
|
return { name, args };
|
|
52929
53017
|
}
|
|
52930
53018
|
function resolveSymbolicRefs(args, createdIds, index) {
|
|
52931
|
-
const
|
|
53019
|
+
const resolve4 = (v) => {
|
|
52932
53020
|
if (typeof v !== "string") return v;
|
|
52933
53021
|
const m = SYMBOLIC_REF.exec(v);
|
|
52934
53022
|
if (!m) return v;
|
|
@@ -52948,15 +53036,15 @@ function resolveSymbolicRefs(args, createdIds, index) {
|
|
|
52948
53036
|
};
|
|
52949
53037
|
const out = { ...args };
|
|
52950
53038
|
for (const key of ["part_id", "node_id"]) {
|
|
52951
|
-
if (key in out) out[key] =
|
|
53039
|
+
if (key in out) out[key] = resolve4(out[key]);
|
|
52952
53040
|
}
|
|
52953
53041
|
if (out.params && typeof out.params === "object" && !Array.isArray(out.params)) {
|
|
52954
53042
|
const params = { ...out.params };
|
|
52955
53043
|
for (const key of ["child", "left", "right", "sketch"]) {
|
|
52956
|
-
if (key in params) params[key] =
|
|
53044
|
+
if (key in params) params[key] = resolve4(params[key]);
|
|
52957
53045
|
}
|
|
52958
53046
|
if (Array.isArray(params.sketches)) {
|
|
52959
|
-
params.sketches = params.sketches.map(
|
|
53047
|
+
params.sketches = params.sketches.map(resolve4);
|
|
52960
53048
|
}
|
|
52961
53049
|
out.params = params;
|
|
52962
53050
|
}
|
|
@@ -57549,12 +57637,12 @@ var init_enclosure = __esm({
|
|
|
57549
57637
|
// src/wasm/ecad-diff.ts
|
|
57550
57638
|
import { createRequire } from "node:module";
|
|
57551
57639
|
import { fileURLToPath } from "node:url";
|
|
57552
|
-
import { dirname, resolve as
|
|
57640
|
+
import { dirname, resolve as resolve3 } from "node:path";
|
|
57553
57641
|
function load() {
|
|
57554
57642
|
if (cached2 !== void 0) return cached2;
|
|
57555
57643
|
try {
|
|
57556
57644
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
57557
|
-
const pkg =
|
|
57645
|
+
const pkg = resolve3(here, "../../../../crates/vcad-ecad-diff-wasm/pkg/vcad_ecad_diff_wasm.js");
|
|
57558
57646
|
const req = createRequire(import.meta.url);
|
|
57559
57647
|
cached2 = req(pkg);
|
|
57560
57648
|
} catch {
|
|
@@ -60096,9 +60184,9 @@ function isPinTypeOrPowerViolation(message) {
|
|
|
60096
60184
|
function synthesizeNettedSheet(sheet, derived) {
|
|
60097
60185
|
const connectedByRef = /* @__PURE__ */ new Map();
|
|
60098
60186
|
for (const key of derived.netByPin.keys()) {
|
|
60099
|
-
const
|
|
60100
|
-
const ref = key.slice(0,
|
|
60101
|
-
const pin = key.slice(
|
|
60187
|
+
const sep3 = key.indexOf(PIN_SEP);
|
|
60188
|
+
const ref = key.slice(0, sep3);
|
|
60189
|
+
const pin = key.slice(sep3 + 1);
|
|
60102
60190
|
let set = connectedByRef.get(ref);
|
|
60103
60191
|
if (!set) connectedByRef.set(ref, set = /* @__PURE__ */ new Set());
|
|
60104
60192
|
set.add(pin);
|
|
@@ -69611,12 +69699,12 @@ import {
|
|
|
69611
69699
|
existsSync as existsSync2,
|
|
69612
69700
|
mkdtempSync,
|
|
69613
69701
|
readdirSync as readdirSync2,
|
|
69614
|
-
readFileSync as
|
|
69702
|
+
readFileSync as readFileSync4,
|
|
69615
69703
|
rmSync,
|
|
69616
69704
|
writeFileSync as writeFileSync4
|
|
69617
69705
|
} from "fs";
|
|
69618
69706
|
import { tmpdir } from "os";
|
|
69619
|
-
import { dirname as dirname2, join as
|
|
69707
|
+
import { dirname as dirname2, join as join3 } from "path";
|
|
69620
69708
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
69621
69709
|
import { promisify } from "util";
|
|
69622
69710
|
function textResult(payload, isError = false) {
|
|
@@ -69625,7 +69713,7 @@ function textResult(payload, isError = false) {
|
|
|
69625
69713
|
function findUpward(start, probe) {
|
|
69626
69714
|
let dir = start;
|
|
69627
69715
|
for (let i = 0; i < 12; i++) {
|
|
69628
|
-
if (existsSync2(
|
|
69716
|
+
if (existsSync2(join3(dir, probe))) return dir;
|
|
69629
69717
|
const parent = dirname2(dir);
|
|
69630
69718
|
if (parent === dir) break;
|
|
69631
69719
|
dir = parent;
|
|
@@ -69634,18 +69722,18 @@ function findUpward(start, probe) {
|
|
|
69634
69722
|
}
|
|
69635
69723
|
function findMechevalRoot() {
|
|
69636
69724
|
const override = process.env.MECHEVAL_DIR;
|
|
69637
|
-
if (override && existsSync2(
|
|
69725
|
+
if (override && existsSync2(join3(override, "tasks"))) {
|
|
69638
69726
|
return override;
|
|
69639
69727
|
}
|
|
69640
69728
|
const here = dirname2(fileURLToPath2(import.meta.url));
|
|
69641
|
-
const repoRoot = findUpward(here,
|
|
69642
|
-
return repoRoot ?
|
|
69729
|
+
const repoRoot = findUpward(here, join3("mecheval", "tasks")) ?? findUpward(process.cwd(), join3("mecheval", "tasks"));
|
|
69730
|
+
return repoRoot ? join3(repoRoot, "mecheval") : null;
|
|
69643
69731
|
}
|
|
69644
69732
|
function findGraderBin(mechevalRoot) {
|
|
69645
69733
|
const override = process.env.MECHEVAL_GRADE_BIN;
|
|
69646
69734
|
if (override && existsSync2(override)) return override;
|
|
69647
69735
|
for (const profile of ["release", "debug"]) {
|
|
69648
|
-
const candidate =
|
|
69736
|
+
const candidate = join3(
|
|
69649
69737
|
dirname2(mechevalRoot),
|
|
69650
69738
|
"target",
|
|
69651
69739
|
profile,
|
|
@@ -69665,13 +69753,13 @@ function listEvalTasks(args) {
|
|
|
69665
69753
|
true
|
|
69666
69754
|
);
|
|
69667
69755
|
}
|
|
69668
|
-
const tasksDir =
|
|
69756
|
+
const tasksDir = join3(mechevalRoot, "tasks");
|
|
69669
69757
|
const suiteFilter = args.suite ? String(args.suite).toUpperCase() : null;
|
|
69670
69758
|
const tasks = [];
|
|
69671
69759
|
for (const file of readdirSync2(tasksDir).sort()) {
|
|
69672
69760
|
if (!file.endsWith(".json")) continue;
|
|
69673
69761
|
try {
|
|
69674
|
-
const raw = JSON.parse(
|
|
69762
|
+
const raw = JSON.parse(readFileSync4(join3(tasksDir, file), "utf8"));
|
|
69675
69763
|
if (suiteFilter && String(raw.suite).toUpperCase() !== suiteFilter) {
|
|
69676
69764
|
continue;
|
|
69677
69765
|
}
|
|
@@ -69705,7 +69793,7 @@ async function verifyPart(args) {
|
|
|
69705
69793
|
if (!/^[A-Za-z0-9._-]+$/.test(taskId)) {
|
|
69706
69794
|
return textResult({ error: `invalid task_id: ${taskId}` }, true);
|
|
69707
69795
|
}
|
|
69708
|
-
const taskPath =
|
|
69796
|
+
const taskPath = join3(mechevalRoot, "tasks", `${taskId}.json`);
|
|
69709
69797
|
if (!existsSync2(taskPath)) {
|
|
69710
69798
|
return textResult(
|
|
69711
69799
|
{
|
|
@@ -69725,8 +69813,8 @@ async function verifyPart(args) {
|
|
|
69725
69813
|
true
|
|
69726
69814
|
);
|
|
69727
69815
|
}
|
|
69728
|
-
const scratch = mkdtempSync(
|
|
69729
|
-
const vcadPath =
|
|
69816
|
+
const scratch = mkdtempSync(join3(tmpdir(), "vcad-verify-"));
|
|
69817
|
+
const vcadPath = join3(scratch, "candidate.vcad");
|
|
69730
69818
|
try {
|
|
69731
69819
|
writeFileSync4(vcadPath, JSON.stringify(doc));
|
|
69732
69820
|
let stdout;
|
|
@@ -73813,7 +73901,7 @@ var init_acoustics = __esm({
|
|
|
73813
73901
|
});
|
|
73814
73902
|
|
|
73815
73903
|
// src/tools/import.ts
|
|
73816
|
-
import { readFileSync as
|
|
73904
|
+
import { readFileSync as readFileSync5, existsSync as existsSync3, statSync } from "node:fs";
|
|
73817
73905
|
import { basename } from "node:path";
|
|
73818
73906
|
function importStep(input, engine) {
|
|
73819
73907
|
const { filename, content_base64, name, material } = input;
|
|
@@ -73846,7 +73934,7 @@ function importStep(input, engine) {
|
|
|
73846
73934
|
if (stat.size > MAX_STEP_BYTES) {
|
|
73847
73935
|
throw new Error(`STEP file exceeds ${MAX_STEP_BYTES} byte limit`);
|
|
73848
73936
|
}
|
|
73849
|
-
fileBuffer =
|
|
73937
|
+
fileBuffer = readFileSync5(filepath);
|
|
73850
73938
|
} else {
|
|
73851
73939
|
throw new Error("Provide either `filename` or `content_base64`");
|
|
73852
73940
|
}
|
|
@@ -73996,7 +74084,7 @@ var init_import = __esm({
|
|
|
73996
74084
|
});
|
|
73997
74085
|
|
|
73998
74086
|
// src/tools/import-pcb.ts
|
|
73999
|
-
import { readFileSync as
|
|
74087
|
+
import { readFileSync as readFileSync6, existsSync as existsSync4, statSync as statSync2 } from "node:fs";
|
|
74000
74088
|
function importError(text) {
|
|
74001
74089
|
return {
|
|
74002
74090
|
content: [{ type: "text", text: JSON.stringify({ error: text }) }],
|
|
@@ -74029,7 +74117,7 @@ function readSource(input, label) {
|
|
|
74029
74117
|
if (stat.size > MAX_PCB_BYTES) {
|
|
74030
74118
|
throw new Error(`${label} file exceeds the ${MAX_PCB_BYTES} byte limit`);
|
|
74031
74119
|
}
|
|
74032
|
-
return
|
|
74120
|
+
return readFileSync6(filepath, "utf8");
|
|
74033
74121
|
}
|
|
74034
74122
|
throw new Error("Provide either `filename` or `content_base64`");
|
|
74035
74123
|
}
|
|
@@ -77583,13 +77671,13 @@ ${issues.map((i) => i.problem).join("\n")}`
|
|
|
77583
77671
|
}
|
|
77584
77672
|
async function hasFfmpeg() {
|
|
77585
77673
|
if (ffmpegAvailable !== null) return ffmpegAvailable;
|
|
77586
|
-
ffmpegAvailable = await new Promise((
|
|
77674
|
+
ffmpegAvailable = await new Promise((resolve4) => {
|
|
77587
77675
|
try {
|
|
77588
77676
|
const p = spawn("ffmpeg", ["-version"], { stdio: "ignore" });
|
|
77589
|
-
p.on("error", () =>
|
|
77590
|
-
p.on("exit", (code) =>
|
|
77677
|
+
p.on("error", () => resolve4(false));
|
|
77678
|
+
p.on("exit", (code) => resolve4(code === 0));
|
|
77591
77679
|
} catch {
|
|
77592
|
-
|
|
77680
|
+
resolve4(false);
|
|
77593
77681
|
}
|
|
77594
77682
|
});
|
|
77595
77683
|
return ffmpegAvailable;
|
|
@@ -77628,13 +77716,13 @@ function startMp4Encoder(width, height, fps) {
|
|
|
77628
77716
|
const exitError = (code) => new Error(
|
|
77629
77717
|
`ffmpeg exited ${code}: ${Buffer.concat(errChunks).toString().slice(-400)}`
|
|
77630
77718
|
);
|
|
77631
|
-
const done = new Promise((
|
|
77719
|
+
const done = new Promise((resolve4, reject) => {
|
|
77632
77720
|
p.on("error", (e) => {
|
|
77633
77721
|
failure = e;
|
|
77634
77722
|
reject(e);
|
|
77635
77723
|
});
|
|
77636
77724
|
p.on("close", (code) => {
|
|
77637
|
-
if (code === 0)
|
|
77725
|
+
if (code === 0) resolve4(Buffer.concat(out));
|
|
77638
77726
|
else {
|
|
77639
77727
|
failure ??= exitError(code);
|
|
77640
77728
|
reject(failure);
|
|
@@ -77645,15 +77733,15 @@ function startMp4Encoder(width, height, fps) {
|
|
|
77645
77733
|
});
|
|
77646
77734
|
return {
|
|
77647
77735
|
writeFrame(rgba) {
|
|
77648
|
-
return new Promise((
|
|
77736
|
+
return new Promise((resolve4, reject) => {
|
|
77649
77737
|
if (failure) return reject(failure);
|
|
77650
77738
|
if (p.exitCode !== null || p.stdin.destroyed) {
|
|
77651
77739
|
return reject(failure ?? exitError(p.exitCode));
|
|
77652
77740
|
}
|
|
77653
|
-
if (p.stdin.write(rgba)) return
|
|
77741
|
+
if (p.stdin.write(rgba)) return resolve4();
|
|
77654
77742
|
const onDrain = () => {
|
|
77655
77743
|
p.removeListener("close", onClose);
|
|
77656
|
-
|
|
77744
|
+
resolve4();
|
|
77657
77745
|
};
|
|
77658
77746
|
const onClose = () => {
|
|
77659
77747
|
p.stdin.removeListener("drain", onDrain);
|
|
@@ -79051,8 +79139,8 @@ var init_server3 = __esm({
|
|
|
79051
79139
|
init_order_feed();
|
|
79052
79140
|
init_animate();
|
|
79053
79141
|
PKG_VERSION = (() => {
|
|
79054
|
-
if ("0.9.4-main.
|
|
79055
|
-
return "0.9.4-main.
|
|
79142
|
+
if ("0.9.4-main.31") {
|
|
79143
|
+
return "0.9.4-main.31";
|
|
79056
79144
|
}
|
|
79057
79145
|
try {
|
|
79058
79146
|
const req = createRequire2(import.meta.url);
|
|
@@ -79061,8 +79149,8 @@ var init_server3 = __esm({
|
|
|
79061
79149
|
return "0.0.0";
|
|
79062
79150
|
}
|
|
79063
79151
|
})();
|
|
79064
|
-
BUILD_SHA = "
|
|
79065
|
-
BUILD_TIME = "2026-07-
|
|
79152
|
+
BUILD_SHA = "ab04c2e57eeea3736ac3421fabbc01e89959f953";
|
|
79153
|
+
BUILD_TIME = "2026-07-26T01:25:19.323Z";
|
|
79066
79154
|
SHORT_SHA = BUILD_SHA === "unknown" ? "unknown" : BUILD_SHA.slice(0, 7);
|
|
79067
79155
|
VERSION_WITH_BUILD = SHORT_SHA === "unknown" ? PKG_VERSION : `${PKG_VERSION}+${SHORT_SHA}`;
|
|
79068
79156
|
INSTANCE_ID = randomUUID6().slice(0, 8);
|
|
@@ -79380,12 +79468,12 @@ var init_index = __esm({
|
|
|
79380
79468
|
|
|
79381
79469
|
// src/npx-entry.ts
|
|
79382
79470
|
init_dist();
|
|
79383
|
-
import { readFileSync as
|
|
79384
|
-
import { dirname as dirname3, join as
|
|
79471
|
+
import { readFileSync as readFileSync7 } from "node:fs";
|
|
79472
|
+
import { dirname as dirname3, join as join4 } from "node:path";
|
|
79385
79473
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
79386
79474
|
primeKernelWasm(
|
|
79387
|
-
|
|
79388
|
-
|
|
79475
|
+
readFileSync7(
|
|
79476
|
+
join4(
|
|
79389
79477
|
dirname3(fileURLToPath3(import.meta.url)),
|
|
79390
79478
|
"vcad_kernel_wasm_bg.wasm"
|
|
79391
79479
|
)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vcad/mcp",
|
|
3
|
-
"version": "0.9.4-main.
|
|
3
|
+
"version": "0.9.4-main.31",
|
|
4
4
|
"description": "vcad MCP server — parametric CAD + PCB design tools for AI agents (self-contained: bundled server + kernel WASM)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
},
|
|
20
20
|
"homepage": "https://vcad.io",
|
|
21
21
|
"vcadBuild": {
|
|
22
|
-
"sha": "
|
|
23
|
-
"builtAt": "2026-07-
|
|
22
|
+
"sha": "ab04c2e57eeea3736ac3421fabbc01e89959f953",
|
|
23
|
+
"builtAt": "2026-07-26T01:25:19.323Z"
|
|
24
24
|
}
|
|
25
25
|
}
|
package/vcad_kernel_wasm_bg.wasm
CHANGED
|
Binary file
|