@vcad/mcp 0.9.4-main.36 → 0.9.4-main.39

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,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 3b9087f300b569c70aa24cebc52af06d15e59187 at 2026-07-26T13:20:01.737Z. Source: https://github.com/ecto/vcad
9
+ Built from 08cd95d4487b522463d0a1a3b58b84935a1f7d08 at 2026-07-26T13:56:33.334Z. Source: https://github.com/ecto/vcad
package/index.mjs CHANGED
@@ -177,6 +177,7 @@ __export(vcad_kernel_wasm_exports, {
177
177
  exprEvaluate: () => exprEvaluate,
178
178
  exprParse: () => exprParse,
179
179
  feaAnalyzeMesh: () => feaAnalyzeMesh,
180
+ feaCheckBeam: () => feaCheckBeam,
180
181
  generate3mf: () => generate3mf,
181
182
  generate3mfWithGcode: () => generate3mfWithGcode,
182
183
  generateGcode: () => generateGcode,
@@ -1925,6 +1926,15 @@ function feaAnalyzeMesh(spec_json, options_json, positions, indices) {
1925
1926
  }
1926
1927
  return takeFromExternrefTable0(ret[0]);
1927
1928
  }
1929
+ function feaCheckBeam(case_json) {
1930
+ const ptr0 = passStringToWasm0(case_json, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
1931
+ const len0 = WASM_VECTOR_LEN;
1932
+ const ret = wasm.feaCheckBeam(ptr0, len0);
1933
+ if (ret[2]) {
1934
+ throw takeFromExternrefTable0(ret[1]);
1935
+ }
1936
+ return takeFromExternrefTable0(ret[0]);
1937
+ }
1928
1938
  function generate3mf(name, vertices, indices, settings_json) {
1929
1939
  const ptr0 = passStringToWasm0(name, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
1930
1940
  const len0 = WASM_VECTOR_LEN;
@@ -11526,6 +11536,7 @@ var init_dist = __esm({
11526
11536
  evaluateDocument: wasmModule7.evaluateDocument,
11527
11537
  evalVcadSource: wasmModule7.evalVcadSource,
11528
11538
  evalVcadSourceWithModules: wasmModule7.evalVcadSourceWithModules,
11539
+ evalVcadSourceParametric: wasmModule7.evalVcadSourceParametric,
11529
11540
  documentParameterGradient: wasmModule7.documentParameterGradient,
11530
11541
  getPartsManifest: wasmModule7.getPartsManifest,
11531
11542
  buildPart: wasmModule7.buildPart,
@@ -11559,6 +11570,7 @@ var init_dist = __esm({
11559
11570
  circuitTransient: wasmModule7.circuitTransient,
11560
11571
  circuitTune: wasmModule7.circuitTune,
11561
11572
  feaAnalyzeMesh: wasmModule7.feaAnalyzeMesh,
11573
+ feaCheckBeam: wasmModule7.feaCheckBeam,
11562
11574
  emSimulate: wasmModule7.emSimulate,
11563
11575
  antennaAnalyze: wasmModule7.antennaAnalyze,
11564
11576
  photonicsSimulate: wasmModule7.photonicsSimulate,
@@ -11887,6 +11899,22 @@ var init_dist = __esm({
11887
11899
  }
11888
11900
  return fn(specJson, optionsJson, positions, indices);
11889
11901
  }
11902
+ /**
11903
+ * Closed-form check of a prismatic member: exact section properties,
11904
+ * beam bending with the Timoshenko shear term, Bredt thin-wall (or
11905
+ * Saint-Venant series) torsion, and Euler buckling — the route for
11906
+ * thin-walled sheet-metal and tube-frame members, which no affordable
11907
+ * lattice pitch can resolve. Same fail-closed contract and
11908
+ * `vcad.fea-claims/1` predicted claims as `feaAnalyzeMesh`; see
11909
+ * `vcad-kernel-fea::section`.
11910
+ */
11911
+ feaCheckBeam(caseJson) {
11912
+ const fn = this.kernel.feaCheckBeam;
11913
+ if (typeof fn !== "function") {
11914
+ throw new Error("feaCheckBeam is not exported by this kernel WASM build \u2014 rebuild packages/kernel-wasm");
11915
+ }
11916
+ return fn(caseJson);
11917
+ }
11890
11918
  /**
11891
11919
  * EM field simulation (2D/axisymmetric finite-volume magnetostatics or
11892
11920
  * electrostatics): inductance, force, torque, capacitance extraction and
@@ -12213,6 +12241,22 @@ var init_dist = __esm({
12213
12241
  const json = this.kernel.evalVcadSourceWithModules(source, JSON.stringify(modules));
12214
12242
  return JSON.parse(json);
12215
12243
  }
12244
+ /**
12245
+ * Evaluate loon source, returning the document alongside any parametric
12246
+ * warnings — intent the bridge could *not* preserve, such as a declared
12247
+ * parameter that ends up driving no geometry, or a field whose dependence
12248
+ * on a parameter is not affine and so keeps its literal.
12249
+ *
12250
+ * The document is identical to {@link evalVcadSourceWithModules}; only the
12251
+ * authoring feedback is extra. Returns null on kernels predating the
12252
+ * parametric loon forms, so callers can fall back.
12253
+ */
12254
+ evalVcadSourceParametric(source, modules = {}) {
12255
+ if (!this.kernel.evalVcadSourceParametric)
12256
+ return null;
12257
+ const json = this.kernel.evalVcadSourceParametric(source, Object.keys(modules).length ? JSON.stringify(modules) : void 0);
12258
+ return JSON.parse(json);
12259
+ }
12216
12260
  /** Evaluate a preview extrusion without adding to document */
12217
12261
  evaluateExtrudePreview(origin, xDir, yDir, segments, direction) {
12218
12262
  if (segments.length === 0)
@@ -40946,6 +40990,26 @@ var init_CHANGELOG = __esm({
40946
40990
  "rendering"
40947
40991
  ]
40948
40992
  },
40993
+ {
40994
+ id: "2026-07-26-parametric-loon-datums",
40995
+ version: "0.9.4",
40996
+ date: "2026-07-26",
40997
+ category: "feat",
40998
+ title: "Named parameters, datums and stacks in loon",
40999
+ summary: "Values named with [defparam] survive into the document and are settable with set_parameters; [datum-plane]/[stack] make shared planes and packing clearances named entities instead of repeated literals.",
41000
+ features: [
41001
+ "loon",
41002
+ "parameters",
41003
+ "datums",
41004
+ "assemblies"
41005
+ ],
41006
+ mcpTools: [
41007
+ "create_cad_loon",
41008
+ "list_parameters",
41009
+ "set_parameters",
41010
+ "parameter_gradient"
41011
+ ]
41012
+ },
40949
41013
  {
40950
41014
  id: "2026-07-26-loon-mirror-symmetry",
40951
41015
  version: "0.9.4",
@@ -40984,6 +41048,25 @@ var init_CHANGELOG = __esm({
40984
41048
  "export_cad"
40985
41049
  ]
40986
41050
  },
41051
+ {
41052
+ id: "2026-07-26-fastener-forms",
41053
+ version: "0.9.4",
41054
+ date: "2026-07-26",
41055
+ category: "feat",
41056
+ title: "Fasteners are a first-class form",
41057
+ summary: "bolt, bolt-circle, clearance-hole and tapped-hole: catalog-backed hardware oriented by its own axis, with real countersunk heads, length checks, and BOM lines counted off the geometry.",
41058
+ features: [
41059
+ "loon",
41060
+ "fasteners",
41061
+ "bom",
41062
+ "assembly"
41063
+ ],
41064
+ mcpTools: [
41065
+ "create_cad_loon",
41066
+ "bom_create",
41067
+ "search_mechanical_parts"
41068
+ ]
41069
+ },
40987
41070
  {
40988
41071
  id: "2026-07-26-document-source-provenance",
40989
41072
  version: "0.9.4",
@@ -41003,6 +41086,23 @@ var init_CHANGELOG = __esm({
41003
41086
  "load_document"
41004
41087
  ]
41005
41088
  },
41089
+ {
41090
+ id: "2026-07-26-beam-check-thin-walled-fea",
41091
+ version: "0.9.4",
41092
+ date: "2026-07-26",
41093
+ category: "feat",
41094
+ title: "beam_check: audited structural answer for thin walls",
41095
+ summary: "Sheet-metal and tube-frame members had no audited FEA answer at any resolution; beam_check prices them in closed form (Bredt torsion, beam bending, Euler buckling), and analyze_structure now measures the wall and refuses with the cell arithmetic instead of a bare Unverifiable.",
41096
+ features: [
41097
+ "fea",
41098
+ "sheet-metal",
41099
+ "receipts"
41100
+ ],
41101
+ mcpTools: [
41102
+ "beam_check",
41103
+ "analyze_structure"
41104
+ ]
41105
+ },
41006
41106
  {
41007
41107
  id: "2026-07-26-backwards-extrude-inverted-winding",
41008
41108
  version: "0.9.4",
@@ -48676,9 +48776,45 @@ function createCadLoon(input, engine) {
48676
48776
  return { content: [{ type: "text", text: text2 }] };
48677
48777
  }
48678
48778
  const text = format === "json" ? JSON.stringify(doc, null, 2) : toVCode2(doc);
48679
- return {
48680
- content: [{ type: "text", text }]
48681
- };
48779
+ const content = [{ type: "text", text }];
48780
+ const note = parametricNote(source, modules, engine, doc);
48781
+ if (note) content.push({ type: "text", text: note });
48782
+ return { content };
48783
+ }
48784
+ function parametricNote(source, modules, engine, doc) {
48785
+ const names = Object.keys(doc.parameters ?? {}).sort();
48786
+ if (!names.length) return null;
48787
+ const lines = [];
48788
+ const value = (n) => doc.parameters[n].value;
48789
+ const bound = new Set(
48790
+ Object.values(doc.bindings ?? {}).flatMap(
48791
+ (expr) => typeof expr === "string" ? expr.match(/[A-Za-z_]\w*/g) ?? [] : []
48792
+ )
48793
+ );
48794
+ const base = names.filter((n) => typeof value(n) === "number");
48795
+ const derived = names.filter((n) => typeof value(n) === "string");
48796
+ if (base.length) {
48797
+ lines.push(
48798
+ `Parameters (${base.length}, settable): ${base.map((n) => bound.has(n) ? n : `${n} (drives nothing)`).join(", ")}`
48799
+ );
48800
+ }
48801
+ if (derived.length) {
48802
+ lines.push(
48803
+ `Derived (${derived.length}, follow from the above): ${derived.map((n) => `${n} = "${value(n)}"`).join(", ")}`
48804
+ );
48805
+ }
48806
+ const datums = Object.keys(doc.datums ?? {}).sort();
48807
+ if (datums.length) lines.push(`Datums (${datums.length}): ${datums.join(", ")}`);
48808
+ if (base.length) {
48809
+ lines.push("Change any settable parameter with set_parameters \u2014 no re-authoring needed.");
48810
+ }
48811
+ let warnings = [];
48812
+ try {
48813
+ warnings = engine.evalVcadSourceParametric(source, modules)?.warnings ?? [];
48814
+ } catch {
48815
+ }
48816
+ for (const w of warnings) lines.push(`- ${w}`);
48817
+ return lines.join("\n");
48682
48818
  }
48683
48819
  var createCadLoonSchema, toolDefs2;
48684
48820
  var init_loon = __esm({
@@ -48740,7 +48876,7 @@ var init_loon = __esm({
48740
48876
  {
48741
48877
  name: "create_cad_loon",
48742
48878
  pack: null,
48743
- 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], [torus major-r minor-r], [wedge x y z], [prism sides radius height]\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], [mirror ox oy oz nx ny nz s] (plane through the origin point with that normal) \u2014 plus the axis sugar [mirror-x s] / [mirror-y s] / [mirror-z s], which mirror through the origin, negating that one coordinate. NEVER hand-mirror by negating coordinates; use these.\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]\nSymmetric patterns (subject-last): [mirror-pattern nx ny nz s] and its sugar [mirror-pattern-x s] / -y / -z union a solid with its mirror image (a left/right pair in one expression, so the halves can\'t drift apart); [quad-pattern s] is the 4-fold X-and-Y version \u2014 a quadruped\'s legs, a 4-post frame, a vehicle chassis\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]], [loft-closed #[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]\nAssembly symmetry: author ONE side as an assembly, then [mirror-group-x "-r" side] (or -y / -z) returns it plus a mirrored, suffixed copy \u2014 parts reflected, placements and joint anchors mirrored, and joint axes flipped by the correct rule (a hinge across its own mirror normal keeps its axis; the other two flip), so the same joint state drives both sides symmetrically. Splice it back with [assembly-join chassis mirrored]. NEVER hand-mirror an assembly.\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"], with [material name r g b metallic roughness] to define one\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',
48879
+ 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], [torus major-r minor-r], [wedge x y z], [prism sides radius height]\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], [mirror ox oy oz nx ny nz s] (plane through the origin point with that normal) \u2014 plus the axis sugar [mirror-x s] / [mirror-y s] / [mirror-z s], which mirror through the origin, negating that one coordinate. NEVER hand-mirror by negating coordinates; use these.\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]\nSymmetric patterns (subject-last): [mirror-pattern nx ny nz s] and its sugar [mirror-pattern-x s] / -y / -z union a solid with its mirror image (a left/right pair in one expression, so the halves can\'t drift apart); [quad-pattern s] is the 4-fold X-and-Y version \u2014 a quadruped\'s legs, a 4-post frame, a vehicle chassis\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]], [loft-closed #[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]\nAssembly symmetry: author ONE side as an assembly, then [mirror-group-x "-r" side] (or -y / -z) returns it plus a mirrored, suffixed copy \u2014 parts reflected, placements and joint anchors mirrored, and joint axes flipped by the correct rule (a hinge across its own mirror normal keeps its axis; the other two flip), so the same joint state drives both sides symmetrically. Splice it back with [assembly-join chassis mirrored]. NEVER hand-mirror an assembly.\nPipe: [pipe [cube 50 30 5] [difference [cylinder 3 10]] [fillet 1.0]]\nLet bindings: [let body [cube 50 30 5]] \u2014 note these are inlined and do NOT survive into the document; use [defparam ...] for a value you want to change later\nParameters (survive into the document and are settable afterwards with set_parameters, and differentiable with parameter_gradient): [defparam pitch_axis_x 310.0], [defparam wall "bore * 0.2"] for derived values, plus optional :unit/:min/:max/:description. Names must be identifier-safe (underscores, not dashes)\nDatums \u2014 named reference geometry, so two parts cannot each hold their own copy of a shared plane: [datum-plane "femur_inner" y 131.0], [datum-axis "pitch" x 0 0 310], [datum-point "hip" 0 0 310], read back with [datum "femur_inner"], [datum+ "femur_inner" 3.0] (3 mm outboard), [datum-x/-y/-z "pitch"]\nStacks \u2014 declarative packing, where each running clearance is a named value instead of an arbitrary number: [stack y "leg" 131.0 [lane "femur_inner" 5.0] [gap "idler_run" 1.0] [lane "idler_boss" 3.0]] declares datum planes leg_femur_inner_lo/_hi, leg_idler_boss_lo/_hi and leg_end; widening leg_idler_run slides everything outboard of it\nScene: [root solid "material-name"], with [material name r g b metallic roughness] to define one\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',
48744
48880
  inputSchema: createCadLoonSchema,
48745
48881
  handler: async (args, ctx) => {
48746
48882
  const useLoons = Array.isArray(args.use_loons) ? args.use_loons : void 0;
@@ -53324,6 +53460,7 @@ var init_tool_metadata = __esm({
53324
53460
  solve_thermal: { title: "Solve Thermal", annotations: RO },
53325
53461
  simulate_flow: { title: "Simulate Flow", annotations: RO },
53326
53462
  analyze_structure: { title: "Analyze Structure", annotations: RO },
53463
+ beam_check: { title: "Check Beam / Section", annotations: RO },
53327
53464
  simulate_neutron_shield: {
53328
53465
  title: "Simulate Neutron Shield",
53329
53466
  annotations: RO
@@ -57148,6 +57285,14 @@ function totalsSummary(totals) {
57148
57285
  currency: totals.currency
57149
57286
  };
57150
57287
  }
57288
+ function documentHardware(documentId) {
57289
+ try {
57290
+ const doc = documents.get(documentId);
57291
+ return Array.isArray(doc?.hardware) ? doc.hardware : [];
57292
+ } catch {
57293
+ return [];
57294
+ }
57295
+ }
57151
57296
  async function bomCreate(input, store, user) {
57152
57297
  const args = input ?? {};
57153
57298
  const owner = ownerId(user);
@@ -57162,7 +57307,23 @@ async function bomCreate(input, store, user) {
57162
57307
  created_at: now,
57163
57308
  updated_at: now
57164
57309
  };
57165
- const lineInputs = Array.isArray(args.lines) ? args.lines : [];
57310
+ const fromGeometry = args.from_geometry !== false;
57311
+ const geometryLines = [];
57312
+ if (fromGeometry && bom.document_id) {
57313
+ for (const hw of documentHardware(bom.document_id)) {
57314
+ geometryLines.push({
57315
+ kind: "cots",
57316
+ name: hw.spec,
57317
+ catalog_id: hw.catalog_id ?? void 0,
57318
+ qty: hw.qty,
57319
+ notes: "counted from the model geometry"
57320
+ });
57321
+ }
57322
+ }
57323
+ const lineInputs = [
57324
+ ...geometryLines,
57325
+ ...Array.isArray(args.lines) ? args.lines : []
57326
+ ];
57166
57327
  for (let i = 0; i < lineInputs.length; i++) {
57167
57328
  const built = await buildLine(lineInputs[i] ?? {}, store, owner);
57168
57329
  if ("error" in built) return err4(`lines[${i}]: ${built.error}`);
@@ -57338,6 +57499,7 @@ var init_bom = __esm({
57338
57499
  init_pricing();
57339
57500
  init_types4();
57340
57501
  init_mech_parts();
57502
+ init_session_core();
57341
57503
  init_tool_def();
57342
57504
  PRICING_NOTE = "All prices are ESTIMATES (Phase-0 quote estimates, catalog price bands, or caller-supplied numbers) \u2014 not binding quotes.";
57343
57505
  memBoms = /* @__PURE__ */ new Map();
@@ -57403,6 +57565,10 @@ var init_bom = __esm({
57403
57565
  type: "array",
57404
57566
  items: { type: "object", properties: lineProperties, required: ["kind"] },
57405
57567
  description: "Optional full line list to build the BOM in ONE call (recommended on serverless \u2014 BOMs are in-memory per instance, so one-shot creation is the robust path)."
57568
+ },
57569
+ from_geometry: {
57570
+ type: "boolean",
57571
+ description: "Seed COTS lines from the hardware the document's geometry declares \u2014 every loon `bolt`/`bolt-circle` (plus its washers and nuts), with quantities already multiplied through patterns. Default true when document_id is given."
57406
57572
  }
57407
57573
  },
57408
57574
  required: []
@@ -57437,7 +57603,7 @@ var init_bom = __esm({
57437
57603
  {
57438
57604
  name: "bom_create",
57439
57605
  pack: "bom",
57440
- description: "Create a project bill of materials \u2014 the deliverable that collects every manufactured part (PCBs, sheet metal, 3D prints; link quote_manufacturing quotes by quote_id) and COTS part (bearings, shafts, screws, magnets; link search_mechanical_parts entries by catalog_id) in one place. Optionally attach a document_id and assembly notes. Pass the full `lines` array to build the whole BOM in ONE call \u2014 recommended, since BOMs are in-memory per server instance. All prices are estimates and flagged as such.",
57606
+ description: "Create a project bill of materials \u2014 the deliverable that collects every manufactured part (PCBs, sheet metal, 3D prints; link quote_manufacturing quotes by quote_id) and COTS part (bearings, shafts, screws, magnets; link search_mechanical_parts entries by catalog_id) in one place. Optionally attach a document_id and assembly notes. Pass the full `lines` array to build the whole BOM in ONE call \u2014 recommended, since BOMs are in-memory per server instance. When document_id names a resident session, fastener lines are seeded from the geometry itself (every loon `bolt`/`bolt-circle`, quantities multiplied through patterns) \u2014 pass from_geometry:false to opt out. All prices are estimates and flagged as such.",
57441
57607
  inputSchema: bomCreateSchema,
57442
57608
  handler: (a, c) => bomCreate(a, c.fabricateStore, c.user),
57443
57609
  behavior: behavior({})
@@ -57946,6 +58112,18 @@ function listParameters(input) {
57946
58112
  );
57947
58113
  return jsonResult({ count: parameters.length, parameters });
57948
58114
  }
58115
+ function referencedNames(doc) {
58116
+ const out = /* @__PURE__ */ new Set();
58117
+ const harvest = (expr) => {
58118
+ if (typeof expr !== "string") return;
58119
+ for (const v of expr.match(/[A-Za-z_]\w*/g) ?? []) out.add(v);
58120
+ };
58121
+ for (const expr of Object.values(doc.bindings ?? {})) harvest(expr);
58122
+ for (const c of doc.constraints ?? []) {
58123
+ for (const v of Object.values(c)) harvest(v);
58124
+ }
58125
+ return out;
58126
+ }
57949
58127
  async function setParameters(input, engine) {
57950
58128
  const args = input ?? {};
57951
58129
  const documentId = String(args.document_id ?? "");
@@ -57975,6 +58153,18 @@ async function setParameters(input, engine) {
57975
58153
  `Unknown parameter(s): ${unknown2.join(", ")}. Use list_parameters to see declared names.`
57976
58154
  );
57977
58155
  }
58156
+ const referenced = referencedNames(doc);
58157
+ const noEffect = entries.map(([name]) => name).filter((name) => !referenced.has(name)).map((name) => {
58158
+ const value = doc.parameters[name].value;
58159
+ const inputs = typeof value === "string" ? [...new Set(value.match(/[A-Za-z_]\w*/g) ?? [])].filter(
58160
+ (v) => v in doc.parameters && referenced.has(v)
58161
+ ) : [];
58162
+ return {
58163
+ name,
58164
+ reason: typeof value === "string" ? `derived (${JSON.stringify(value)}) \u2014 no field is bound to it` : "no field is bound to it",
58165
+ ...inputs.length ? { set_instead: inputs } : {}
58166
+ };
58167
+ });
57978
58168
  const changed = [];
57979
58169
  for (const [name, value] of entries) {
57980
58170
  const param = doc.parameters[name];
@@ -57996,6 +58186,7 @@ async function setParameters(input, engine) {
57996
58186
  document_id: documentId,
57997
58187
  updated: changed.length,
57998
58188
  changed,
58189
+ ...noEffect.length ? { no_effect: noEffect } : {},
57999
58190
  ...constraintSolve ? { constraint_solve: constraintSolve } : {}
58000
58191
  });
58001
58192
  result.structuredContent = { changed };
@@ -72230,7 +72421,7 @@ var init_structure = __esm({
72230
72421
  {
72231
72422
  name: "analyze_structure",
72232
72423
  pack: null,
72233
- description: 'Static structural FEA on a part\'s real geometry with fail-closed mesh-convergence gating: linear-tet fill of the tessellated interior at 2+ refinement levels, linear-elastic solve (PCG), returning max von Mises stress, max displacement, discretization-error estimates, and (with yield_strength_mpa) a safety factor \u2014 plus `vcad.fea-claims/1` and unified-receipt claims. If QoIs disagree across levels the verdict is Unverifiable and NO stress/displacement claim is emitted (raise resolution, or fillet the singular corner it points at). Claims carry basis "predicted" and roll up Provisional until hardware is load-tested. Scope: small-displacement linear elasticity, one isotropic material; no plasticity, buckling, contact, or dynamic loads; boundary is staircase-approximated at the lattice pitch. Use predict_physics for the fast coarse steering loop; use this for the audited answer.',
72424
+ description: 'Static structural FEA on a part\'s real geometry with fail-closed mesh-convergence gating: linear-tet fill of the tessellated interior at 2+ refinement levels, linear-elastic solve (PCG), returning max von Mises stress, max displacement, discretization-error estimates, and (with yield_strength_mpa) a safety factor \u2014 plus `vcad.fea-claims/1` and unified-receipt claims. If QoIs disagree across levels the verdict is Unverifiable and NO stress/displacement claim is emitted (raise resolution, or fillet the singular corner it points at). Claims carry basis "predicted" and roll up Provisional until hardware is load-tested. Scope: small-displacement linear elasticity, one isotropic material; no plasticity, buckling, contact, or dynamic loads; boundary is staircase-approximated at the lattice pitch. THIN-WALLED PARTS (sheet metal, tube frame, plate) are outside this tool: it measures the thinnest load-bearing section and fails closed with the cell arithmetic and a pointer to beam_check, which is the more accurate answer for a prismatic member anyway. Use predict_physics for the fast coarse steering loop; use this for the audited answer on chunky geometry.',
72234
72425
  inputSchema: {
72235
72426
  type: "object",
72236
72427
  required: ["document_id", "part", "loads", "supports"],
@@ -72295,7 +72486,7 @@ var init_structure = __esm({
72295
72486
  },
72296
72487
  resolution: {
72297
72488
  type: "number",
72298
- description: "Coarse-level lattice cells along the longest bbox axis (default 24). The finest level is resolution * 2^(levels-1), capped at 160 for this tier. Keep >= ~6 cells through the thinnest load-bearing section."
72489
+ description: "Coarse-level lattice cells along the longest bbox axis (default 24). The finest level is resolution * 2^(levels-1), capped at 160 for this tier. Keep >= ~6 cells through the thinnest load-bearing section \u2014 the tool measures the part and refuses below ~4, with the cell arithmetic attached. Raising this is NOT the lever for a thin wall (a 2 mm wall on a 300 mm member needs ~950 cells): use beam_check."
72299
72490
  },
72300
72491
  levels: {
72301
72492
  type: "number",
@@ -72346,6 +72537,89 @@ var init_structure = __esm({
72346
72537
  });
72347
72538
  },
72348
72539
  behavior: behavior({})
72540
+ },
72541
+ {
72542
+ name: "beam_check",
72543
+ pack: null,
72544
+ description: 'Closed-form structural check of a PRISMATIC member \u2014 the audited answer for thin-walled geometry, where the lattice in analyze_structure cannot put enough cells through the wall at any affordable resolution. Give it a profile (rect, rect_tube, round, round_tube, i_beam), a span, an end condition, and a load case; get section properties (A, I_y, I_z, J, section moduli, torsional stiffness), stresses (bending, axial, torsional and transverse shear, von Mises), deflection with the Timoshenko shear term, twist, the Euler buckling load under compression, and a safety factor \u2014 plus `vcad.fea-claims/1` and unified-receipt claims under `structure.beam.*`. Exact integrals where they exist (round, round tube, rectangle bending), the convergent Saint-Venant series for solid-rectangle torsion, Bredt closed thin-wall theory for tube torsion \u2014 every approximation is stated in the returned notes. For a constant cross-section this is not a fallback: it beats a staircased lattice on accuracy and costs microseconds. Fail-closed: too stubby for beam theory (L/depth < 5), too thick-walled for Bredt, deflecting past a tenth of the span, torque on an open section, or buckling before it yields \u2014 any of those makes the verdict Unverifiable and NO QoI is claimed, with the reason naming the route forward. Claims carry basis "predicted" and roll up Provisional until hardware is load-tested. Scope: one uniform section over the whole span, idealized ends, no stress concentrations (holes, welds, corner radii), no local wall buckling, no fatigue. Needs no document \u2014 it is geometry-by-description, so it works before the part exists.',
72545
+ inputSchema: {
72546
+ type: "object",
72547
+ required: ["profile", "length_mm", "end_condition"],
72548
+ properties: {
72549
+ profile: {
72550
+ type: "object",
72551
+ required: ["type"],
72552
+ description: 'Cross-section. The member runs along X and the section lives in (Y, Z): width_mm is the Y extent, height_mm the Z extent. One of: {"type":"rect",width_mm,height_mm}, {"type":"rect_tube",width_mm,height_mm,wall_mm}, {"type":"round",diameter_mm}, {"type":"round_tube",diameter_mm,wall_mm}, {"type":"i_beam",width_mm,height_mm,flange_mm,web_mm}. Tube dimensions are OUTSIDE dimensions.',
72553
+ properties: {
72554
+ type: {
72555
+ type: "string",
72556
+ enum: ["rect", "rect_tube", "round", "round_tube", "i_beam"]
72557
+ },
72558
+ width_mm: { type: "number", description: "Y extent, mm." },
72559
+ height_mm: { type: "number", description: "Z extent, mm." },
72560
+ wall_mm: { type: "number", description: "Wall thickness, mm." },
72561
+ diameter_mm: { type: "number", description: "Outside diameter, mm." },
72562
+ flange_mm: { type: "number", description: "Flange thickness, mm." },
72563
+ web_mm: { type: "number", description: "Web thickness, mm." }
72564
+ }
72565
+ },
72566
+ length_mm: {
72567
+ type: "number",
72568
+ description: "Free span between supports, mm."
72569
+ },
72570
+ end_condition: {
72571
+ type: "string",
72572
+ enum: [
72573
+ "cantilever_tip",
72574
+ "cantilever_uniform",
72575
+ "simple_center",
72576
+ "simple_uniform",
72577
+ "fixed_fixed_center",
72578
+ "fixed_fixed_uniform"
72579
+ ],
72580
+ description: "Support and load arrangement. `*_uniform` spreads the transverse force over the span; `*_center`/`*_tip` concentrates it. Also sets the Euler effective-length factor (cantilever K=2, simple K=1, fixed-fixed K=0.5)."
72581
+ },
72582
+ transverse_force_n: {
72583
+ type: "number",
72584
+ description: "Total transverse force, N (magnitude). Default 0."
72585
+ },
72586
+ bend_axis: {
72587
+ type: "string",
72588
+ enum: ["y", "z"],
72589
+ description: "Which principal axis to bend about: `y` uses I_y and deflects along Z (default), `z` uses I_z and deflects along Y. Matters for any non-square section."
72590
+ },
72591
+ torque_nmm: {
72592
+ type: "number",
72593
+ description: "Torque about the member axis, N\xB7mm (1 N\xB7m = 1000 N\xB7mm). Default 0. This is the case closed-form theory answers best and the lattice answers worst."
72594
+ },
72595
+ axial_force_n: {
72596
+ type: "number",
72597
+ description: "Axial force, N \u2014 positive tension, negative compression. Compression is also checked against Euler buckling, and a member that buckles is reported Unverifiable rather than 'safe'. Default 0."
72598
+ },
72599
+ youngs_modulus_mpa: {
72600
+ type: "number",
72601
+ description: "Young's modulus, MPa. Default 69000 (6061 aluminum); steel ~200000, PLA ~2300."
72602
+ },
72603
+ poisson: {
72604
+ type: "number",
72605
+ description: "Poisson's ratio in [0, 0.5). Default 0.33; sets G = E/(2(1+nu))."
72606
+ },
72607
+ yield_strength_mpa: {
72608
+ type: "number",
72609
+ description: "Yield strength, MPa. When given, safety_factor = yield / von Mises is computed and claimed (applicable checks only)."
72610
+ }
72611
+ }
72612
+ },
72613
+ handler: (args, ctx) => {
72614
+ const a = args;
72615
+ const started = performance.now();
72616
+ const out = ctx.engine.feaCheckBeam(JSON.stringify(a));
72617
+ return textResult7({
72618
+ solve_ms: Math.round(performance.now() - started),
72619
+ ...out
72620
+ });
72621
+ },
72622
+ behavior: behavior({})
72349
72623
  }
72350
72624
  ];
72351
72625
  }
@@ -80049,8 +80323,8 @@ var init_server3 = __esm({
80049
80323
  init_order_feed();
80050
80324
  init_animate();
80051
80325
  PKG_VERSION = (() => {
80052
- if ("0.9.4-main.36") {
80053
- return "0.9.4-main.36";
80326
+ if ("0.9.4-main.39") {
80327
+ return "0.9.4-main.39";
80054
80328
  }
80055
80329
  try {
80056
80330
  const req = createRequire2(import.meta.url);
@@ -80059,8 +80333,8 @@ var init_server3 = __esm({
80059
80333
  return "0.0.0";
80060
80334
  }
80061
80335
  })();
80062
- BUILD_SHA = "3b9087f300b569c70aa24cebc52af06d15e59187";
80063
- BUILD_TIME = "2026-07-26T13:20:01.737Z";
80336
+ BUILD_SHA = "08cd95d4487b522463d0a1a3b58b84935a1f7d08";
80337
+ BUILD_TIME = "2026-07-26T13:56:33.334Z";
80064
80338
  SHORT_SHA = BUILD_SHA === "unknown" ? "unknown" : BUILD_SHA.slice(0, 7);
80065
80339
  VERSION_WITH_BUILD = SHORT_SHA === "unknown" ? PKG_VERSION : `${PKG_VERSION}+${SHORT_SHA}`;
80066
80340
  INSTANCE_ID = randomUUID6().slice(0, 8);
@@ -80208,6 +80482,7 @@ var init_server3 = __esm({
80208
80482
  "solve_thermal",
80209
80483
  "simulate_flow",
80210
80484
  "analyze_structure",
80485
+ "beam_check",
80211
80486
  "simulate_neutron_shield",
80212
80487
  "simulate_lattice_gauge",
80213
80488
  "analyze_tolerance_stackup",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vcad/mcp",
3
- "version": "0.9.4-main.36",
3
+ "version": "0.9.4-main.39",
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": "3b9087f300b569c70aa24cebc52af06d15e59187",
23
- "builtAt": "2026-07-26T13:20:01.737Z"
22
+ "sha": "08cd95d4487b522463d0a1a3b58b84935a1f7d08",
23
+ "builtAt": "2026-07-26T13:56:33.334Z"
24
24
  }
25
25
  }
Binary file