@vcad/mcp 0.9.4-main.72 → 0.9.4-main.74

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 150875476b916d4e772bdeb33726c543ccf2756d at 2026-08-17T03:05:59.733Z. Source: https://github.com/ecto/vcad
9
+ Built from cad60a705d41503617de2b556150aaeae3e893c8 at 2026-08-19T15:53:22.260Z. Source: https://github.com/ecto/vcad
package/index.mjs CHANGED
@@ -225,6 +225,10 @@ __export(vcad_kernel_wasm_exports, {
225
225
  op_shell: () => op_shell,
226
226
  op_sweep_helix: () => op_sweep_helix,
227
227
  op_sweep_line: () => op_sweep_line,
228
+ parseAltiumAsciiPcb: () => parseAltiumAsciiPcb,
229
+ parseAltiumPcbDoc: () => parseAltiumPcbDoc,
230
+ parseAltiumPcbLib: () => parseAltiumPcbLib,
231
+ parseEagleBrd: () => parseEagleBrd,
228
232
  parseKicadPcb: () => parseKicadPcb,
229
233
  parseVCode: () => parseVCode,
230
234
  parseVcadFile: () => parseVcadFile,
@@ -2443,6 +2447,42 @@ function op_sweep_line(profile_json, start, end, twist_angle, scale_start, scale
2443
2447
  }
2444
2448
  return Solid.__wrap(ret[0]);
2445
2449
  }
2450
+ function parseAltiumAsciiPcb(content) {
2451
+ const ptr0 = passStringToWasm0(content, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
2452
+ const len0 = WASM_VECTOR_LEN;
2453
+ const ret = wasm.parseAltiumAsciiPcb(ptr0, len0);
2454
+ if (ret[2]) {
2455
+ throw takeFromExternrefTable0(ret[1]);
2456
+ }
2457
+ return takeFromExternrefTable0(ret[0]);
2458
+ }
2459
+ function parseAltiumPcbDoc(bytes) {
2460
+ const ptr0 = passArray8ToWasm0(bytes, wasm.__wbindgen_malloc);
2461
+ const len0 = WASM_VECTOR_LEN;
2462
+ const ret = wasm.parseAltiumPcbDoc(ptr0, len0);
2463
+ if (ret[2]) {
2464
+ throw takeFromExternrefTable0(ret[1]);
2465
+ }
2466
+ return takeFromExternrefTable0(ret[0]);
2467
+ }
2468
+ function parseAltiumPcbLib(bytes) {
2469
+ const ptr0 = passArray8ToWasm0(bytes, wasm.__wbindgen_malloc);
2470
+ const len0 = WASM_VECTOR_LEN;
2471
+ const ret = wasm.parseAltiumPcbLib(ptr0, len0);
2472
+ if (ret[2]) {
2473
+ throw takeFromExternrefTable0(ret[1]);
2474
+ }
2475
+ return takeFromExternrefTable0(ret[0]);
2476
+ }
2477
+ function parseEagleBrd(content) {
2478
+ const ptr0 = passStringToWasm0(content, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
2479
+ const len0 = WASM_VECTOR_LEN;
2480
+ const ret = wasm.parseEagleBrd(ptr0, len0);
2481
+ if (ret[2]) {
2482
+ throw takeFromExternrefTable0(ret[1]);
2483
+ }
2484
+ return takeFromExternrefTable0(ret[0]);
2485
+ }
2446
2486
  function parseKicadPcb(content) {
2447
2487
  const ptr0 = passStringToWasm0(content, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
2448
2488
  const len0 = WASM_VECTOR_LEN;
@@ -10874,6 +10914,46 @@ async function parseKicadPcb2(content) {
10874
10914
  return null;
10875
10915
  }
10876
10916
  }
10917
+ function importBindings(wasm3) {
10918
+ const mod = wasm3;
10919
+ const bind = (name) => {
10920
+ const fn = mod[name];
10921
+ if (typeof fn !== "function") {
10922
+ throw new Error(`Altium import is unavailable: this kernel WASM build predates ${name}. Rebuild the kernel WASM (npm run build -w vcad-kernel-wasm) or update @vcad/mcp.`);
10923
+ }
10924
+ return fn;
10925
+ };
10926
+ return {
10927
+ parseAltiumAsciiPcb: (content) => bind("parseAltiumAsciiPcb")(content),
10928
+ parseAltiumPcbDoc: (bytes) => bind("parseAltiumPcbDoc")(bytes),
10929
+ parseAltiumPcbLib: (bytes) => bind("parseAltiumPcbLib")(bytes),
10930
+ parseEagleBrd: (content) => bind("parseEagleBrd")(content)
10931
+ };
10932
+ }
10933
+ async function parseEagleBrd2(content) {
10934
+ const wasm3 = await loadEcadWasm();
10935
+ if (!wasm3)
10936
+ return null;
10937
+ return importBindings(wasm3).parseEagleBrd(content);
10938
+ }
10939
+ async function parseAltiumAsciiPcb2(content) {
10940
+ const wasm3 = await loadEcadWasm();
10941
+ if (!wasm3)
10942
+ return null;
10943
+ return importBindings(wasm3).parseAltiumAsciiPcb(content);
10944
+ }
10945
+ async function parseAltiumPcbDoc2(bytes) {
10946
+ const wasm3 = await loadEcadWasm();
10947
+ if (!wasm3)
10948
+ return null;
10949
+ return importBindings(wasm3).parseAltiumPcbDoc(bytes);
10950
+ }
10951
+ async function parseAltiumPcbLib2(bytes) {
10952
+ const wasm3 = await loadEcadWasm();
10953
+ if (!wasm3)
10954
+ return null;
10955
+ return importBindings(wasm3).parseAltiumPcbLib(bytes);
10956
+ }
10877
10957
  async function exportKicadPcb2(pcb) {
10878
10958
  const wasm3 = await loadEcadWasm();
10879
10959
  if (!wasm3)
@@ -11719,7 +11799,11 @@ __export(dist_exports, {
11719
11799
  mountingHolesFromPcb: () => mountingHolesFromPcb,
11720
11800
  netContinuity: () => netContinuity,
11721
11801
  outlineAabb: () => outlineAabb,
11802
+ parseAltiumAsciiPcb: () => parseAltiumAsciiPcb2,
11803
+ parseAltiumPcbDoc: () => parseAltiumPcbDoc2,
11804
+ parseAltiumPcbLib: () => parseAltiumPcbLib2,
11722
11805
  parseBindingKey: () => parseBindingKey,
11806
+ parseEagleBrd: () => parseEagleBrd2,
11723
11807
  parseExpression: () => parse,
11724
11808
  parseKicadPcb: () => parseKicadPcb2,
11725
11809
  parseXyz: () => parseXyz,
@@ -41547,6 +41631,45 @@ var init_CHANGELOG = __esm({
41547
41631
  CHANGELOG_default = {
41548
41632
  $schema: "./changelog.schema.json",
41549
41633
  entries: [
41634
+ {
41635
+ id: "2026-08-19-loon-modules-and-root-cache",
41636
+ version: "0.9.4",
41637
+ date: "2026-08-19",
41638
+ category: "feat",
41639
+ title: "Loon lib path and a content-addressed evaluation cache",
41640
+ summary: "[use] modules resolve next to the file then on $VCAD_LOON_PATH, identically in vcad, vcad-render and MCP; evaluated root meshes are cached on disk keyed by expression + kernel build.",
41641
+ features: [
41642
+ "loon",
41643
+ "modules",
41644
+ "cache",
41645
+ "cli",
41646
+ "render"
41647
+ ],
41648
+ mcpTools: [
41649
+ "load_document",
41650
+ "create_cad_loon"
41651
+ ]
41652
+ },
41653
+ {
41654
+ id: "2026-08-17-altium-pcb-import",
41655
+ version: "0.9.4",
41656
+ date: "2026-08-17",
41657
+ category: "feat",
41658
+ title: "Altium and Eagle PCB import",
41659
+ summary: "Import Altium .PcbDoc boards (native binary or ASCII) and .PcbLib libraries, plus Eagle .brd boards, straight into a live session.",
41660
+ features: [
41661
+ "pcb",
41662
+ "import",
41663
+ "ecad",
41664
+ "altium",
41665
+ "eagle"
41666
+ ],
41667
+ mcpTools: [
41668
+ "import_altium",
41669
+ "import_altium_library",
41670
+ "import_eagle"
41671
+ ]
41672
+ },
41550
41673
  {
41551
41674
  id: "2026-08-16-render-z-up",
41552
41675
  version: "0.9.4",
@@ -41559,6 +41682,48 @@ var init_CHANGELOG = __esm({
41559
41682
  "rendering"
41560
41683
  ]
41561
41684
  },
41685
+ {
41686
+ id: "2026-08-16-render-hidden-lines",
41687
+ version: "0.9.4",
41688
+ date: "2026-08-16",
41689
+ category: "fix",
41690
+ title: "SVG renders draw complete, actually-dashed hidden lines",
41691
+ summary: "Hidden edges on curved features were mostly deleted and the survivors drew solid, leaving a stray line across the part; they now run the full occluded length as proper dashes.",
41692
+ features: [
41693
+ "rendering",
41694
+ "drafting"
41695
+ ]
41696
+ },
41697
+ {
41698
+ id: "2026-08-16-render-annotation-scaling",
41699
+ version: "0.9.4",
41700
+ date: "2026-08-16",
41701
+ category: "fix",
41702
+ title: "Render overlays stay readable at any size",
41703
+ summary: "The axes gizmo, part labels and dimensions now scale with the canvas instead of staying 1024px-sized, dimension lines clear round parts, and labels no longer land on the geometry they name.",
41704
+ features: [
41705
+ "rendering",
41706
+ "drafting"
41707
+ ],
41708
+ mcpTools: [
41709
+ "render_view"
41710
+ ]
41711
+ },
41712
+ {
41713
+ id: "2026-08-16-render-aa-and-depth-cue",
41714
+ version: "0.9.4",
41715
+ date: "2026-08-16",
41716
+ category: "fix",
41717
+ title: "Smoother renders: anti-aliasing and a per-pixel depth cue",
41718
+ summary: "Isometric renders anti-alias their silhouettes and linework, flat faces no longer break into hard tonal wedges at triangle boundaries, and edge strokes sit on their geometry instead of half a stroke off.",
41719
+ features: [
41720
+ "rendering",
41721
+ "tessellation"
41722
+ ],
41723
+ mcpTools: [
41724
+ "render_view"
41725
+ ]
41726
+ },
41562
41727
  {
41563
41728
  id: "2026-08-16-loon-sheet-metal-bindings",
41564
41729
  version: "0.9.4",
@@ -41578,6 +41743,20 @@ var init_CHANGELOG = __esm({
41578
41743
  "flat_pattern_from_solid"
41579
41744
  ]
41580
41745
  },
41746
+ {
41747
+ id: "2026-08-16-cli-sheet-metal-fold",
41748
+ version: "0.9.4",
41749
+ date: "2026-08-16",
41750
+ category: "fix",
41751
+ title: "Sheet-metal parts export from the CLI again",
41752
+ summary: "vcad info/export and vcad-render now fold sheet-metal chains \u2014 including jogs and bend relief \u2014 instead of reporting zero triangles, and a root that fails to evaluate says why.",
41753
+ features: [
41754
+ "sheet-metal",
41755
+ "cli",
41756
+ "loon",
41757
+ "export"
41758
+ ]
41759
+ },
41581
41760
  {
41582
41761
  id: "2026-08-16-cli-loon-input-everywhere",
41583
41762
  version: "0.9.4",
@@ -50057,7 +50236,7 @@ var init_source_provenance = __esm({
50057
50236
 
50058
50237
  // src/tools/loon.ts
50059
50238
  import { readFileSync as readFileSync3 } from "node:fs";
50060
- import { isAbsolute as isAbsolute2, join as join2, resolve as resolve2, sep as sep2 } from "node:path";
50239
+ import { delimiter, isAbsolute as isAbsolute2, join as join2, resolve as resolve2, sep as sep2 } from "node:path";
50061
50240
  function composeLoonProgram(input) {
50062
50241
  const { source, use_loons, loons } = input;
50063
50242
  const names = [
@@ -50088,12 +50267,18 @@ function readModule(base, name) {
50088
50267
  }
50089
50268
  return null;
50090
50269
  }
50270
+ function loonLibDirs(env = process.env) {
50271
+ const raw = env.VCAD_LOON_PATH;
50272
+ if (!raw) return [];
50273
+ return raw.split(delimiter).filter((d) => d.length > 0);
50274
+ }
50091
50275
  function composeLoonModules(input) {
50092
50276
  const { source, loons, modules, base_dir } = input;
50093
50277
  const map = {};
50094
50278
  for (const m of loons ?? []) map[m.name] = m.source;
50095
50279
  Object.assign(map, modules ?? {});
50096
- if (base_dir) {
50280
+ const dirs = [...base_dir ? [base_dir] : [], ...loonLibDirs()];
50281
+ if (dirs.length) {
50097
50282
  const pending2 = [
50098
50283
  ...importedNames(source),
50099
50284
  ...Object.values(map).flatMap(importedNames)
@@ -50103,7 +50288,11 @@ function composeLoonModules(input) {
50103
50288
  const name = pending2.pop();
50104
50289
  if (seen.has(name) || map[name]) continue;
50105
50290
  seen.add(name);
50106
- const src = readModule(base_dir, name);
50291
+ let src = null;
50292
+ for (const dir of dirs) {
50293
+ src = readModule(dir, name);
50294
+ if (src !== null) break;
50295
+ }
50107
50296
  if (src === null) continue;
50108
50297
  map[name] = src;
50109
50298
  pending2.push(...importedNames(src));
@@ -55478,6 +55667,8 @@ var init_tool_metadata = __esm({
55478
55667
  import_step: { title: "Import STEP", annotations: RW },
55479
55668
  import_urdf: { title: "Import URDF", annotations: RW },
55480
55669
  import_kicad: { title: "Import KiCad", annotations: RW },
55670
+ import_altium: { title: "Import Altium", annotations: RW },
55671
+ import_altium_library: { title: "Import Altium Library", annotations: RO },
55481
55672
  import_eagle: { title: "Import EAGLE", annotations: RW },
55482
55673
  open_in_browser: { title: "Open in Browser", annotations: RW },
55483
55674
  // ── Physics gym ────────────────────────────────────────────────────────
@@ -78484,10 +78675,72 @@ function readSource(input, label) {
78484
78675
  }
78485
78676
  throw new Error("Provide either `filename` or `content_base64`");
78486
78677
  }
78678
+ function readSourceBytes(input, label) {
78679
+ const { filename, content_base64 } = input;
78680
+ if (content_base64) {
78681
+ const buf = Buffer.from(content_base64, "base64");
78682
+ if (buf.length === 0) throw new Error("content_base64 decoded to zero bytes");
78683
+ if (buf.length > MAX_PCB_BYTES) {
78684
+ throw new Error(`${label} content exceeds the ${MAX_PCB_BYTES} byte limit`);
78685
+ }
78686
+ return buf;
78687
+ }
78688
+ if (filename) {
78689
+ if (isRemoteDeployment()) {
78690
+ throw new Error(
78691
+ `This hosted server has no filesystem access \u2014 pass the ${label} contents as \`content_base64\` instead of \`filename\`.`
78692
+ );
78693
+ }
78694
+ const filepath = resolveWithinRoot(
78695
+ filename,
78696
+ process.env.VCAD_MCP_EXPORT_DIR ?? process.cwd()
78697
+ );
78698
+ if (!existsSync5(filepath)) throw new Error(`${label} file not found`);
78699
+ const stat = statSync3(filepath);
78700
+ if (!stat.isFile()) throw new Error(`${label} path is not a regular file`);
78701
+ if (stat.size > MAX_PCB_BYTES) {
78702
+ throw new Error(`${label} file exceeds the ${MAX_PCB_BYTES} byte limit`);
78703
+ }
78704
+ return readFileSync8(filepath);
78705
+ }
78706
+ throw new Error("Provide either `filename` or `content_base64`");
78707
+ }
78708
+ function registerBoard(pcb, name) {
78709
+ const doc = createDocument();
78710
+ doc.nodes["1"] = {
78711
+ id: 1,
78712
+ name,
78713
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
78714
+ op: { type: "PcbBoard", board: pcb }
78715
+ };
78716
+ doc.roots.push({ root: 1, material: "__pcb_fr4__" });
78717
+ doc.materials["__pcb_fr4__"] = {
78718
+ name: "__pcb_fr4__",
78719
+ color: [0.05, 0.35, 0.15],
78720
+ roughness: 0.6,
78721
+ metallic: 0
78722
+ };
78723
+ return registerSession(doc);
78724
+ }
78725
+ function boardSummary(pcb) {
78726
+ return {
78727
+ footprints: pcb.footprints.length,
78728
+ nets: pcb.nets.length,
78729
+ outline_vertices: pcb.outline?.vertices?.length ?? 0,
78730
+ traces: pcb.traces.length,
78731
+ vias: pcb.vias.length,
78732
+ zones: pcb.zones.length,
78733
+ design_rules: {
78734
+ trace_width: pcb.rules?.defaultRules?.traceWidth,
78735
+ clearance: pcb.rules?.defaultRules?.clearance,
78736
+ via_diameter: pcb.rules?.defaultRules?.viaDiameter
78737
+ }
78738
+ };
78739
+ }
78487
78740
  function boardName(input) {
78488
78741
  if (input.name) return input.name;
78489
78742
  if (input.filename) {
78490
- return input.filename.replace(/^.*[/\\]/, "").replace(/\.kicad_pcb$/i, "");
78743
+ return input.filename.replace(/^.*[/\\]/, "").replace(/\.(kicad_pcb|pcbdoc|pcblib|brd)$/i, "");
78491
78744
  }
78492
78745
  return "Imported board";
78493
78746
  }
@@ -78512,21 +78765,7 @@ async function importKicad(input) {
78512
78765
  "KiCad parsing is unavailable (kernel WASM is missing parseKicadPcb) or the file produced no board."
78513
78766
  );
78514
78767
  }
78515
- const doc = createDocument();
78516
- doc.nodes["1"] = {
78517
- id: 1,
78518
- name: boardName(args),
78519
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
78520
- op: { type: "PcbBoard", board: pcb }
78521
- };
78522
- doc.roots.push({ root: 1, material: "__pcb_fr4__" });
78523
- doc.materials["__pcb_fr4__"] = {
78524
- name: "__pcb_fr4__",
78525
- color: [0.05, 0.35, 0.15],
78526
- roughness: 0.6,
78527
- metallic: 0
78528
- };
78529
- const documentId = registerSession(doc);
78768
+ const documentId = registerBoard(pcb, boardName(args));
78530
78769
  const onlyPlacement = pcb.traces.length === 0 && pcb.vias.length === 0;
78531
78770
  return {
78532
78771
  content: [
@@ -78535,19 +78774,7 @@ async function importKicad(input) {
78535
78774
  text: JSON.stringify({
78536
78775
  success: true,
78537
78776
  document_id: documentId,
78538
- summary: {
78539
- footprints: pcb.footprints.length,
78540
- nets: pcb.nets.length,
78541
- outline_vertices: pcb.outline?.vertices?.length ?? 0,
78542
- traces: pcb.traces.length,
78543
- vias: pcb.vias.length,
78544
- zones: pcb.zones.length,
78545
- design_rules: {
78546
- trace_width: pcb.rules?.defaultRules?.traceWidth,
78547
- clearance: pcb.rules?.defaultRules?.clearance,
78548
- via_diameter: pcb.rules?.defaultRules?.viaDiameter
78549
- }
78550
- },
78777
+ summary: boardSummary(pcb),
78551
78778
  ...onlyPlacement ? {
78552
78779
  note: "Placement + netlist only \u2014 the file carried no routed copper."
78553
78780
  } : {}
@@ -78556,12 +78783,138 @@ async function importKicad(input) {
78556
78783
  ]
78557
78784
  };
78558
78785
  }
78559
- function importEagle(_input) {
78560
- return importError(
78561
- "Eagle (.brd) import is not yet supported. Export your board from Eagle as KiCad (File > Export > KiCad .kicad_pcb) and use import_kicad instead."
78562
- );
78786
+ function isCompoundFile(bytes) {
78787
+ return bytes.length >= 8 && bytes.subarray(0, 8).equals(Buffer.from([208, 207, 17, 224, 161, 177, 26, 225]));
78563
78788
  }
78564
- var MAX_PCB_BYTES, importKicadSchema, importEagleSchema, toolDefs46;
78789
+ async function importAltium(input) {
78790
+ const args = input ?? {};
78791
+ let bytes;
78792
+ try {
78793
+ bytes = readSourceBytes(args, "Altium");
78794
+ } catch (e) {
78795
+ return importError(e instanceof Error ? e.message : String(e));
78796
+ }
78797
+ const binary = isCompoundFile(bytes);
78798
+ let pcb;
78799
+ try {
78800
+ pcb = binary ? await parseAltiumPcbDoc2(new Uint8Array(bytes)) : await parseAltiumAsciiPcb2(bytes.toString("utf8"));
78801
+ } catch (e) {
78802
+ return importError(
78803
+ `Could not parse the Altium ${binary ? "binary" : "ASCII"} .PcbDoc: ` + (e instanceof Error ? e.message : String(e))
78804
+ );
78805
+ }
78806
+ if (!pcb) {
78807
+ return importError(
78808
+ "Altium parsing is unavailable (kernel WASM is missing parseAltiumPcbDoc)."
78809
+ );
78810
+ }
78811
+ const documentId = registerBoard(pcb, boardName(args));
78812
+ const onlyPlacement = pcb.traces.length === 0 && pcb.vias.length === 0;
78813
+ return {
78814
+ content: [
78815
+ {
78816
+ type: "text",
78817
+ text: JSON.stringify({
78818
+ success: true,
78819
+ document_id: documentId,
78820
+ source_format: binary ? "PcbDoc (binary)" : "PcbDoc (ASCII)",
78821
+ summary: boardSummary(pcb),
78822
+ ...onlyPlacement ? { note: "Placement + netlist only \u2014 the file carried no routed copper." } : {},
78823
+ ...binary ? {
78824
+ note_binary: "Binary .PcbDoc record layouts are reconstructed, not specified by Altium, and are validated against real open-hardware projects. Geometry that decoded implausibly fails the import outright rather than importing wrong, but spot-check with render_pcb before fabricating. Copper pours and text are not imported."
78825
+ } : {}
78826
+ })
78827
+ }
78828
+ ]
78829
+ };
78830
+ }
78831
+ async function importAltiumLibrary(input) {
78832
+ const args = input ?? {};
78833
+ let bytes;
78834
+ try {
78835
+ bytes = readSourceBytes(args, "Altium library");
78836
+ } catch (e) {
78837
+ return importError(e instanceof Error ? e.message : String(e));
78838
+ }
78839
+ let lib;
78840
+ try {
78841
+ lib = await parseAltiumPcbLib2(new Uint8Array(bytes));
78842
+ } catch (e) {
78843
+ return importError(
78844
+ `Could not parse the Altium .PcbLib: ${e instanceof Error ? e.message : String(e)}`
78845
+ );
78846
+ }
78847
+ if (!lib) {
78848
+ return importError(
78849
+ "Altium parsing is unavailable (kernel WASM is missing parseAltiumPcbLib)."
78850
+ );
78851
+ }
78852
+ return {
78853
+ content: [
78854
+ {
78855
+ type: "text",
78856
+ text: JSON.stringify({
78857
+ success: true,
78858
+ footprint_count: lib.footprints.length,
78859
+ footprints: lib.footprints.map((f) => ({
78860
+ name: f.name,
78861
+ pad_count: f.pads.length,
78862
+ pads: f.pads.map((p) => ({
78863
+ number: p.number,
78864
+ type: p.pad_type,
78865
+ position: p.position,
78866
+ rotation: p.rotation,
78867
+ drill: p.drill?.diameter ?? null
78868
+ }))
78869
+ }))
78870
+ })
78871
+ }
78872
+ ]
78873
+ };
78874
+ }
78875
+ async function importEagle(input) {
78876
+ const args = input ?? {};
78877
+ let content;
78878
+ try {
78879
+ content = readSource(args, "Eagle");
78880
+ } catch (e) {
78881
+ return importError(e instanceof Error ? e.message : String(e));
78882
+ }
78883
+ if (!content.includes("<eagle") && !content.includes("<board")) {
78884
+ return importError(
78885
+ "This does not look like an Eagle XML .brd file. Eagle 5 and earlier wrote a binary .brd \u2014 open it in Eagle 6+ (which converts it to XML) and save, then import that."
78886
+ );
78887
+ }
78888
+ let pcb;
78889
+ try {
78890
+ pcb = await parseEagleBrd2(content);
78891
+ } catch (e) {
78892
+ return importError(
78893
+ `Could not parse the Eagle .brd: ${e instanceof Error ? e.message : String(e)}`
78894
+ );
78895
+ }
78896
+ if (!pcb) {
78897
+ return importError(
78898
+ "Eagle parsing is unavailable (kernel WASM is missing parseEagleBrd)."
78899
+ );
78900
+ }
78901
+ const documentId = registerBoard(pcb, boardName(args));
78902
+ const onlyPlacement = pcb.traces.length === 0 && pcb.vias.length === 0;
78903
+ return {
78904
+ content: [
78905
+ {
78906
+ type: "text",
78907
+ text: JSON.stringify({
78908
+ success: true,
78909
+ document_id: documentId,
78910
+ summary: boardSummary(pcb),
78911
+ ...onlyPlacement ? { note: "Placement + netlist only \u2014 the file carried no routed copper." } : {}
78912
+ })
78913
+ }
78914
+ ]
78915
+ };
78916
+ }
78917
+ var MAX_PCB_BYTES, importKicadSchema, importAltiumSchema, importAltiumLibrarySchema, importEagleSchema, toolDefs46;
78565
78918
  var init_import_pcb = __esm({
78566
78919
  "src/tools/import-pcb.ts"() {
78567
78920
  "use strict";
@@ -78589,16 +78942,50 @@ var init_import_pcb = __esm({
78589
78942
  }
78590
78943
  }
78591
78944
  };
78945
+ importAltiumSchema = {
78946
+ type: "object",
78947
+ properties: {
78948
+ filename: {
78949
+ type: "string",
78950
+ description: "Path to a .PcbDoc file on the server filesystem (relative to VCAD_MCP_EXPORT_DIR or cwd). Native binary and ASCII exports are both accepted. On hosted servers pass content_base64 instead."
78951
+ },
78952
+ content_base64: {
78953
+ type: "string",
78954
+ description: "Base64-encoded .PcbDoc contents (preferred for hosted servers)."
78955
+ },
78956
+ name: {
78957
+ type: "string",
78958
+ description: "Board name for display (default: filename without extension)."
78959
+ }
78960
+ }
78961
+ };
78962
+ importAltiumLibrarySchema = {
78963
+ type: "object",
78964
+ properties: {
78965
+ filename: {
78966
+ type: "string",
78967
+ description: "Path to a .PcbLib footprint library (binary or ASCII)."
78968
+ },
78969
+ content_base64: {
78970
+ type: "string",
78971
+ description: "Base64-encoded .PcbLib contents."
78972
+ }
78973
+ }
78974
+ };
78592
78975
  importEagleSchema = {
78593
78976
  type: "object",
78594
78977
  properties: {
78595
78978
  filename: {
78596
78979
  type: "string",
78597
- description: "Path to a .brd file (Eagle XML format)."
78980
+ description: "Path to a .brd file (Eagle XML, Eagle 6+)."
78598
78981
  },
78599
78982
  content_base64: {
78600
78983
  type: "string",
78601
- description: "Base64-encoded .brd contents."
78984
+ description: "Base64-encoded .brd contents (preferred for hosted servers)."
78985
+ },
78986
+ name: {
78987
+ type: "string",
78988
+ description: "Board name for display (default: filename without extension)."
78602
78989
  }
78603
78990
  }
78604
78991
  };
@@ -78611,13 +78998,29 @@ var init_import_pcb = __esm({
78611
78998
  handler: async (a) => await importKicad(a),
78612
78999
  behavior: behavior({ writesDoc: true, geometry: true })
78613
79000
  },
79001
+ {
79002
+ name: "import_altium",
79003
+ pack: "ecad",
79004
+ description: "Import an Altium .PcbDoc board into a live session \u2014 board outline, footprints with pads + nets, design rules, and any routed traces/vias. Accepts both the native binary file and an ASCII export (detected automatically). Returns a document_id ready for render_pcb, run_drc, get_pad_positions, route_nets, and export_gerber. The binary path fails closed on record layouts it cannot decode rather than importing a partially-correct board. Pass content_base64 on hosted servers.",
79005
+ inputSchema: importAltiumSchema,
79006
+ handler: async (a) => await importAltium(a),
79007
+ behavior: behavior({ writesDoc: true, geometry: true })
79008
+ },
79009
+ {
79010
+ name: "import_altium_library",
79011
+ pack: "ecad",
79012
+ description: "Read an Altium .PcbLib footprint library (binary or ASCII) and return its footprint patterns with pads, shapes, and drills. A library is not a board, so no document_id is minted.",
79013
+ inputSchema: importAltiumLibrarySchema,
79014
+ handler: async (a) => await importAltiumLibrary(a),
79015
+ behavior: behavior({})
79016
+ },
78614
79017
  {
78615
79018
  name: "import_eagle",
78616
79019
  pack: "ecad",
78617
- description: "Import an Eagle .brd file (not yet supported). Export your board from Eagle as KiCad (.kicad_pcb) and use import_kicad instead.",
79020
+ description: "Import an Eagle .brd board (XML, Eagle 6+) into a live session \u2014 board outline, packages placed as footprints with pads, signals as nets, and any hand-routed traces/vias. Returns a document_id ready for render_pcb, run_drc, get_pad_positions, route_nets, and export_gerber. Pass content_base64 on hosted servers.",
78618
79021
  inputSchema: importEagleSchema,
78619
- handler: (a) => importEagle(a),
78620
- behavior: behavior({})
79022
+ handler: async (a) => await importEagle(a),
79023
+ behavior: behavior({ writesDoc: true, geometry: true })
78621
79024
  }
78622
79025
  ];
78623
79026
  }
@@ -83649,8 +84052,8 @@ var init_server3 = __esm({
83649
84052
  init_order_feed();
83650
84053
  init_animate();
83651
84054
  PKG_VERSION = (() => {
83652
- if ("0.9.4-main.72") {
83653
- return "0.9.4-main.72";
84055
+ if ("0.9.4-main.74") {
84056
+ return "0.9.4-main.74";
83654
84057
  }
83655
84058
  try {
83656
84059
  const req = createRequire2(import.meta.url);
@@ -83659,8 +84062,8 @@ var init_server3 = __esm({
83659
84062
  return "0.0.0";
83660
84063
  }
83661
84064
  })();
83662
- BUILD_SHA = "150875476b916d4e772bdeb33726c543ccf2756d";
83663
- BUILD_TIME = "2026-08-17T03:05:59.733Z";
84065
+ BUILD_SHA = "cad60a705d41503617de2b556150aaeae3e893c8";
84066
+ BUILD_TIME = "2026-08-19T15:53:22.260Z";
83664
84067
  SHORT_SHA = BUILD_SHA === "unknown" ? "unknown" : BUILD_SHA.slice(0, 7);
83665
84068
  VERSION_WITH_BUILD = SHORT_SHA === "unknown" ? PKG_VERSION : `${PKG_VERSION}+${SHORT_SHA}`;
83666
84069
  INSTANCE_ID = randomUUID7().slice(0, 8);
@@ -83856,6 +84259,8 @@ var init_server3 = __esm({
83856
84259
  "import_step",
83857
84260
  "import_urdf",
83858
84261
  "import_kicad",
84262
+ "import_altium",
84263
+ "import_altium_library",
83859
84264
  "import_eagle",
83860
84265
  "open_in_browser",
83861
84266
  // ── Physics gym ────────────────────────────────────────────
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vcad/mcp",
3
- "version": "0.9.4-main.72",
3
+ "version": "0.9.4-main.74",
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": "150875476b916d4e772bdeb33726c543ccf2756d",
23
- "builtAt": "2026-08-17T03:05:59.733Z"
22
+ "sha": "cad60a705d41503617de2b556150aaeae3e893c8",
23
+ "builtAt": "2026-08-19T15:53:22.260Z"
24
24
  }
25
25
  }
Binary file