@vcad/mcp 0.9.4-main.47 → 0.9.4-main.49

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 e8c30217ef94fd492ae13caee599b581d8334d34 at 2026-07-26T17:13:18.437Z. Source: https://github.com/ecto/vcad
9
+ Built from 40ccc073edea8494ce6e1a94ea360bf79cfc630d at 2026-07-29T16:38:39.110Z. Source: https://github.com/ecto/vcad
package/index.mjs CHANGED
@@ -6180,22 +6180,6 @@ var init_vcad_kernel_wasm = __esm({
6180
6180
  resetAccumulation() {
6181
6181
  wasm.raytracer_resetAccumulation(this.__wbg_ptr);
6182
6182
  }
6183
- /**
6184
- * Set SSAO (screen-space ambient occlusion) parameters.
6185
- *
6186
- * # Arguments
6187
- * * `radius` - World-space hemisphere sample radius (default 0.3)
6188
- * * `intensity` - Occlusion strength: 0 = disabled, 1 = default (>1 stylized)
6189
- * * `bias` - Depth bias to prevent self-occlusion (default 0.001)
6190
- * * `sample_count` - Hemisphere samples per frame: 8, 16, or 32 (default 16)
6191
- * @param {number} radius
6192
- * @param {number} intensity
6193
- * @param {number} bias
6194
- * @param {number} sample_count
6195
- */
6196
- setAO(radius, intensity, bias, sample_count) {
6197
- wasm.raytracer_setAO(this.__wbg_ptr, radius, intensity, bias, sample_count);
6198
- }
6199
6183
  /**
6200
6184
  * Set the debug render mode.
6201
6185
  *
@@ -6251,12 +6235,6 @@ var init_vcad_kernel_wasm = __esm({
6251
6235
  wasm.raytracer_setEdgeStyle(this.__wbg_ptr, enable_silhouette, enable_crease, enable_boundary, silhouette_r, silhouette_g, silhouette_b, silhouette_a, crease_r, crease_g, crease_b, crease_a, boundary_r, boundary_g, boundary_b, boundary_a, silhouette_width, crease_width, boundary_width, edge_softness);
6252
6236
  }
6253
6237
  /**
6254
- * Set the material for all faces in the scene.
6255
- *
6256
- * # Arguments
6257
- * * `r`, `g`, `b` - RGB color components (0-1 range, linear)
6258
- * * `metallic` - Metallic factor (0 = dielectric, 1 = metal)
6259
- * * `roughness` - Roughness factor (0 = smooth/mirror, 1 = rough/diffuse)
6260
6238
  * @param {number} r
6261
6239
  * @param {number} g
6262
6240
  * @param {number} b
@@ -6269,6 +6247,57 @@ var init_vcad_kernel_wasm = __esm({
6269
6247
  throw takeFromExternrefTable0(ret[0]);
6270
6248
  }
6271
6249
  }
6250
+ /**
6251
+ * Set the material for all faces in the scene.
6252
+ *
6253
+ * # Arguments
6254
+ * * `r`, `g`, `b` - RGB color components (0-1 range, linear)
6255
+ * * `metallic` - Metallic factor (0 = dielectric, 1 = metal)
6256
+ * * `roughness` - Roughness factor (0 = smooth/mirror, 1 = rough/diffuse)
6257
+ * Set the material from a serialized IR `MaterialDef`.
6258
+ *
6259
+ * Preferred over `setMaterial`: that one only carries colour, metallic and
6260
+ * roughness, so clearcoat, IOR and anisotropy never reached the viewport
6261
+ * and a brushed or lacquered part shaded differently here than under
6262
+ * `vcad-render --photoreal`. This runs the SAME derivation the CPU
6263
+ * renderer uses (`Pbr::from_material_def`), so both agree by construction.
6264
+ *
6265
+ * `json` is a `MaterialDef` object; pass `null`/empty to fall back to the
6266
+ * optional `tint` (linear RGB) or the neutral default.
6267
+ * @param {string | null} [json]
6268
+ * @param {Float64Array | null} [tint]
6269
+ */
6270
+ setMaterialFromDef(json, tint) {
6271
+ var ptr0 = isLikeNone(json) ? 0 : passStringToWasm0(json, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
6272
+ var len0 = WASM_VECTOR_LEN;
6273
+ var ptr1 = isLikeNone(tint) ? 0 : passArrayF64ToWasm0(tint, wasm.__wbindgen_malloc);
6274
+ var len1 = WASM_VECTOR_LEN;
6275
+ const ret = wasm.raytracer_setMaterialFromDef(this.__wbg_ptr, ptr0, len0, ptr1, len1);
6276
+ if (ret[1]) {
6277
+ throw takeFromExternrefTable0(ret[0]);
6278
+ }
6279
+ }
6280
+ /**
6281
+ * Set the path tracer's quality ceiling and stylisation mode.
6282
+ *
6283
+ * Replaces the old `setAO`. The renderer is a real path tracer now, so
6284
+ * screen-space ambient occlusion is gone — multi-bounce GI computes
6285
+ * contact occlusion correctly, and stacking a proxy on top of it would
6286
+ * double-darken every concave corner.
6287
+ *
6288
+ * # Arguments
6289
+ * * `max_depth` - Ceiling on path length (1 = direct lighting only,
6290
+ * default 6, which matches `vcad-render --photoreal`). Actual depth
6291
+ * escalates with accumulation, so the draft frame stays interactive
6292
+ * regardless of this value.
6293
+ * * `stylize` - Draw the Sobel edge overlay. Turn this OFF for a
6294
+ * photoreal viewport: edge lines fight photorealism.
6295
+ * @param {number} max_depth
6296
+ * @param {boolean} stylize
6297
+ */
6298
+ setPathTrace(max_depth, stylize) {
6299
+ wasm.raytracer_setPathTrace(this.__wbg_ptr, max_depth, stylize);
6300
+ }
6272
6301
  /**
6273
6302
  * Set the adaptive refinement sample count.
6274
6303
  *
@@ -13733,10 +13762,10 @@ function isValidJWT(token, algorithm = null) {
13733
13762
  const tokensParts = token.split(".");
13734
13763
  if (tokensParts.length !== 3)
13735
13764
  return false;
13736
- const [header] = tokensParts;
13737
- if (!header)
13765
+ const [header2] = tokensParts;
13766
+ if (!header2)
13738
13767
  return false;
13739
- const parsedHeader = JSON.parse(atob(header));
13768
+ const parsedHeader = JSON.parse(atob(header2));
13740
13769
  if ("typ" in parsedHeader && parsedHeader?.typ !== "JWT")
13741
13770
  return false;
13742
13771
  if (!parsedHeader.alg)
@@ -18655,9 +18684,9 @@ var init_protocol = __esm({
18655
18684
  });
18656
18685
  this.setRequestHandler(ListTasksRequestSchema, async (request, extra) => {
18657
18686
  try {
18658
- const { tasks, nextCursor } = await this._taskStore.listTasks(request.params?.cursor, extra.sessionId);
18687
+ const { tasks: tasks2, nextCursor } = await this._taskStore.listTasks(request.params?.cursor, extra.sessionId);
18659
18688
  return {
18660
- tasks,
18689
+ tasks: tasks2,
18661
18690
  nextCursor,
18662
18691
  _meta: {}
18663
18692
  };
@@ -18804,7 +18833,7 @@ var init_protocol = __esm({
18804
18833
  const capturedTransport = this._transport;
18805
18834
  const relatedTaskId = request.params?._meta?.[RELATED_TASK_META_KEY]?.taskId;
18806
18835
  if (handler === void 0) {
18807
- const errorResponse = {
18836
+ const errorResponse2 = {
18808
18837
  jsonrpc: "2.0",
18809
18838
  id: request.id,
18810
18839
  error: {
@@ -18815,11 +18844,11 @@ var init_protocol = __esm({
18815
18844
  if (relatedTaskId && this._taskMessageQueue) {
18816
18845
  this._enqueueTaskMessage(relatedTaskId, {
18817
18846
  type: "error",
18818
- message: errorResponse,
18847
+ message: errorResponse2,
18819
18848
  timestamp: Date.now()
18820
18849
  }, capturedTransport?.sessionId).catch((error2) => this._onerror(new Error(`Failed to enqueue error response: ${error2}`)));
18821
18850
  } else {
18822
- capturedTransport?.send(errorResponse).catch((error2) => this._onerror(new Error(`Failed to send an error response: ${error2}`)));
18851
+ capturedTransport?.send(errorResponse2).catch((error2) => this._onerror(new Error(`Failed to send an error response: ${error2}`)));
18823
18852
  }
18824
18853
  return;
18825
18854
  }
@@ -18889,7 +18918,7 @@ var init_protocol = __esm({
18889
18918
  if (abortController.signal.aborted) {
18890
18919
  return;
18891
18920
  }
18892
- const errorResponse = {
18921
+ const errorResponse2 = {
18893
18922
  jsonrpc: "2.0",
18894
18923
  id: request.id,
18895
18924
  error: {
@@ -18901,11 +18930,11 @@ var init_protocol = __esm({
18901
18930
  if (relatedTaskId && this._taskMessageQueue) {
18902
18931
  await this._enqueueTaskMessage(relatedTaskId, {
18903
18932
  type: "error",
18904
- message: errorResponse,
18933
+ message: errorResponse2,
18905
18934
  timestamp: Date.now()
18906
18935
  }, capturedTransport?.sessionId);
18907
18936
  } else {
18908
- await capturedTransport?.send(errorResponse);
18937
+ await capturedTransport?.send(errorResponse2);
18909
18938
  }
18910
18939
  }).catch((error2) => this._onerror(new Error(`Failed to send response: ${error2}`))).finally(() => {
18911
18940
  if (this._requestHandlerAbortControllers.get(request.id) === abortController) {
@@ -41040,6 +41069,18 @@ var init_CHANGELOG = __esm({
41040
41069
  CHANGELOG_default = {
41041
41070
  $schema: "./changelog.schema.json",
41042
41071
  entries: [
41072
+ {
41073
+ id: "2026-07-28-mcp-2026-07-28-protocol",
41074
+ version: "0.9.4",
41075
+ date: "2026-07-28",
41076
+ category: "feat",
41077
+ title: "MCP 2026-07-28 protocol support",
41078
+ summary: "The vcad MCP server now fully speaks the stateless 2026-07-28 revision \u2014 server/discover, MRTR, the Tasks extension, subscriptions/listen, and streamed progress \u2014 alongside every earlier revision.",
41079
+ features: [
41080
+ "mcp"
41081
+ ],
41082
+ mcpTools: []
41083
+ },
41043
41084
  {
41044
41085
  id: "2026-07-26-viewport-path-tracer",
41045
41086
  version: "0.9.4",
@@ -41073,6 +41114,21 @@ var init_CHANGELOG = __esm({
41073
41114
  "verify_receipt"
41074
41115
  ]
41075
41116
  },
41117
+ {
41118
+ id: "2026-07-26-step-export-product-anchor",
41119
+ version: "0.9.4",
41120
+ date: "2026-07-26",
41121
+ category: "fix",
41122
+ title: "STEP exports now open in external CAD",
41123
+ summary: "Exported STEP files carry the AP214 product/representation anchor and mm units, so Shapr3D, SolidWorks, and Fusion no longer reject them as containing no 3D geometry.",
41124
+ features: [
41125
+ "step-export",
41126
+ "interop"
41127
+ ],
41128
+ mcpTools: [
41129
+ "export_cad"
41130
+ ]
41131
+ },
41076
41132
  {
41077
41133
  id: "2026-07-26-step-export-booleans",
41078
41134
  version: "0.9.4",
@@ -41090,6 +41146,19 @@ var init_CHANGELOG = __esm({
41090
41146
  "export_cad"
41091
41147
  ]
41092
41148
  },
41149
+ {
41150
+ id: "2026-07-26-step-analytic-circle-edges",
41151
+ version: "0.9.4",
41152
+ date: "2026-07-26",
41153
+ category: "fix",
41154
+ title: "STEP export: curved faces survive third-party import",
41155
+ summary: "Circular edges now export as analytic CIRCLEs instead of chord polylines, so importers like Shapr3D keep cylinder walls and annular faces at the spec 1e-6 accuracy.",
41156
+ features: [
41157
+ "step",
41158
+ "export",
41159
+ "kernel"
41160
+ ]
41161
+ },
41093
41162
  {
41094
41163
  id: "2026-07-26-render-loon-directly",
41095
41164
  version: "0.9.4",
@@ -53175,6 +53244,693 @@ var init_artifact_route = __esm({
53175
53244
  }
53176
53245
  });
53177
53246
 
53247
+ // node_modules/@modelcontextprotocol/sdk/dist/esm/inMemory.js
53248
+ var InMemoryTransport;
53249
+ var init_inMemory = __esm({
53250
+ "node_modules/@modelcontextprotocol/sdk/dist/esm/inMemory.js"() {
53251
+ InMemoryTransport = class _InMemoryTransport {
53252
+ constructor() {
53253
+ this._messageQueue = [];
53254
+ }
53255
+ /**
53256
+ * Creates a pair of linked in-memory transports that can communicate with each other. One should be passed to a Client and one to a Server.
53257
+ */
53258
+ static createLinkedPair() {
53259
+ const clientTransport = new _InMemoryTransport();
53260
+ const serverTransport = new _InMemoryTransport();
53261
+ clientTransport._otherTransport = serverTransport;
53262
+ serverTransport._otherTransport = clientTransport;
53263
+ return [clientTransport, serverTransport];
53264
+ }
53265
+ async start() {
53266
+ while (this._messageQueue.length > 0) {
53267
+ const queuedMessage = this._messageQueue.shift();
53268
+ this.onmessage?.(queuedMessage.message, queuedMessage.extra);
53269
+ }
53270
+ }
53271
+ async close() {
53272
+ const other = this._otherTransport;
53273
+ this._otherTransport = void 0;
53274
+ await other?.close();
53275
+ this.onclose?.();
53276
+ }
53277
+ /**
53278
+ * Sends a message with optional auth info.
53279
+ * This is useful for testing authentication scenarios.
53280
+ */
53281
+ async send(message, options) {
53282
+ if (!this._otherTransport) {
53283
+ throw new Error("Not connected");
53284
+ }
53285
+ if (this._otherTransport.onmessage) {
53286
+ this._otherTransport.onmessage(message, { authInfo: options?.authInfo });
53287
+ } else {
53288
+ this._otherTransport._messageQueue.push({ message, extra: { authInfo: options?.authInfo } });
53289
+ }
53290
+ }
53291
+ };
53292
+ }
53293
+ });
53294
+
53295
+ // src/protocol-2026.ts
53296
+ import { EventEmitter } from "node:events";
53297
+ import { randomUUID as randomUUID2 } from "node:crypto";
53298
+ function header(headers, name) {
53299
+ if (!headers) return void 0;
53300
+ const lower = name.toLowerCase();
53301
+ for (const [k, v] of Object.entries(headers)) {
53302
+ if (k.toLowerCase() !== lower) continue;
53303
+ return Array.isArray(v) ? v[0] : v;
53304
+ }
53305
+ return void 0;
53306
+ }
53307
+ function metaOf(msg) {
53308
+ const params = msg?.params;
53309
+ const meta = params?._meta;
53310
+ return meta && typeof meta === "object" ? meta : void 0;
53311
+ }
53312
+ function isModernMessage(msg, headers) {
53313
+ const first = Array.isArray(msg) ? msg[0] : msg;
53314
+ if (!first || typeof first !== "object") return false;
53315
+ const method = first.method;
53316
+ if (typeof method === "string" && MODERN_ONLY_METHODS.has(method)) return true;
53317
+ const declared = metaOf(first)?.[META_PROTOCOL_VERSION];
53318
+ if (typeof declared === "string") return true;
53319
+ return header(headers, "mcp-method") !== void 0;
53320
+ }
53321
+ function decodeHeaderValue(value) {
53322
+ if (!value.startsWith(BASE64_SENTINEL_PREFIX) || !value.endsWith(BASE64_SENTINEL_SUFFIX) || value.length < BASE64_SENTINEL_PREFIX.length + BASE64_SENTINEL_SUFFIX.length) {
53323
+ return value;
53324
+ }
53325
+ const encoded = value.slice(
53326
+ BASE64_SENTINEL_PREFIX.length,
53327
+ value.length - BASE64_SENTINEL_SUFFIX.length
53328
+ );
53329
+ try {
53330
+ return Buffer.from(encoded, "base64").toString("utf-8");
53331
+ } catch {
53332
+ return value;
53333
+ }
53334
+ }
53335
+ function errorResponse(id, code, message, data) {
53336
+ return {
53337
+ jsonrpc: "2.0",
53338
+ id: id ?? null,
53339
+ error: data === void 0 ? { code, message } : { code, message, data }
53340
+ };
53341
+ }
53342
+ function unsupportedVersion(id, requested) {
53343
+ return {
53344
+ status: 400,
53345
+ body: errorResponse(
53346
+ id,
53347
+ ERR_UNSUPPORTED_PROTOCOL_VERSION,
53348
+ "Unsupported protocol version",
53349
+ { supported: SUPPORTED_PROTOCOL_VERSIONS2, requested }
53350
+ )
53351
+ };
53352
+ }
53353
+ function headerMismatch(id, message) {
53354
+ return {
53355
+ status: 400,
53356
+ body: errorResponse(id, ERR_HEADER_MISMATCH, `Header mismatch: ${message}`)
53357
+ };
53358
+ }
53359
+ function pruneTasks() {
53360
+ const now = Date.now();
53361
+ for (const [id, t2] of tasks) {
53362
+ if (t2.expiresAt <= now) {
53363
+ t2.dispose?.();
53364
+ tasks.delete(id);
53365
+ }
53366
+ }
53367
+ }
53368
+ function touchTask(t2, status, message) {
53369
+ if (status) t2.status = status;
53370
+ if (message !== void 0) t2.statusMessage = message;
53371
+ t2.lastUpdatedAt = (/* @__PURE__ */ new Date()).toISOString();
53372
+ bus.emit(EVT_TASK, detailedTask(t2));
53373
+ }
53374
+ function detailedTask(t2) {
53375
+ const base = {
53376
+ taskId: t2.taskId,
53377
+ status: t2.status,
53378
+ createdAt: t2.createdAt,
53379
+ lastUpdatedAt: t2.lastUpdatedAt,
53380
+ ttlMs: TASK_TTL_MS,
53381
+ pollIntervalMs: TASK_POLL_INTERVAL_MS
53382
+ };
53383
+ if (t2.statusMessage !== void 0) base.statusMessage = t2.statusMessage;
53384
+ if (t2.status === "input_required") base.inputRequests = t2.inputRequests;
53385
+ if (t2.status === "completed") base.result = t2.result ?? {};
53386
+ if (t2.status === "failed") base.error = t2.error ?? { code: ERR_INTERNAL, message: "unknown" };
53387
+ return base;
53388
+ }
53389
+ async function openBridge(createServer2, clientInfo, clientCapabilities, hooks) {
53390
+ const [clientSide, serverSide] = InMemoryTransport.createLinkedPair();
53391
+ let nextId3 = 1;
53392
+ const pending2 = /* @__PURE__ */ new Map();
53393
+ clientSide.onmessage = (msg) => {
53394
+ const m = msg;
53395
+ const id = m.id;
53396
+ if (id !== void 0 && ("result" in m || "error" in m)) {
53397
+ pending2.get(id)?.(m);
53398
+ pending2.delete(id);
53399
+ return;
53400
+ }
53401
+ if (id !== void 0 && typeof m.method === "string") {
53402
+ void hooks.onServerRequest(
53403
+ m.method,
53404
+ m.params ?? {}
53405
+ ).then(
53406
+ (result) => clientSide.send({ jsonrpc: "2.0", id, result })
53407
+ ).catch(
53408
+ () => clientSide.send({
53409
+ jsonrpc: "2.0",
53410
+ id,
53411
+ error: { code: ERR_INTERNAL, message: "input bridge failed" }
53412
+ })
53413
+ );
53414
+ return;
53415
+ }
53416
+ if (typeof m.method === "string" && id === void 0) {
53417
+ hooks.onNotification?.(m);
53418
+ }
53419
+ };
53420
+ await clientSide.start();
53421
+ const server = await createServer2();
53422
+ await server.connect(serverSide);
53423
+ const rpc = (method, params) => {
53424
+ const id = nextId3++;
53425
+ return new Promise((resolve5) => {
53426
+ pending2.set(id, (m) => resolve5(m));
53427
+ void clientSide.send({
53428
+ jsonrpc: "2.0",
53429
+ id,
53430
+ method,
53431
+ params: params ?? {}
53432
+ });
53433
+ });
53434
+ };
53435
+ const init2 = await rpc("initialize", {
53436
+ protocolVersion: BRIDGE_VERSION,
53437
+ capabilities: clientCapabilities,
53438
+ clientInfo
53439
+ });
53440
+ await clientSide.send({
53441
+ jsonrpc: "2.0",
53442
+ method: "notifications/initialized"
53443
+ });
53444
+ return {
53445
+ rpc,
53446
+ handshake: init2.result ?? {},
53447
+ close: async () => {
53448
+ pending2.clear();
53449
+ await server.close().catch(() => {
53450
+ });
53451
+ await clientSide.close().catch(() => {
53452
+ });
53453
+ }
53454
+ };
53455
+ }
53456
+ async function handleModernRequest(msg, opts) {
53457
+ if (Array.isArray(msg)) {
53458
+ return {
53459
+ status: 400,
53460
+ body: errorResponse(
53461
+ null,
53462
+ ERR_INVALID_PARAMS,
53463
+ "Batched requests are not supported; send one JSON-RPC message per POST"
53464
+ )
53465
+ };
53466
+ }
53467
+ if (!msg || typeof msg !== "object") {
53468
+ return {
53469
+ status: 400,
53470
+ body: errorResponse(null, ERR_INVALID_PARAMS, "Malformed JSON-RPC message")
53471
+ };
53472
+ }
53473
+ const req = msg;
53474
+ const id = req.id;
53475
+ const method = req.method;
53476
+ const params = req.params ?? {};
53477
+ const meta = metaOf(req) ?? {};
53478
+ if (id === void 0 || id === null) return { status: 202, body: null };
53479
+ if (typeof method !== "string") {
53480
+ return {
53481
+ status: 400,
53482
+ body: errorResponse(id, ERR_INVALID_PARAMS, "Missing JSON-RPC method")
53483
+ };
53484
+ }
53485
+ const bodyVersion = meta[META_PROTOCOL_VERSION];
53486
+ const headerVersion = header(opts.headers, "mcp-protocol-version");
53487
+ if (typeof bodyVersion === "string" && headerVersion !== void 0 && headerVersion !== bodyVersion) {
53488
+ return headerMismatch(
53489
+ id,
53490
+ `MCP-Protocol-Version header value '${headerVersion}' does not match body value '${bodyVersion}'`
53491
+ );
53492
+ }
53493
+ const version2 = typeof bodyVersion === "string" ? bodyVersion : headerVersion;
53494
+ if (version2 === void 0) {
53495
+ if (method !== "server/discover") {
53496
+ return unsupportedVersion(id, "(unspecified)");
53497
+ }
53498
+ } else if (version2 !== PROTOCOL_2026) {
53499
+ return unsupportedVersion(id, version2);
53500
+ }
53501
+ if (opts.headers) {
53502
+ const methodHeader = header(opts.headers, "mcp-method");
53503
+ if (methodHeader === void 0) {
53504
+ return headerMismatch(id, "required header Mcp-Method is missing");
53505
+ }
53506
+ if (methodHeader !== method) {
53507
+ return headerMismatch(
53508
+ id,
53509
+ `Mcp-Method header value '${methodHeader}' does not match body value '${method}'`
53510
+ );
53511
+ }
53512
+ const expectedName = NAME_HEADER_METHODS.has(method) ? params.name : method === "resources/read" ? params.uri : void 0;
53513
+ if (typeof expectedName === "string") {
53514
+ const nameHeader = header(opts.headers, "mcp-name");
53515
+ if (nameHeader === void 0) {
53516
+ return headerMismatch(id, "required header Mcp-Name is missing");
53517
+ }
53518
+ if (decodeHeaderValue(nameHeader) !== expectedName) {
53519
+ return headerMismatch(
53520
+ id,
53521
+ `Mcp-Name header value does not match body value '${expectedName}'`
53522
+ );
53523
+ }
53524
+ }
53525
+ }
53526
+ if (method === "tasks/get" || method === "tasks/update" || method === "tasks/cancel") {
53527
+ return handleTaskMethod(id, method, params);
53528
+ }
53529
+ if (method === "initialize") {
53530
+ return {
53531
+ status: 404,
53532
+ body: errorResponse(
53533
+ id,
53534
+ ERR_METHOD_NOT_FOUND,
53535
+ `'initialize' was removed in protocol ${PROTOCOL_2026}; use 'server/discover'. This server supports: ${SUPPORTED_PROTOCOL_VERSIONS2.join(", ")}`
53536
+ )
53537
+ };
53538
+ }
53539
+ if (method !== "server/discover" && !FORWARDED_METHODS.has(method)) {
53540
+ return {
53541
+ status: 404,
53542
+ body: errorResponse(id, ERR_METHOD_NOT_FOUND, `Unknown method '${method}'`)
53543
+ };
53544
+ }
53545
+ const rawClientInfo = meta[META_CLIENT_INFO];
53546
+ const infoObj = rawClientInfo && typeof rawClientInfo === "object" ? rawClientInfo : {};
53547
+ const clientInfo = {
53548
+ name: typeof infoObj.name === "string" ? infoObj.name : "unknown",
53549
+ version: typeof infoObj.version === "string" ? infoObj.version : "0.0.0"
53550
+ };
53551
+ const rawCaps = meta[META_CLIENT_CAPABILITIES];
53552
+ const clientCapabilities = rawCaps && typeof rawCaps === "object" ? rawCaps : {};
53553
+ const wantsLogs = typeof meta[META_LOG_LEVEL] === "string";
53554
+ const inputResponses = params.inputResponses && typeof params.inputResponses === "object" ? params.inputResponses : {};
53555
+ const extCaps = clientCapabilities.extensions ?? {};
53556
+ const asTask = method === "tools/call" && TASKS_EXTENSION in extCaps && typeof params.name === "string" && LONG_RUNNING_TOOLS.has(params.name);
53557
+ let inputSeq = 0;
53558
+ const capturedInputRequests = {};
53559
+ const onServerRequest = async (reqMethod, reqParams) => {
53560
+ if (reqMethod !== "elicitation/create") {
53561
+ throw new Error(`unsupported server-initiated request ${reqMethod}`);
53562
+ }
53563
+ if (bridgeRef) {
53564
+ const parked = taskElicitation(bridgeRef, reqParams);
53565
+ if (parked) return parked;
53566
+ }
53567
+ const key = `input_${++inputSeq}`;
53568
+ const provided = inputResponses[key];
53569
+ if (provided) return provided;
53570
+ if (!MRTR_METHODS.has(method)) return { action: "cancel" };
53571
+ const { elicitationId: _drop, ...cleanParams } = reqParams;
53572
+ capturedInputRequests[key] = {
53573
+ method: "elicitation/create",
53574
+ params: cleanParams
53575
+ };
53576
+ return { action: "cancel" };
53577
+ };
53578
+ let bridge;
53579
+ let bridgeRef;
53580
+ try {
53581
+ bridge = bridgeRef = await openBridge(opts.createServer, clientInfo, clientCapabilities, {
53582
+ onNotification: (n) => {
53583
+ const nm = n.method;
53584
+ if (nm === "notifications/message" && !wantsLogs) return;
53585
+ opts.onNotification?.(n);
53586
+ },
53587
+ onServerRequest
53588
+ });
53589
+ if (method === "server/discover") {
53590
+ const info = bridge.handshake;
53591
+ const capabilities = { ...info.capabilities ?? {} };
53592
+ capabilities.extensions = {
53593
+ ...capabilities.extensions ?? {},
53594
+ [TASKS_EXTENSION]: {}
53595
+ };
53596
+ const result2 = {
53597
+ resultType: "complete",
53598
+ supportedVersions: SUPPORTED_PROTOCOL_VERSIONS2,
53599
+ capabilities,
53600
+ _meta: { [META_SERVER_INFO]: info.serverInfo ?? {} }
53601
+ };
53602
+ if (info.instructions) result2.instructions = info.instructions;
53603
+ decorateCache(result2, "server/discover");
53604
+ return { status: 200, body: { jsonrpc: "2.0", id, result: result2 } };
53605
+ }
53606
+ const serverInfo = bridge.handshake.serverInfo ?? {
53607
+ name: "vcad",
53608
+ version: "0.0.0"
53609
+ };
53610
+ const forwarded = stripModernFields(params);
53611
+ if (asTask) {
53612
+ pruneTasks();
53613
+ const record2 = createTaskRecord(params.name);
53614
+ const owned = bridge;
53615
+ bridge = void 0;
53616
+ runTaskInBackground(record2, owned, method, forwarded, inputResponses);
53617
+ const result2 = {
53618
+ resultType: "task",
53619
+ ...detailedTask(record2),
53620
+ _meta: { [META_SERVER_INFO]: serverInfo }
53621
+ };
53622
+ return { status: 200, body: { jsonrpc: "2.0", id, result: result2 } };
53623
+ }
53624
+ const reply = await bridge.rpc(method, forwarded);
53625
+ if (Object.keys(capturedInputRequests).length > 0) {
53626
+ const result2 = {
53627
+ resultType: "input_required",
53628
+ inputRequests: capturedInputRequests,
53629
+ _meta: { [META_SERVER_INFO]: serverInfo }
53630
+ };
53631
+ return { status: 200, body: { jsonrpc: "2.0", id, result: result2 } };
53632
+ }
53633
+ if (reply.error) {
53634
+ const code = reply.error.code === LEGACY_RESOURCE_NOT_FOUND ? ERR_INVALID_PARAMS : reply.error.code;
53635
+ return {
53636
+ status: code === ERR_METHOD_NOT_FOUND ? 404 : 200,
53637
+ body: errorResponse(id, code, reply.error.message, reply.error.data)
53638
+ };
53639
+ }
53640
+ const result = { ...reply.result ?? {} };
53641
+ result.resultType = "complete";
53642
+ const resultMeta = result._meta && typeof result._meta === "object" ? { ...result._meta } : {};
53643
+ resultMeta[META_SERVER_INFO] = serverInfo;
53644
+ result._meta = resultMeta;
53645
+ decorateCache(result, method);
53646
+ if (method === "tools/call" && params.name === "set_tool_packs") {
53647
+ bus.emit(EVT_TOOLS_CHANGED);
53648
+ }
53649
+ return { status: 200, body: { jsonrpc: "2.0", id, result } };
53650
+ } catch (err7) {
53651
+ return {
53652
+ status: 500,
53653
+ body: errorResponse(
53654
+ id,
53655
+ ERR_INTERNAL,
53656
+ err7 instanceof Error ? err7.message : String(err7)
53657
+ )
53658
+ };
53659
+ } finally {
53660
+ await bridge?.close();
53661
+ }
53662
+ }
53663
+ function decorateCache(result, method) {
53664
+ const ttl = CACHEABLE_METHODS.get(method);
53665
+ if (ttl === void 0) return;
53666
+ result.ttlMs = ttl;
53667
+ result.cacheScope = CACHE_SCOPE;
53668
+ }
53669
+ function stripModernFields(params) {
53670
+ const out = { ...params };
53671
+ delete out.inputResponses;
53672
+ delete out.requestState;
53673
+ const meta = out._meta;
53674
+ if (meta && typeof meta === "object") {
53675
+ const rest = { ...meta };
53676
+ delete rest[META_PROTOCOL_VERSION];
53677
+ delete rest[META_CLIENT_CAPABILITIES];
53678
+ delete rest[META_CLIENT_INFO];
53679
+ delete rest[META_LOG_LEVEL];
53680
+ if (Object.keys(rest).length === 0) delete out._meta;
53681
+ else out._meta = rest;
53682
+ }
53683
+ return out;
53684
+ }
53685
+ function createTaskRecord(toolName) {
53686
+ const now = (/* @__PURE__ */ new Date()).toISOString();
53687
+ const record2 = {
53688
+ taskId: `task_${randomUUID2().slice(0, 13)}`,
53689
+ status: "working",
53690
+ statusMessage: `running ${toolName}`,
53691
+ createdAt: now,
53692
+ lastUpdatedAt: now,
53693
+ expiresAt: Date.now() + TASK_TTL_MS,
53694
+ toolName,
53695
+ inputRequests: {},
53696
+ pendingInputs: /* @__PURE__ */ new Map(),
53697
+ cancelRequested: false
53698
+ };
53699
+ tasks.set(record2.taskId, record2);
53700
+ return record2;
53701
+ }
53702
+ function runTaskInBackground(record2, bridge, method, forwarded, presetResponses) {
53703
+ record2.dispose = () => {
53704
+ void bridge.close();
53705
+ };
53706
+ taskInputHooks.set(bridge, { record: record2, presetResponses, seq: 0 });
53707
+ void bridge.rpc(method, forwarded).then((reply) => {
53708
+ if (record2.status === "cancelled") return;
53709
+ if (reply.error) {
53710
+ record2.error = reply.error;
53711
+ touchTask(record2, "failed", reply.error.message);
53712
+ } else {
53713
+ const result = { ...reply.result ?? {} };
53714
+ result.resultType = "complete";
53715
+ record2.result = result;
53716
+ touchTask(record2, "completed", `${record2.toolName} finished`);
53717
+ }
53718
+ }).catch((err7) => {
53719
+ record2.error = {
53720
+ code: ERR_INTERNAL,
53721
+ message: err7 instanceof Error ? err7.message : String(err7)
53722
+ };
53723
+ touchTask(record2, "failed");
53724
+ }).finally(() => {
53725
+ taskInputHooks.delete(bridge);
53726
+ void bridge.close();
53727
+ record2.dispose = void 0;
53728
+ });
53729
+ }
53730
+ function taskElicitation(bridge, reqParams) {
53731
+ const ctx = taskInputHooks.get(bridge);
53732
+ if (!ctx) return void 0;
53733
+ const { record: record2, presetResponses } = ctx;
53734
+ const key = `input_${++ctx.seq}`;
53735
+ const preset = presetResponses[key];
53736
+ if (preset) return Promise.resolve(preset);
53737
+ if (record2.cancelRequested) return Promise.resolve({ action: "cancel" });
53738
+ const { elicitationId: _drop, ...cleanParams } = reqParams;
53739
+ record2.inputRequests[key] = {
53740
+ method: "elicitation/create",
53741
+ params: cleanParams
53742
+ };
53743
+ return new Promise((resolve5) => {
53744
+ record2.pendingInputs.set(key, resolve5);
53745
+ touchTask(record2, "input_required", "waiting for client input");
53746
+ });
53747
+ }
53748
+ function handleTaskMethod(id, method, params) {
53749
+ pruneTasks();
53750
+ const taskId = typeof params.taskId === "string" ? params.taskId : "";
53751
+ const record2 = tasks.get(taskId);
53752
+ if (!record2) {
53753
+ return {
53754
+ status: 200,
53755
+ body: errorResponse(
53756
+ id,
53757
+ ERR_INVALID_PARAMS,
53758
+ `Unknown taskId '${taskId}' \u2014 expired, cancelled long ago, or from a previous server instance`
53759
+ )
53760
+ };
53761
+ }
53762
+ if (method === "tasks/get") {
53763
+ const result = { resultType: "complete", ...detailedTask(record2) };
53764
+ return { status: 200, body: { jsonrpc: "2.0", id, result } };
53765
+ }
53766
+ if (method === "tasks/update") {
53767
+ const responses = params.inputResponses && typeof params.inputResponses === "object" ? params.inputResponses : {};
53768
+ for (const [key, response] of Object.entries(responses)) {
53769
+ const resolve5 = record2.pendingInputs.get(key);
53770
+ if (!resolve5) continue;
53771
+ record2.pendingInputs.delete(key);
53772
+ delete record2.inputRequests[key];
53773
+ resolve5(response);
53774
+ }
53775
+ if (record2.status === "input_required" && Object.keys(record2.inputRequests).length === 0) {
53776
+ touchTask(record2, "working", `running ${record2.toolName}`);
53777
+ }
53778
+ return {
53779
+ status: 200,
53780
+ body: { jsonrpc: "2.0", id, result: { resultType: "complete" } }
53781
+ };
53782
+ }
53783
+ record2.cancelRequested = true;
53784
+ if (record2.status === "working" || record2.status === "input_required") {
53785
+ for (const [key, resolve5] of record2.pendingInputs) {
53786
+ record2.pendingInputs.delete(key);
53787
+ delete record2.inputRequests[key];
53788
+ resolve5({ action: "cancel" });
53789
+ }
53790
+ touchTask(record2, "cancelled", "cancelled by client");
53791
+ }
53792
+ return {
53793
+ status: 200,
53794
+ body: { jsonrpc: "2.0", id, result: { resultType: "complete" } }
53795
+ };
53796
+ }
53797
+ function handleModernListen(msg, sink) {
53798
+ const req = msg;
53799
+ const subscriptionId = req.id ?? null;
53800
+ const filter = req.params?.notifications && typeof req.params.notifications === "object" ? req.params.notifications : {};
53801
+ const honored = {};
53802
+ const listeners = [];
53803
+ if (filter.toolsListChanged === true) {
53804
+ honored.toolsListChanged = true;
53805
+ const onChange = () => sink.send({
53806
+ jsonrpc: "2.0",
53807
+ method: "notifications/tools/list_changed",
53808
+ params: { _meta: { [META_SUBSCRIPTION_ID]: subscriptionId } }
53809
+ });
53810
+ bus.on(EVT_TOOLS_CHANGED, onChange);
53811
+ listeners.push(() => bus.off(EVT_TOOLS_CHANGED, onChange));
53812
+ }
53813
+ const taskIds = Array.isArray(filter.taskIds) ? filter.taskIds.filter((t2) => typeof t2 === "string") : [];
53814
+ if (taskIds.length > 0) {
53815
+ honored.taskIds = taskIds;
53816
+ const wanted = new Set(taskIds);
53817
+ const onTask = (task) => {
53818
+ if (!wanted.has(task.taskId)) return;
53819
+ sink.send({
53820
+ jsonrpc: "2.0",
53821
+ method: "notifications/tasks",
53822
+ params: { _meta: { [META_SUBSCRIPTION_ID]: subscriptionId }, ...task }
53823
+ });
53824
+ };
53825
+ bus.on(EVT_TASK, onTask);
53826
+ listeners.push(() => bus.off(EVT_TASK, onTask));
53827
+ }
53828
+ sink.send({
53829
+ jsonrpc: "2.0",
53830
+ method: "notifications/subscriptions/acknowledged",
53831
+ params: {
53832
+ _meta: { [META_SUBSCRIPTION_ID]: subscriptionId },
53833
+ notifications: honored
53834
+ }
53835
+ });
53836
+ return {
53837
+ close: () => {
53838
+ for (const off of listeners) off();
53839
+ listeners.length = 0;
53840
+ }
53841
+ };
53842
+ }
53843
+ var PROTOCOL_2026, BRIDGE_VERSION, SUPPORTED_PROTOCOL_VERSIONS2, META_PROTOCOL_VERSION, META_CLIENT_CAPABILITIES, META_CLIENT_INFO, META_SERVER_INFO, META_LOG_LEVEL, META_SUBSCRIPTION_ID, TASKS_EXTENSION, ERR_HEADER_MISMATCH, ERR_UNSUPPORTED_PROTOCOL_VERSION, ERR_METHOD_NOT_FOUND, ERR_INVALID_PARAMS, ERR_INTERNAL, LEGACY_RESOURCE_NOT_FOUND, LIST_TTL_MS, DISCOVER_TTL_MS, CACHE_SCOPE, CACHEABLE_METHODS, FORWARDED_METHODS, NAME_HEADER_METHODS, MRTR_METHODS, LONG_RUNNING_TOOLS, TASK_TTL_MS, TASK_POLL_INTERVAL_MS, MODERN_ONLY_METHODS, BASE64_SENTINEL_PREFIX, BASE64_SENTINEL_SUFFIX, bus, EVT_TOOLS_CHANGED, EVT_TASK, tasks, taskInputHooks;
53844
+ var init_protocol_2026 = __esm({
53845
+ "src/protocol-2026.ts"() {
53846
+ "use strict";
53847
+ init_inMemory();
53848
+ PROTOCOL_2026 = "2026-07-28";
53849
+ BRIDGE_VERSION = "2025-11-25";
53850
+ SUPPORTED_PROTOCOL_VERSIONS2 = [
53851
+ PROTOCOL_2026,
53852
+ "2025-11-25",
53853
+ "2025-06-18",
53854
+ "2025-03-26",
53855
+ "2024-11-05"
53856
+ ];
53857
+ META_PROTOCOL_VERSION = "io.modelcontextprotocol/protocolVersion";
53858
+ META_CLIENT_CAPABILITIES = "io.modelcontextprotocol/clientCapabilities";
53859
+ META_CLIENT_INFO = "io.modelcontextprotocol/clientInfo";
53860
+ META_SERVER_INFO = "io.modelcontextprotocol/serverInfo";
53861
+ META_LOG_LEVEL = "io.modelcontextprotocol/logLevel";
53862
+ META_SUBSCRIPTION_ID = "io.modelcontextprotocol/subscriptionId";
53863
+ TASKS_EXTENSION = "io.modelcontextprotocol/tasks";
53864
+ ERR_HEADER_MISMATCH = -32020;
53865
+ ERR_UNSUPPORTED_PROTOCOL_VERSION = -32022;
53866
+ ERR_METHOD_NOT_FOUND = -32601;
53867
+ ERR_INVALID_PARAMS = -32602;
53868
+ ERR_INTERNAL = -32603;
53869
+ LEGACY_RESOURCE_NOT_FOUND = -32002;
53870
+ LIST_TTL_MS = 6e4;
53871
+ DISCOVER_TTL_MS = 3e5;
53872
+ CACHE_SCOPE = "private";
53873
+ CACHEABLE_METHODS = /* @__PURE__ */ new Map([
53874
+ ["server/discover", DISCOVER_TTL_MS],
53875
+ ["tools/list", LIST_TTL_MS],
53876
+ ["prompts/list", LIST_TTL_MS],
53877
+ ["resources/list", LIST_TTL_MS],
53878
+ ["resources/templates/list", LIST_TTL_MS],
53879
+ ["resources/read", LIST_TTL_MS]
53880
+ ]);
53881
+ FORWARDED_METHODS = /* @__PURE__ */ new Set([
53882
+ "tools/list",
53883
+ "tools/call",
53884
+ "resources/list",
53885
+ "resources/templates/list",
53886
+ "resources/read",
53887
+ "prompts/list",
53888
+ "prompts/get",
53889
+ "completion/complete"
53890
+ ]);
53891
+ NAME_HEADER_METHODS = /* @__PURE__ */ new Set(["tools/call", "prompts/get"]);
53892
+ MRTR_METHODS = /* @__PURE__ */ new Set(["tools/call", "prompts/get", "resources/read"]);
53893
+ LONG_RUNNING_TOOLS = /* @__PURE__ */ new Set([
53894
+ "route_nets",
53895
+ "route_diff_pair",
53896
+ "length_match_traces",
53897
+ "fab_prep",
53898
+ "fix_drc",
53899
+ "topology_optimize",
53900
+ "optimize_electrodes",
53901
+ "export_video",
53902
+ "render_sequence",
53903
+ "simulate_flow",
53904
+ "simulate_em",
53905
+ "simulate_photonics",
53906
+ "simulate_charged_particles",
53907
+ "simulate_neutron_shield",
53908
+ "simulate_lattice_gauge",
53909
+ "md_run",
53910
+ "minimize_energy",
53911
+ "homogenize_material",
53912
+ "design_material"
53913
+ ]);
53914
+ TASK_TTL_MS = 30 * 6e4;
53915
+ TASK_POLL_INTERVAL_MS = 1e3;
53916
+ MODERN_ONLY_METHODS = /* @__PURE__ */ new Set([
53917
+ "server/discover",
53918
+ "subscriptions/listen",
53919
+ "tasks/get",
53920
+ "tasks/update",
53921
+ "tasks/cancel"
53922
+ ]);
53923
+ BASE64_SENTINEL_PREFIX = "=?base64?";
53924
+ BASE64_SENTINEL_SUFFIX = "?=";
53925
+ bus = new EventEmitter();
53926
+ bus.setMaxListeners(100);
53927
+ EVT_TOOLS_CHANGED = "tools_list_changed";
53928
+ EVT_TASK = "task";
53929
+ tasks = /* @__PURE__ */ new Map();
53930
+ taskInputHooks = /* @__PURE__ */ new WeakMap();
53931
+ }
53932
+ });
53933
+
53178
53934
  // src/viewer-html.generated.ts
53179
53935
  var VIEWER_HTML;
53180
53936
  var init_viewer_html_generated = __esm({
@@ -54791,7 +55547,7 @@ var init_client = __esm({
54791
55547
  });
54792
55548
 
54793
55549
  // src/fabricate/adapters/kerf.ts
54794
- import { randomUUID as randomUUID2 } from "node:crypto";
55550
+ import { randomUUID as randomUUID3 } from "node:crypto";
54795
55551
  function kerfQuoteMode() {
54796
55552
  return process.env.KERF_QUOTE_MODE === "live" ? "live" : "scripted";
54797
55553
  }
@@ -54821,7 +55577,7 @@ function buildKerfSheetMetalIntent(p) {
54821
55577
  files: p.files,
54822
55578
  config: p.config,
54823
55579
  quantity: p.quantity,
54824
- idempotency_key: `vq_${randomUUID2()}`,
55580
+ idempotency_key: `vq_${randomUUID3()}`,
54825
55581
  ship_to: quoteShipTo(),
54826
55582
  budget_cap: { currency: "USD", amount_minor: 0 }
54827
55583
  };
@@ -55185,7 +55941,7 @@ var init_types4 = __esm({
55185
55941
  });
55186
55942
 
55187
55943
  // src/tools/order.ts
55188
- import { randomUUID as randomUUID3, createHash as createHash4 } from "node:crypto";
55944
+ import { randomUUID as randomUUID4, createHash as createHash4 } from "node:crypto";
55189
55945
  function quoteDfm(process3, metrics) {
55190
55946
  const violations = [];
55191
55947
  if (process3 !== "pcb" && !metrics.ok) {
@@ -55371,8 +56127,8 @@ async function quoteManufacturing(input, engine, store, user) {
55371
56127
  }
55372
56128
  const now = /* @__PURE__ */ new Date();
55373
56129
  const expiresAt = new Date(now.getTime() + QUOTE_TTL_MS).toISOString();
55374
- const quoteId = randomUUID3();
55375
- const orderId = randomUUID3();
56130
+ const quoteId = randomUUID4();
56131
+ const orderId = randomUUID4();
55376
56132
  const owner = ownerId(user);
55377
56133
  const quote = {
55378
56134
  quote_id: quoteId,
@@ -56734,7 +57490,7 @@ var init_clearance = __esm({
56734
57490
  });
56735
57491
 
56736
57492
  // src/tools/ordering.ts
56737
- import { randomUUID as randomUUID4 } from "node:crypto";
57493
+ import { randomUUID as randomUUID5 } from "node:crypto";
56738
57494
  function orderingEnabled() {
56739
57495
  return process.env.VCAD_FABRICATE_ORDERING === "1";
56740
57496
  }
@@ -56768,7 +57524,7 @@ async function authorizeSpend(input, store, eventStore, user, elicit) {
56768
57524
  }
56769
57525
  const now = /* @__PURE__ */ new Date();
56770
57526
  const authz = {
56771
- id: randomUUID4(),
57527
+ id: randomUUID5(),
56772
57528
  user_id: owner,
56773
57529
  quote_id: order.quote_id,
56774
57530
  kind: "one_time",
@@ -57308,7 +58064,7 @@ var init_mech_parts = __esm({
57308
58064
  });
57309
58065
 
57310
58066
  // src/tools/bom.ts
57311
- import { randomUUID as randomUUID5 } from "node:crypto";
58067
+ import { randomUUID as randomUUID6 } from "node:crypto";
57312
58068
  function ok3(payload) {
57313
58069
  return { content: [{ type: "text", text: JSON.stringify(payload, null, 2) }] };
57314
58070
  }
@@ -57379,7 +58135,7 @@ async function buildLine(args, store, owner) {
57379
58135
  if (kind !== "manufactured" && kind !== "cots") {
57380
58136
  return { error: `Line kind must be 'manufactured' or 'cots', got "${kind}".` };
57381
58137
  }
57382
- const lineId = randomUUID5().slice(0, 8);
58138
+ const lineId = randomUUID6().slice(0, 8);
57383
58139
  const explicitUnitUsd = typeof args.unit_price_usd === "number" && Number.isFinite(args.unit_price_usd) ? args.unit_price_usd : null;
57384
58140
  if (kind === "manufactured") {
57385
58141
  const name2 = str(args.name);
@@ -57540,7 +58296,7 @@ async function bomCreate(input, store, user) {
57540
58296
  const owner = ownerId(user);
57541
58297
  const now = (/* @__PURE__ */ new Date()).toISOString();
57542
58298
  const bom = {
57543
- bom_id: randomUUID5(),
58299
+ bom_id: randomUUID6(),
57544
58300
  title: str(args.title) ?? "Bill of Materials",
57545
58301
  document_id: str(args.document_id),
57546
58302
  assembly_notes: Array.isArray(args.assembly_notes) ? args.assembly_notes.map(String).filter((s) => s.trim()) : [],
@@ -71089,7 +71845,7 @@ function listEvalTasks(args) {
71089
71845
  }
71090
71846
  const tasksDir = join3(mechevalRoot, "tasks");
71091
71847
  const suiteFilter = args.suite ? String(args.suite).toUpperCase() : null;
71092
- const tasks = [];
71848
+ const tasks2 = [];
71093
71849
  for (const file of readdirSync2(tasksDir).sort()) {
71094
71850
  if (!file.endsWith(".json")) continue;
71095
71851
  try {
@@ -71097,7 +71853,7 @@ function listEvalTasks(args) {
71097
71853
  if (suiteFilter && String(raw.suite).toUpperCase() !== suiteFilter) {
71098
71854
  continue;
71099
71855
  }
71100
- tasks.push({
71856
+ tasks2.push({
71101
71857
  id: raw.id,
71102
71858
  suite: raw.suite,
71103
71859
  tier: raw.tier,
@@ -71109,7 +71865,7 @@ function listEvalTasks(args) {
71109
71865
  } catch {
71110
71866
  }
71111
71867
  }
71112
- return textResult({ count: tasks.length, tasks });
71868
+ return textResult({ count: tasks2.length, tasks: tasks2 });
71113
71869
  }
71114
71870
  async function verifyPart(args) {
71115
71871
  const documentId = String(args.document_id ?? "");
@@ -79895,7 +80651,7 @@ var init_animate = __esm({
79895
80651
 
79896
80652
  // src/server.ts
79897
80653
  import { createRequire as createRequire2 } from "node:module";
79898
- import { randomUUID as randomUUID6 } from "node:crypto";
80654
+ import { randomUUID as randomUUID7 } from "node:crypto";
79899
80655
  function getBuildInfo() {
79900
80656
  return {
79901
80657
  name: "vcad",
@@ -79908,7 +80664,7 @@ function getBuildInfo() {
79908
80664
  };
79909
80665
  }
79910
80666
  function buildInstructions(kernelPrompt) {
79911
- const header = [
80667
+ const header2 = [
79912
80668
  "vcad \u2014 parametric CAD with a real BRep kernel. Coordinate system: Z-up (X right, Y forward, Z up). Units: millimeters.",
79913
80669
  "",
79914
80670
  "Workflow: `open_document` \u2192 author \u2192 see \u2192 measure \u2192 ship.",
@@ -79926,7 +80682,7 @@ function buildInstructions(kernelPrompt) {
79926
80682
  "",
79927
80683
  "PCB workflow: `create_schematic` (declare connectivity as data via `nets`) \u2192 `place_components` \u2192 `route_nets` / `add_coil` / `add_coil_array` \u2192 `run_drc` \u2192 `validate_for_fab` \u2192 `export_gerber`. All take the `document_id` from create_schematic and mutate that session \u2014 never re-send the document. `validate_for_fab` is the single 'is this board ready?' gate (DRC + renderability + Gerber serialization + blockers, all fail-closed); `export_gerber` enforces a clean DRC by default and blocks a dirty board. `board_from_solid` turns a solid part (e.g. an enclosure or stator disc in a CAD session) into an outline polygon for `place_components`. For motors, plan the winding first with `winding_layout` (slots + poles \u2192 per-coil phase/polarity/winding-factor, as data \u2014 it touches no board), then realize it with `add_coil_array`. `run_drc` returns a summary by default (counts by rule + net-pair, worst clearance, a capped sample); pass `detail:'full'` for every violation. Surgical copper edits: `get_copper` lists existing traces/vias/zones (filtered by layer/net/bbox/kind) with the same indices `delete_trace`/`delete_via`/`delete_zone` accept \u2014 discover, then delete, without exporting the document. Where two nets must touch on purpose (wye neutral, split ground, shunt tap), declare it with `add_net_tie` \u2014 prefer a region-scoped tie (position+radius) so DRC stays honest away from the junction; `delete_net_tie` takes it back."
79928
80684
  ].join("\n");
79929
- if (!kernelPrompt) return header;
80685
+ if (!kernelPrompt) return header2;
79930
80686
  const sections = /* @__PURE__ */ new Map();
79931
80687
  for (const part of kernelPrompt.split(/^## /m).slice(1)) {
79932
80688
  const nl = part.indexOf("\n");
@@ -79937,7 +80693,7 @@ function buildInstructions(kernelPrompt) {
79937
80693
  (t2) => `## ${t2}
79938
80694
  ${sections.get(t2)}`
79939
80695
  );
79940
- return [header, ...picked].join("\n\n");
80696
+ return [header2, ...picked].join("\n\n");
79941
80697
  }
79942
80698
  function parseEnvPacks() {
79943
80699
  const env = process.env.VCAD_MCP_PACKS?.trim();
@@ -80373,7 +81129,7 @@ async function createServer(existingEngine, context = { user: null }) {
80373
81129
  ]
80374
81130
  };
80375
81131
  }
80376
- throw new Error(`Unknown resource: ${uri}`);
81132
+ throw new McpError(ErrorCode.InvalidParams, `Unknown resource: ${uri}`);
80377
81133
  });
80378
81134
  const clientHasInlineUi = () => {
80379
81135
  const caps = server.getClientCapabilities();
@@ -80724,6 +81480,7 @@ var init_server3 = __esm({
80724
81480
  init_edge_config();
80725
81481
  init_live_route();
80726
81482
  init_artifact_route();
81483
+ init_protocol_2026();
80727
81484
  init_artifact_store();
80728
81485
  init_viewer();
80729
81486
  init_notify();
@@ -80787,8 +81544,8 @@ var init_server3 = __esm({
80787
81544
  init_order_feed();
80788
81545
  init_animate();
80789
81546
  PKG_VERSION = (() => {
80790
- if ("0.9.4-main.47") {
80791
- return "0.9.4-main.47";
81547
+ if ("0.9.4-main.49") {
81548
+ return "0.9.4-main.49";
80792
81549
  }
80793
81550
  try {
80794
81551
  const req = createRequire2(import.meta.url);
@@ -80797,11 +81554,11 @@ var init_server3 = __esm({
80797
81554
  return "0.0.0";
80798
81555
  }
80799
81556
  })();
80800
- BUILD_SHA = "e8c30217ef94fd492ae13caee599b581d8334d34";
80801
- BUILD_TIME = "2026-07-26T17:13:18.437Z";
81557
+ BUILD_SHA = "40ccc073edea8494ce6e1a94ea360bf79cfc630d";
81558
+ BUILD_TIME = "2026-07-29T16:38:39.110Z";
80802
81559
  SHORT_SHA = BUILD_SHA === "unknown" ? "unknown" : BUILD_SHA.slice(0, 7);
80803
81560
  VERSION_WITH_BUILD = SHORT_SHA === "unknown" ? PKG_VERSION : `${PKG_VERSION}+${SHORT_SHA}`;
80804
- INSTANCE_ID = randomUUID6().slice(0, 8);
81561
+ INSTANCE_ID = randomUUID7().slice(0, 8);
80805
81562
  PROCESS_STARTED_AT = Date.now();
80806
81563
  configureTelemetry(getBuildInfo());
80807
81564
  warnIfSessionStoreNotDurable();
@@ -81103,12 +81860,47 @@ async function main() {
81103
81860
  const server = await createServer(void 0, { user: null });
81104
81861
  const transport = new StdioServerTransport();
81105
81862
  await server.connect(transport);
81863
+ const legacyOnMessage = transport.onmessage?.bind(transport);
81864
+ const listens = /* @__PURE__ */ new Map();
81865
+ transport.onmessage = (msg) => {
81866
+ if (!isModernMessage(msg)) {
81867
+ const m = msg;
81868
+ const cancelId = m.params?.requestId;
81869
+ if (m.method === "notifications/cancelled" && cancelId !== void 0 && listens.has(cancelId)) {
81870
+ listens.get(cancelId).close();
81871
+ listens.delete(cancelId);
81872
+ return;
81873
+ }
81874
+ legacyOnMessage?.(msg);
81875
+ return;
81876
+ }
81877
+ const method = msg.method;
81878
+ if (method === "subscriptions/listen") {
81879
+ const id = msg.id;
81880
+ const handle = handleModernListen(msg, {
81881
+ send: (m) => void transport.send(m)
81882
+ });
81883
+ if (id !== void 0) listens.set(id, handle);
81884
+ return;
81885
+ }
81886
+ void handleModernRequest(msg, {
81887
+ createServer: () => createServer(void 0, { user: null }),
81888
+ // Request-scoped notifications share the stdio channel; the SDK
81889
+ // client correlates progress via its progressToken.
81890
+ onNotification: (n) => void transport.send(n)
81891
+ }).then(({ body }) => {
81892
+ if (body !== null) {
81893
+ void transport.send(body);
81894
+ }
81895
+ });
81896
+ };
81106
81897
  }
81107
81898
  var init_index = __esm({
81108
81899
  "src/index.ts"() {
81109
81900
  "use strict";
81110
81901
  init_stdio2();
81111
81902
  init_server3();
81903
+ init_protocol_2026();
81112
81904
  console.log = (...args) => console.error(...args);
81113
81905
  main().catch((err7) => {
81114
81906
  console.error("Fatal error:", err7);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vcad/mcp",
3
- "version": "0.9.4-main.47",
3
+ "version": "0.9.4-main.49",
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": "e8c30217ef94fd492ae13caee599b581d8334d34",
23
- "builtAt": "2026-07-26T17:13:18.437Z"
22
+ "sha": "40ccc073edea8494ce6e1a94ea360bf79cfc630d",
23
+ "builtAt": "2026-07-29T16:38:39.110Z"
24
24
  }
25
25
  }
Binary file