@vcad/mcp 0.9.4-main.47 → 0.9.4-main.48
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/index.mjs +835 -44
- package/package.json +3 -3
- package/vcad_kernel_wasm_bg.wasm +0 -0
package/README.md
CHANGED
|
@@ -6,4 +6,4 @@ vcad's MCP server as a self-contained bundle (server + BRep kernel WASM).
|
|
|
6
6
|
{ "mcpServers": { "vcad": { "command": "npx", "args": ["-y", "@vcad/mcp"] } } }
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
-
Built from
|
|
9
|
+
Built from fe62ea0789005cd4b3f00777274323698634167d at 2026-07-29T16:12:24.504Z. 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 [
|
|
13737
|
-
if (!
|
|
13765
|
+
const [header2] = tokensParts;
|
|
13766
|
+
if (!header2)
|
|
13738
13767
|
return false;
|
|
13739
|
-
const parsedHeader = JSON.parse(atob(
|
|
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
|
|
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:
|
|
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(
|
|
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
|
|
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:
|
|
18933
|
+
message: errorResponse2,
|
|
18905
18934
|
timestamp: Date.now()
|
|
18906
18935
|
}, capturedTransport?.sessionId);
|
|
18907
18936
|
} else {
|
|
18908
|
-
await capturedTransport?.send(
|
|
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",
|
|
@@ -71089,7 +71158,7 @@ function listEvalTasks(args) {
|
|
|
71089
71158
|
}
|
|
71090
71159
|
const tasksDir = join3(mechevalRoot, "tasks");
|
|
71091
71160
|
const suiteFilter = args.suite ? String(args.suite).toUpperCase() : null;
|
|
71092
|
-
const
|
|
71161
|
+
const tasks2 = [];
|
|
71093
71162
|
for (const file of readdirSync2(tasksDir).sort()) {
|
|
71094
71163
|
if (!file.endsWith(".json")) continue;
|
|
71095
71164
|
try {
|
|
@@ -71097,7 +71166,7 @@ function listEvalTasks(args) {
|
|
|
71097
71166
|
if (suiteFilter && String(raw.suite).toUpperCase() !== suiteFilter) {
|
|
71098
71167
|
continue;
|
|
71099
71168
|
}
|
|
71100
|
-
|
|
71169
|
+
tasks2.push({
|
|
71101
71170
|
id: raw.id,
|
|
71102
71171
|
suite: raw.suite,
|
|
71103
71172
|
tier: raw.tier,
|
|
@@ -71109,7 +71178,7 @@ function listEvalTasks(args) {
|
|
|
71109
71178
|
} catch {
|
|
71110
71179
|
}
|
|
71111
71180
|
}
|
|
71112
|
-
return textResult({ count:
|
|
71181
|
+
return textResult({ count: tasks2.length, tasks: tasks2 });
|
|
71113
71182
|
}
|
|
71114
71183
|
async function verifyPart(args) {
|
|
71115
71184
|
const documentId = String(args.document_id ?? "");
|
|
@@ -79908,7 +79977,7 @@ function getBuildInfo() {
|
|
|
79908
79977
|
};
|
|
79909
79978
|
}
|
|
79910
79979
|
function buildInstructions(kernelPrompt) {
|
|
79911
|
-
const
|
|
79980
|
+
const header2 = [
|
|
79912
79981
|
"vcad \u2014 parametric CAD with a real BRep kernel. Coordinate system: Z-up (X right, Y forward, Z up). Units: millimeters.",
|
|
79913
79982
|
"",
|
|
79914
79983
|
"Workflow: `open_document` \u2192 author \u2192 see \u2192 measure \u2192 ship.",
|
|
@@ -79926,7 +79995,7 @@ function buildInstructions(kernelPrompt) {
|
|
|
79926
79995
|
"",
|
|
79927
79996
|
"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
79997
|
].join("\n");
|
|
79929
|
-
if (!kernelPrompt) return
|
|
79998
|
+
if (!kernelPrompt) return header2;
|
|
79930
79999
|
const sections = /* @__PURE__ */ new Map();
|
|
79931
80000
|
for (const part of kernelPrompt.split(/^## /m).slice(1)) {
|
|
79932
80001
|
const nl = part.indexOf("\n");
|
|
@@ -79937,7 +80006,7 @@ function buildInstructions(kernelPrompt) {
|
|
|
79937
80006
|
(t2) => `## ${t2}
|
|
79938
80007
|
${sections.get(t2)}`
|
|
79939
80008
|
);
|
|
79940
|
-
return [
|
|
80009
|
+
return [header2, ...picked].join("\n\n");
|
|
79941
80010
|
}
|
|
79942
80011
|
function parseEnvPacks() {
|
|
79943
80012
|
const env = process.env.VCAD_MCP_PACKS?.trim();
|
|
@@ -80373,7 +80442,7 @@ async function createServer(existingEngine, context = { user: null }) {
|
|
|
80373
80442
|
]
|
|
80374
80443
|
};
|
|
80375
80444
|
}
|
|
80376
|
-
throw new
|
|
80445
|
+
throw new McpError(ErrorCode.InvalidParams, `Unknown resource: ${uri}`);
|
|
80377
80446
|
});
|
|
80378
80447
|
const clientHasInlineUi = () => {
|
|
80379
80448
|
const caps = server.getClientCapabilities();
|
|
@@ -80787,8 +80856,8 @@ var init_server3 = __esm({
|
|
|
80787
80856
|
init_order_feed();
|
|
80788
80857
|
init_animate();
|
|
80789
80858
|
PKG_VERSION = (() => {
|
|
80790
|
-
if ("0.9.4-main.
|
|
80791
|
-
return "0.9.4-main.
|
|
80859
|
+
if ("0.9.4-main.48") {
|
|
80860
|
+
return "0.9.4-main.48";
|
|
80792
80861
|
}
|
|
80793
80862
|
try {
|
|
80794
80863
|
const req = createRequire2(import.meta.url);
|
|
@@ -80797,8 +80866,8 @@ var init_server3 = __esm({
|
|
|
80797
80866
|
return "0.0.0";
|
|
80798
80867
|
}
|
|
80799
80868
|
})();
|
|
80800
|
-
BUILD_SHA = "
|
|
80801
|
-
BUILD_TIME = "2026-07-
|
|
80869
|
+
BUILD_SHA = "fe62ea0789005cd4b3f00777274323698634167d";
|
|
80870
|
+
BUILD_TIME = "2026-07-29T16:12:24.504Z";
|
|
80802
80871
|
SHORT_SHA = BUILD_SHA === "unknown" ? "unknown" : BUILD_SHA.slice(0, 7);
|
|
80803
80872
|
VERSION_WITH_BUILD = SHORT_SHA === "unknown" ? PKG_VERSION : `${PKG_VERSION}+${SHORT_SHA}`;
|
|
80804
80873
|
INSTANCE_ID = randomUUID6().slice(0, 8);
|
|
@@ -81097,18 +81166,740 @@ var init_server3 = __esm({
|
|
|
81097
81166
|
}
|
|
81098
81167
|
});
|
|
81099
81168
|
|
|
81169
|
+
// node_modules/@modelcontextprotocol/sdk/dist/esm/inMemory.js
|
|
81170
|
+
var InMemoryTransport;
|
|
81171
|
+
var init_inMemory = __esm({
|
|
81172
|
+
"node_modules/@modelcontextprotocol/sdk/dist/esm/inMemory.js"() {
|
|
81173
|
+
InMemoryTransport = class _InMemoryTransport {
|
|
81174
|
+
constructor() {
|
|
81175
|
+
this._messageQueue = [];
|
|
81176
|
+
}
|
|
81177
|
+
/**
|
|
81178
|
+
* 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.
|
|
81179
|
+
*/
|
|
81180
|
+
static createLinkedPair() {
|
|
81181
|
+
const clientTransport = new _InMemoryTransport();
|
|
81182
|
+
const serverTransport = new _InMemoryTransport();
|
|
81183
|
+
clientTransport._otherTransport = serverTransport;
|
|
81184
|
+
serverTransport._otherTransport = clientTransport;
|
|
81185
|
+
return [clientTransport, serverTransport];
|
|
81186
|
+
}
|
|
81187
|
+
async start() {
|
|
81188
|
+
while (this._messageQueue.length > 0) {
|
|
81189
|
+
const queuedMessage = this._messageQueue.shift();
|
|
81190
|
+
this.onmessage?.(queuedMessage.message, queuedMessage.extra);
|
|
81191
|
+
}
|
|
81192
|
+
}
|
|
81193
|
+
async close() {
|
|
81194
|
+
const other = this._otherTransport;
|
|
81195
|
+
this._otherTransport = void 0;
|
|
81196
|
+
await other?.close();
|
|
81197
|
+
this.onclose?.();
|
|
81198
|
+
}
|
|
81199
|
+
/**
|
|
81200
|
+
* Sends a message with optional auth info.
|
|
81201
|
+
* This is useful for testing authentication scenarios.
|
|
81202
|
+
*/
|
|
81203
|
+
async send(message, options) {
|
|
81204
|
+
if (!this._otherTransport) {
|
|
81205
|
+
throw new Error("Not connected");
|
|
81206
|
+
}
|
|
81207
|
+
if (this._otherTransport.onmessage) {
|
|
81208
|
+
this._otherTransport.onmessage(message, { authInfo: options?.authInfo });
|
|
81209
|
+
} else {
|
|
81210
|
+
this._otherTransport._messageQueue.push({ message, extra: { authInfo: options?.authInfo } });
|
|
81211
|
+
}
|
|
81212
|
+
}
|
|
81213
|
+
};
|
|
81214
|
+
}
|
|
81215
|
+
});
|
|
81216
|
+
|
|
81217
|
+
// src/protocol-2026.ts
|
|
81218
|
+
import { EventEmitter } from "node:events";
|
|
81219
|
+
import { randomUUID as randomUUID7 } from "node:crypto";
|
|
81220
|
+
function header(headers, name) {
|
|
81221
|
+
if (!headers) return void 0;
|
|
81222
|
+
const lower = name.toLowerCase();
|
|
81223
|
+
for (const [k, v] of Object.entries(headers)) {
|
|
81224
|
+
if (k.toLowerCase() !== lower) continue;
|
|
81225
|
+
return Array.isArray(v) ? v[0] : v;
|
|
81226
|
+
}
|
|
81227
|
+
return void 0;
|
|
81228
|
+
}
|
|
81229
|
+
function metaOf(msg) {
|
|
81230
|
+
const params = msg?.params;
|
|
81231
|
+
const meta = params?._meta;
|
|
81232
|
+
return meta && typeof meta === "object" ? meta : void 0;
|
|
81233
|
+
}
|
|
81234
|
+
function isModernMessage(msg, headers) {
|
|
81235
|
+
const first = Array.isArray(msg) ? msg[0] : msg;
|
|
81236
|
+
if (!first || typeof first !== "object") return false;
|
|
81237
|
+
const method = first.method;
|
|
81238
|
+
if (typeof method === "string" && MODERN_ONLY_METHODS.has(method)) return true;
|
|
81239
|
+
const declared = metaOf(first)?.[META_PROTOCOL_VERSION];
|
|
81240
|
+
if (typeof declared === "string") return true;
|
|
81241
|
+
return header(headers, "mcp-method") !== void 0;
|
|
81242
|
+
}
|
|
81243
|
+
function decodeHeaderValue(value) {
|
|
81244
|
+
if (!value.startsWith(BASE64_SENTINEL_PREFIX) || !value.endsWith(BASE64_SENTINEL_SUFFIX) || value.length < BASE64_SENTINEL_PREFIX.length + BASE64_SENTINEL_SUFFIX.length) {
|
|
81245
|
+
return value;
|
|
81246
|
+
}
|
|
81247
|
+
const encoded = value.slice(
|
|
81248
|
+
BASE64_SENTINEL_PREFIX.length,
|
|
81249
|
+
value.length - BASE64_SENTINEL_SUFFIX.length
|
|
81250
|
+
);
|
|
81251
|
+
try {
|
|
81252
|
+
return Buffer.from(encoded, "base64").toString("utf-8");
|
|
81253
|
+
} catch {
|
|
81254
|
+
return value;
|
|
81255
|
+
}
|
|
81256
|
+
}
|
|
81257
|
+
function errorResponse(id, code, message, data) {
|
|
81258
|
+
return {
|
|
81259
|
+
jsonrpc: "2.0",
|
|
81260
|
+
id: id ?? null,
|
|
81261
|
+
error: data === void 0 ? { code, message } : { code, message, data }
|
|
81262
|
+
};
|
|
81263
|
+
}
|
|
81264
|
+
function unsupportedVersion(id, requested) {
|
|
81265
|
+
return {
|
|
81266
|
+
status: 400,
|
|
81267
|
+
body: errorResponse(
|
|
81268
|
+
id,
|
|
81269
|
+
ERR_UNSUPPORTED_PROTOCOL_VERSION,
|
|
81270
|
+
"Unsupported protocol version",
|
|
81271
|
+
{ supported: SUPPORTED_PROTOCOL_VERSIONS2, requested }
|
|
81272
|
+
)
|
|
81273
|
+
};
|
|
81274
|
+
}
|
|
81275
|
+
function headerMismatch(id, message) {
|
|
81276
|
+
return {
|
|
81277
|
+
status: 400,
|
|
81278
|
+
body: errorResponse(id, ERR_HEADER_MISMATCH, `Header mismatch: ${message}`)
|
|
81279
|
+
};
|
|
81280
|
+
}
|
|
81281
|
+
function pruneTasks() {
|
|
81282
|
+
const now = Date.now();
|
|
81283
|
+
for (const [id, t2] of tasks) {
|
|
81284
|
+
if (t2.expiresAt <= now) {
|
|
81285
|
+
t2.dispose?.();
|
|
81286
|
+
tasks.delete(id);
|
|
81287
|
+
}
|
|
81288
|
+
}
|
|
81289
|
+
}
|
|
81290
|
+
function touchTask(t2, status, message) {
|
|
81291
|
+
if (status) t2.status = status;
|
|
81292
|
+
if (message !== void 0) t2.statusMessage = message;
|
|
81293
|
+
t2.lastUpdatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
81294
|
+
bus.emit(EVT_TASK, detailedTask(t2));
|
|
81295
|
+
}
|
|
81296
|
+
function detailedTask(t2) {
|
|
81297
|
+
const base = {
|
|
81298
|
+
taskId: t2.taskId,
|
|
81299
|
+
status: t2.status,
|
|
81300
|
+
createdAt: t2.createdAt,
|
|
81301
|
+
lastUpdatedAt: t2.lastUpdatedAt,
|
|
81302
|
+
ttlMs: TASK_TTL_MS,
|
|
81303
|
+
pollIntervalMs: TASK_POLL_INTERVAL_MS
|
|
81304
|
+
};
|
|
81305
|
+
if (t2.statusMessage !== void 0) base.statusMessage = t2.statusMessage;
|
|
81306
|
+
if (t2.status === "input_required") base.inputRequests = t2.inputRequests;
|
|
81307
|
+
if (t2.status === "completed") base.result = t2.result ?? {};
|
|
81308
|
+
if (t2.status === "failed") base.error = t2.error ?? { code: ERR_INTERNAL, message: "unknown" };
|
|
81309
|
+
return base;
|
|
81310
|
+
}
|
|
81311
|
+
async function openBridge(createServer2, clientInfo, clientCapabilities, hooks) {
|
|
81312
|
+
const [clientSide, serverSide] = InMemoryTransport.createLinkedPair();
|
|
81313
|
+
let nextId3 = 1;
|
|
81314
|
+
const pending2 = /* @__PURE__ */ new Map();
|
|
81315
|
+
clientSide.onmessage = (msg) => {
|
|
81316
|
+
const m = msg;
|
|
81317
|
+
const id = m.id;
|
|
81318
|
+
if (id !== void 0 && ("result" in m || "error" in m)) {
|
|
81319
|
+
pending2.get(id)?.(m);
|
|
81320
|
+
pending2.delete(id);
|
|
81321
|
+
return;
|
|
81322
|
+
}
|
|
81323
|
+
if (id !== void 0 && typeof m.method === "string") {
|
|
81324
|
+
void hooks.onServerRequest(
|
|
81325
|
+
m.method,
|
|
81326
|
+
m.params ?? {}
|
|
81327
|
+
).then(
|
|
81328
|
+
(result) => clientSide.send({ jsonrpc: "2.0", id, result })
|
|
81329
|
+
).catch(
|
|
81330
|
+
() => clientSide.send({
|
|
81331
|
+
jsonrpc: "2.0",
|
|
81332
|
+
id,
|
|
81333
|
+
error: { code: ERR_INTERNAL, message: "input bridge failed" }
|
|
81334
|
+
})
|
|
81335
|
+
);
|
|
81336
|
+
return;
|
|
81337
|
+
}
|
|
81338
|
+
if (typeof m.method === "string" && id === void 0) {
|
|
81339
|
+
hooks.onNotification?.(m);
|
|
81340
|
+
}
|
|
81341
|
+
};
|
|
81342
|
+
await clientSide.start();
|
|
81343
|
+
const server = await createServer2();
|
|
81344
|
+
await server.connect(serverSide);
|
|
81345
|
+
const rpc = (method, params) => {
|
|
81346
|
+
const id = nextId3++;
|
|
81347
|
+
return new Promise((resolve5) => {
|
|
81348
|
+
pending2.set(id, (m) => resolve5(m));
|
|
81349
|
+
void clientSide.send({
|
|
81350
|
+
jsonrpc: "2.0",
|
|
81351
|
+
id,
|
|
81352
|
+
method,
|
|
81353
|
+
params: params ?? {}
|
|
81354
|
+
});
|
|
81355
|
+
});
|
|
81356
|
+
};
|
|
81357
|
+
const init2 = await rpc("initialize", {
|
|
81358
|
+
protocolVersion: BRIDGE_VERSION,
|
|
81359
|
+
capabilities: clientCapabilities,
|
|
81360
|
+
clientInfo
|
|
81361
|
+
});
|
|
81362
|
+
await clientSide.send({
|
|
81363
|
+
jsonrpc: "2.0",
|
|
81364
|
+
method: "notifications/initialized"
|
|
81365
|
+
});
|
|
81366
|
+
return {
|
|
81367
|
+
rpc,
|
|
81368
|
+
handshake: init2.result ?? {},
|
|
81369
|
+
close: async () => {
|
|
81370
|
+
pending2.clear();
|
|
81371
|
+
await server.close().catch(() => {
|
|
81372
|
+
});
|
|
81373
|
+
await clientSide.close().catch(() => {
|
|
81374
|
+
});
|
|
81375
|
+
}
|
|
81376
|
+
};
|
|
81377
|
+
}
|
|
81378
|
+
async function handleModernRequest(msg, opts) {
|
|
81379
|
+
if (Array.isArray(msg)) {
|
|
81380
|
+
return {
|
|
81381
|
+
status: 400,
|
|
81382
|
+
body: errorResponse(
|
|
81383
|
+
null,
|
|
81384
|
+
ERR_INVALID_PARAMS,
|
|
81385
|
+
"Batched requests are not supported; send one JSON-RPC message per POST"
|
|
81386
|
+
)
|
|
81387
|
+
};
|
|
81388
|
+
}
|
|
81389
|
+
if (!msg || typeof msg !== "object") {
|
|
81390
|
+
return {
|
|
81391
|
+
status: 400,
|
|
81392
|
+
body: errorResponse(null, ERR_INVALID_PARAMS, "Malformed JSON-RPC message")
|
|
81393
|
+
};
|
|
81394
|
+
}
|
|
81395
|
+
const req = msg;
|
|
81396
|
+
const id = req.id;
|
|
81397
|
+
const method = req.method;
|
|
81398
|
+
const params = req.params ?? {};
|
|
81399
|
+
const meta = metaOf(req) ?? {};
|
|
81400
|
+
if (id === void 0 || id === null) return { status: 202, body: null };
|
|
81401
|
+
if (typeof method !== "string") {
|
|
81402
|
+
return {
|
|
81403
|
+
status: 400,
|
|
81404
|
+
body: errorResponse(id, ERR_INVALID_PARAMS, "Missing JSON-RPC method")
|
|
81405
|
+
};
|
|
81406
|
+
}
|
|
81407
|
+
const bodyVersion = meta[META_PROTOCOL_VERSION];
|
|
81408
|
+
const headerVersion = header(opts.headers, "mcp-protocol-version");
|
|
81409
|
+
if (typeof bodyVersion === "string" && headerVersion !== void 0 && headerVersion !== bodyVersion) {
|
|
81410
|
+
return headerMismatch(
|
|
81411
|
+
id,
|
|
81412
|
+
`MCP-Protocol-Version header value '${headerVersion}' does not match body value '${bodyVersion}'`
|
|
81413
|
+
);
|
|
81414
|
+
}
|
|
81415
|
+
const version2 = typeof bodyVersion === "string" ? bodyVersion : headerVersion;
|
|
81416
|
+
if (version2 === void 0) {
|
|
81417
|
+
if (method !== "server/discover") {
|
|
81418
|
+
return unsupportedVersion(id, "(unspecified)");
|
|
81419
|
+
}
|
|
81420
|
+
} else if (version2 !== PROTOCOL_2026) {
|
|
81421
|
+
return unsupportedVersion(id, version2);
|
|
81422
|
+
}
|
|
81423
|
+
if (opts.headers) {
|
|
81424
|
+
const methodHeader = header(opts.headers, "mcp-method");
|
|
81425
|
+
if (methodHeader === void 0) {
|
|
81426
|
+
return headerMismatch(id, "required header Mcp-Method is missing");
|
|
81427
|
+
}
|
|
81428
|
+
if (methodHeader !== method) {
|
|
81429
|
+
return headerMismatch(
|
|
81430
|
+
id,
|
|
81431
|
+
`Mcp-Method header value '${methodHeader}' does not match body value '${method}'`
|
|
81432
|
+
);
|
|
81433
|
+
}
|
|
81434
|
+
const expectedName = NAME_HEADER_METHODS.has(method) ? params.name : method === "resources/read" ? params.uri : void 0;
|
|
81435
|
+
if (typeof expectedName === "string") {
|
|
81436
|
+
const nameHeader = header(opts.headers, "mcp-name");
|
|
81437
|
+
if (nameHeader === void 0) {
|
|
81438
|
+
return headerMismatch(id, "required header Mcp-Name is missing");
|
|
81439
|
+
}
|
|
81440
|
+
if (decodeHeaderValue(nameHeader) !== expectedName) {
|
|
81441
|
+
return headerMismatch(
|
|
81442
|
+
id,
|
|
81443
|
+
`Mcp-Name header value does not match body value '${expectedName}'`
|
|
81444
|
+
);
|
|
81445
|
+
}
|
|
81446
|
+
}
|
|
81447
|
+
}
|
|
81448
|
+
if (method === "tasks/get" || method === "tasks/update" || method === "tasks/cancel") {
|
|
81449
|
+
return handleTaskMethod(id, method, params);
|
|
81450
|
+
}
|
|
81451
|
+
if (method === "initialize") {
|
|
81452
|
+
return {
|
|
81453
|
+
status: 404,
|
|
81454
|
+
body: errorResponse(
|
|
81455
|
+
id,
|
|
81456
|
+
ERR_METHOD_NOT_FOUND,
|
|
81457
|
+
`'initialize' was removed in protocol ${PROTOCOL_2026}; use 'server/discover'. This server supports: ${SUPPORTED_PROTOCOL_VERSIONS2.join(", ")}`
|
|
81458
|
+
)
|
|
81459
|
+
};
|
|
81460
|
+
}
|
|
81461
|
+
if (method !== "server/discover" && !FORWARDED_METHODS.has(method)) {
|
|
81462
|
+
return {
|
|
81463
|
+
status: 404,
|
|
81464
|
+
body: errorResponse(id, ERR_METHOD_NOT_FOUND, `Unknown method '${method}'`)
|
|
81465
|
+
};
|
|
81466
|
+
}
|
|
81467
|
+
const rawClientInfo = meta[META_CLIENT_INFO];
|
|
81468
|
+
const infoObj = rawClientInfo && typeof rawClientInfo === "object" ? rawClientInfo : {};
|
|
81469
|
+
const clientInfo = {
|
|
81470
|
+
name: typeof infoObj.name === "string" ? infoObj.name : "unknown",
|
|
81471
|
+
version: typeof infoObj.version === "string" ? infoObj.version : "0.0.0"
|
|
81472
|
+
};
|
|
81473
|
+
const rawCaps = meta[META_CLIENT_CAPABILITIES];
|
|
81474
|
+
const clientCapabilities = rawCaps && typeof rawCaps === "object" ? rawCaps : {};
|
|
81475
|
+
const wantsLogs = typeof meta[META_LOG_LEVEL] === "string";
|
|
81476
|
+
const inputResponses = params.inputResponses && typeof params.inputResponses === "object" ? params.inputResponses : {};
|
|
81477
|
+
const extCaps = clientCapabilities.extensions ?? {};
|
|
81478
|
+
const asTask = method === "tools/call" && TASKS_EXTENSION in extCaps && typeof params.name === "string" && LONG_RUNNING_TOOLS.has(params.name);
|
|
81479
|
+
let inputSeq = 0;
|
|
81480
|
+
const capturedInputRequests = {};
|
|
81481
|
+
const onServerRequest = async (reqMethod, reqParams) => {
|
|
81482
|
+
if (reqMethod !== "elicitation/create") {
|
|
81483
|
+
throw new Error(`unsupported server-initiated request ${reqMethod}`);
|
|
81484
|
+
}
|
|
81485
|
+
if (bridgeRef) {
|
|
81486
|
+
const parked = taskElicitation(bridgeRef, reqParams);
|
|
81487
|
+
if (parked) return parked;
|
|
81488
|
+
}
|
|
81489
|
+
const key = `input_${++inputSeq}`;
|
|
81490
|
+
const provided = inputResponses[key];
|
|
81491
|
+
if (provided) return provided;
|
|
81492
|
+
if (!MRTR_METHODS.has(method)) return { action: "cancel" };
|
|
81493
|
+
const { elicitationId: _drop, ...cleanParams } = reqParams;
|
|
81494
|
+
capturedInputRequests[key] = {
|
|
81495
|
+
method: "elicitation/create",
|
|
81496
|
+
params: cleanParams
|
|
81497
|
+
};
|
|
81498
|
+
return { action: "cancel" };
|
|
81499
|
+
};
|
|
81500
|
+
let bridge;
|
|
81501
|
+
let bridgeRef;
|
|
81502
|
+
try {
|
|
81503
|
+
bridge = bridgeRef = await openBridge(opts.createServer, clientInfo, clientCapabilities, {
|
|
81504
|
+
onNotification: (n) => {
|
|
81505
|
+
const nm = n.method;
|
|
81506
|
+
if (nm === "notifications/message" && !wantsLogs) return;
|
|
81507
|
+
opts.onNotification?.(n);
|
|
81508
|
+
},
|
|
81509
|
+
onServerRequest
|
|
81510
|
+
});
|
|
81511
|
+
if (method === "server/discover") {
|
|
81512
|
+
const info = bridge.handshake;
|
|
81513
|
+
const capabilities = { ...info.capabilities ?? {} };
|
|
81514
|
+
capabilities.extensions = {
|
|
81515
|
+
...capabilities.extensions ?? {},
|
|
81516
|
+
[TASKS_EXTENSION]: {}
|
|
81517
|
+
};
|
|
81518
|
+
const result2 = {
|
|
81519
|
+
resultType: "complete",
|
|
81520
|
+
supportedVersions: SUPPORTED_PROTOCOL_VERSIONS2,
|
|
81521
|
+
capabilities,
|
|
81522
|
+
_meta: { [META_SERVER_INFO]: info.serverInfo ?? {} }
|
|
81523
|
+
};
|
|
81524
|
+
if (info.instructions) result2.instructions = info.instructions;
|
|
81525
|
+
decorateCache(result2, "server/discover");
|
|
81526
|
+
return { status: 200, body: { jsonrpc: "2.0", id, result: result2 } };
|
|
81527
|
+
}
|
|
81528
|
+
const serverInfo = bridge.handshake.serverInfo ?? {
|
|
81529
|
+
name: "vcad",
|
|
81530
|
+
version: "0.0.0"
|
|
81531
|
+
};
|
|
81532
|
+
const forwarded = stripModernFields(params);
|
|
81533
|
+
if (asTask) {
|
|
81534
|
+
pruneTasks();
|
|
81535
|
+
const record2 = createTaskRecord(params.name);
|
|
81536
|
+
const owned = bridge;
|
|
81537
|
+
bridge = void 0;
|
|
81538
|
+
runTaskInBackground(record2, owned, method, forwarded, inputResponses);
|
|
81539
|
+
const result2 = {
|
|
81540
|
+
resultType: "task",
|
|
81541
|
+
...detailedTask(record2),
|
|
81542
|
+
_meta: { [META_SERVER_INFO]: serverInfo }
|
|
81543
|
+
};
|
|
81544
|
+
return { status: 200, body: { jsonrpc: "2.0", id, result: result2 } };
|
|
81545
|
+
}
|
|
81546
|
+
const reply = await bridge.rpc(method, forwarded);
|
|
81547
|
+
if (Object.keys(capturedInputRequests).length > 0) {
|
|
81548
|
+
const result2 = {
|
|
81549
|
+
resultType: "input_required",
|
|
81550
|
+
inputRequests: capturedInputRequests,
|
|
81551
|
+
_meta: { [META_SERVER_INFO]: serverInfo }
|
|
81552
|
+
};
|
|
81553
|
+
return { status: 200, body: { jsonrpc: "2.0", id, result: result2 } };
|
|
81554
|
+
}
|
|
81555
|
+
if (reply.error) {
|
|
81556
|
+
const code = reply.error.code === LEGACY_RESOURCE_NOT_FOUND ? ERR_INVALID_PARAMS : reply.error.code;
|
|
81557
|
+
return {
|
|
81558
|
+
status: code === ERR_METHOD_NOT_FOUND ? 404 : 200,
|
|
81559
|
+
body: errorResponse(id, code, reply.error.message, reply.error.data)
|
|
81560
|
+
};
|
|
81561
|
+
}
|
|
81562
|
+
const result = { ...reply.result ?? {} };
|
|
81563
|
+
result.resultType = "complete";
|
|
81564
|
+
const resultMeta = result._meta && typeof result._meta === "object" ? { ...result._meta } : {};
|
|
81565
|
+
resultMeta[META_SERVER_INFO] = serverInfo;
|
|
81566
|
+
result._meta = resultMeta;
|
|
81567
|
+
decorateCache(result, method);
|
|
81568
|
+
if (method === "tools/call" && params.name === "set_tool_packs") {
|
|
81569
|
+
bus.emit(EVT_TOOLS_CHANGED);
|
|
81570
|
+
}
|
|
81571
|
+
return { status: 200, body: { jsonrpc: "2.0", id, result } };
|
|
81572
|
+
} catch (err7) {
|
|
81573
|
+
return {
|
|
81574
|
+
status: 500,
|
|
81575
|
+
body: errorResponse(
|
|
81576
|
+
id,
|
|
81577
|
+
ERR_INTERNAL,
|
|
81578
|
+
err7 instanceof Error ? err7.message : String(err7)
|
|
81579
|
+
)
|
|
81580
|
+
};
|
|
81581
|
+
} finally {
|
|
81582
|
+
await bridge?.close();
|
|
81583
|
+
}
|
|
81584
|
+
}
|
|
81585
|
+
function decorateCache(result, method) {
|
|
81586
|
+
const ttl = CACHEABLE_METHODS.get(method);
|
|
81587
|
+
if (ttl === void 0) return;
|
|
81588
|
+
result.ttlMs = ttl;
|
|
81589
|
+
result.cacheScope = CACHE_SCOPE;
|
|
81590
|
+
}
|
|
81591
|
+
function stripModernFields(params) {
|
|
81592
|
+
const out = { ...params };
|
|
81593
|
+
delete out.inputResponses;
|
|
81594
|
+
delete out.requestState;
|
|
81595
|
+
const meta = out._meta;
|
|
81596
|
+
if (meta && typeof meta === "object") {
|
|
81597
|
+
const rest = { ...meta };
|
|
81598
|
+
delete rest[META_PROTOCOL_VERSION];
|
|
81599
|
+
delete rest[META_CLIENT_CAPABILITIES];
|
|
81600
|
+
delete rest[META_CLIENT_INFO];
|
|
81601
|
+
delete rest[META_LOG_LEVEL];
|
|
81602
|
+
if (Object.keys(rest).length === 0) delete out._meta;
|
|
81603
|
+
else out._meta = rest;
|
|
81604
|
+
}
|
|
81605
|
+
return out;
|
|
81606
|
+
}
|
|
81607
|
+
function createTaskRecord(toolName) {
|
|
81608
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
81609
|
+
const record2 = {
|
|
81610
|
+
taskId: `task_${randomUUID7().slice(0, 13)}`,
|
|
81611
|
+
status: "working",
|
|
81612
|
+
statusMessage: `running ${toolName}`,
|
|
81613
|
+
createdAt: now,
|
|
81614
|
+
lastUpdatedAt: now,
|
|
81615
|
+
expiresAt: Date.now() + TASK_TTL_MS,
|
|
81616
|
+
toolName,
|
|
81617
|
+
inputRequests: {},
|
|
81618
|
+
pendingInputs: /* @__PURE__ */ new Map(),
|
|
81619
|
+
cancelRequested: false
|
|
81620
|
+
};
|
|
81621
|
+
tasks.set(record2.taskId, record2);
|
|
81622
|
+
return record2;
|
|
81623
|
+
}
|
|
81624
|
+
function runTaskInBackground(record2, bridge, method, forwarded, presetResponses) {
|
|
81625
|
+
record2.dispose = () => {
|
|
81626
|
+
void bridge.close();
|
|
81627
|
+
};
|
|
81628
|
+
taskInputHooks.set(bridge, { record: record2, presetResponses, seq: 0 });
|
|
81629
|
+
void bridge.rpc(method, forwarded).then((reply) => {
|
|
81630
|
+
if (record2.status === "cancelled") return;
|
|
81631
|
+
if (reply.error) {
|
|
81632
|
+
record2.error = reply.error;
|
|
81633
|
+
touchTask(record2, "failed", reply.error.message);
|
|
81634
|
+
} else {
|
|
81635
|
+
const result = { ...reply.result ?? {} };
|
|
81636
|
+
result.resultType = "complete";
|
|
81637
|
+
record2.result = result;
|
|
81638
|
+
touchTask(record2, "completed", `${record2.toolName} finished`);
|
|
81639
|
+
}
|
|
81640
|
+
}).catch((err7) => {
|
|
81641
|
+
record2.error = {
|
|
81642
|
+
code: ERR_INTERNAL,
|
|
81643
|
+
message: err7 instanceof Error ? err7.message : String(err7)
|
|
81644
|
+
};
|
|
81645
|
+
touchTask(record2, "failed");
|
|
81646
|
+
}).finally(() => {
|
|
81647
|
+
taskInputHooks.delete(bridge);
|
|
81648
|
+
void bridge.close();
|
|
81649
|
+
record2.dispose = void 0;
|
|
81650
|
+
});
|
|
81651
|
+
}
|
|
81652
|
+
function taskElicitation(bridge, reqParams) {
|
|
81653
|
+
const ctx = taskInputHooks.get(bridge);
|
|
81654
|
+
if (!ctx) return void 0;
|
|
81655
|
+
const { record: record2, presetResponses } = ctx;
|
|
81656
|
+
const key = `input_${++ctx.seq}`;
|
|
81657
|
+
const preset = presetResponses[key];
|
|
81658
|
+
if (preset) return Promise.resolve(preset);
|
|
81659
|
+
if (record2.cancelRequested) return Promise.resolve({ action: "cancel" });
|
|
81660
|
+
const { elicitationId: _drop, ...cleanParams } = reqParams;
|
|
81661
|
+
record2.inputRequests[key] = {
|
|
81662
|
+
method: "elicitation/create",
|
|
81663
|
+
params: cleanParams
|
|
81664
|
+
};
|
|
81665
|
+
return new Promise((resolve5) => {
|
|
81666
|
+
record2.pendingInputs.set(key, resolve5);
|
|
81667
|
+
touchTask(record2, "input_required", "waiting for client input");
|
|
81668
|
+
});
|
|
81669
|
+
}
|
|
81670
|
+
function handleTaskMethod(id, method, params) {
|
|
81671
|
+
pruneTasks();
|
|
81672
|
+
const taskId = typeof params.taskId === "string" ? params.taskId : "";
|
|
81673
|
+
const record2 = tasks.get(taskId);
|
|
81674
|
+
if (!record2) {
|
|
81675
|
+
return {
|
|
81676
|
+
status: 200,
|
|
81677
|
+
body: errorResponse(
|
|
81678
|
+
id,
|
|
81679
|
+
ERR_INVALID_PARAMS,
|
|
81680
|
+
`Unknown taskId '${taskId}' \u2014 expired, cancelled long ago, or from a previous server instance`
|
|
81681
|
+
)
|
|
81682
|
+
};
|
|
81683
|
+
}
|
|
81684
|
+
if (method === "tasks/get") {
|
|
81685
|
+
const result = { resultType: "complete", ...detailedTask(record2) };
|
|
81686
|
+
return { status: 200, body: { jsonrpc: "2.0", id, result } };
|
|
81687
|
+
}
|
|
81688
|
+
if (method === "tasks/update") {
|
|
81689
|
+
const responses = params.inputResponses && typeof params.inputResponses === "object" ? params.inputResponses : {};
|
|
81690
|
+
for (const [key, response] of Object.entries(responses)) {
|
|
81691
|
+
const resolve5 = record2.pendingInputs.get(key);
|
|
81692
|
+
if (!resolve5) continue;
|
|
81693
|
+
record2.pendingInputs.delete(key);
|
|
81694
|
+
delete record2.inputRequests[key];
|
|
81695
|
+
resolve5(response);
|
|
81696
|
+
}
|
|
81697
|
+
if (record2.status === "input_required" && Object.keys(record2.inputRequests).length === 0) {
|
|
81698
|
+
touchTask(record2, "working", `running ${record2.toolName}`);
|
|
81699
|
+
}
|
|
81700
|
+
return {
|
|
81701
|
+
status: 200,
|
|
81702
|
+
body: { jsonrpc: "2.0", id, result: { resultType: "complete" } }
|
|
81703
|
+
};
|
|
81704
|
+
}
|
|
81705
|
+
record2.cancelRequested = true;
|
|
81706
|
+
if (record2.status === "working" || record2.status === "input_required") {
|
|
81707
|
+
for (const [key, resolve5] of record2.pendingInputs) {
|
|
81708
|
+
record2.pendingInputs.delete(key);
|
|
81709
|
+
delete record2.inputRequests[key];
|
|
81710
|
+
resolve5({ action: "cancel" });
|
|
81711
|
+
}
|
|
81712
|
+
touchTask(record2, "cancelled", "cancelled by client");
|
|
81713
|
+
}
|
|
81714
|
+
return {
|
|
81715
|
+
status: 200,
|
|
81716
|
+
body: { jsonrpc: "2.0", id, result: { resultType: "complete" } }
|
|
81717
|
+
};
|
|
81718
|
+
}
|
|
81719
|
+
function handleModernListen(msg, sink) {
|
|
81720
|
+
const req = msg;
|
|
81721
|
+
const subscriptionId = req.id ?? null;
|
|
81722
|
+
const filter = req.params?.notifications && typeof req.params.notifications === "object" ? req.params.notifications : {};
|
|
81723
|
+
const honored = {};
|
|
81724
|
+
const listeners = [];
|
|
81725
|
+
if (filter.toolsListChanged === true) {
|
|
81726
|
+
honored.toolsListChanged = true;
|
|
81727
|
+
const onChange = () => sink.send({
|
|
81728
|
+
jsonrpc: "2.0",
|
|
81729
|
+
method: "notifications/tools/list_changed",
|
|
81730
|
+
params: { _meta: { [META_SUBSCRIPTION_ID]: subscriptionId } }
|
|
81731
|
+
});
|
|
81732
|
+
bus.on(EVT_TOOLS_CHANGED, onChange);
|
|
81733
|
+
listeners.push(() => bus.off(EVT_TOOLS_CHANGED, onChange));
|
|
81734
|
+
}
|
|
81735
|
+
const taskIds = Array.isArray(filter.taskIds) ? filter.taskIds.filter((t2) => typeof t2 === "string") : [];
|
|
81736
|
+
if (taskIds.length > 0) {
|
|
81737
|
+
honored.taskIds = taskIds;
|
|
81738
|
+
const wanted = new Set(taskIds);
|
|
81739
|
+
const onTask = (task) => {
|
|
81740
|
+
if (!wanted.has(task.taskId)) return;
|
|
81741
|
+
sink.send({
|
|
81742
|
+
jsonrpc: "2.0",
|
|
81743
|
+
method: "notifications/tasks",
|
|
81744
|
+
params: { _meta: { [META_SUBSCRIPTION_ID]: subscriptionId }, ...task }
|
|
81745
|
+
});
|
|
81746
|
+
};
|
|
81747
|
+
bus.on(EVT_TASK, onTask);
|
|
81748
|
+
listeners.push(() => bus.off(EVT_TASK, onTask));
|
|
81749
|
+
}
|
|
81750
|
+
sink.send({
|
|
81751
|
+
jsonrpc: "2.0",
|
|
81752
|
+
method: "notifications/subscriptions/acknowledged",
|
|
81753
|
+
params: {
|
|
81754
|
+
_meta: { [META_SUBSCRIPTION_ID]: subscriptionId },
|
|
81755
|
+
notifications: honored
|
|
81756
|
+
}
|
|
81757
|
+
});
|
|
81758
|
+
return {
|
|
81759
|
+
close: () => {
|
|
81760
|
+
for (const off of listeners) off();
|
|
81761
|
+
listeners.length = 0;
|
|
81762
|
+
}
|
|
81763
|
+
};
|
|
81764
|
+
}
|
|
81765
|
+
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;
|
|
81766
|
+
var init_protocol_2026 = __esm({
|
|
81767
|
+
"src/protocol-2026.ts"() {
|
|
81768
|
+
"use strict";
|
|
81769
|
+
init_inMemory();
|
|
81770
|
+
PROTOCOL_2026 = "2026-07-28";
|
|
81771
|
+
BRIDGE_VERSION = "2025-11-25";
|
|
81772
|
+
SUPPORTED_PROTOCOL_VERSIONS2 = [
|
|
81773
|
+
PROTOCOL_2026,
|
|
81774
|
+
"2025-11-25",
|
|
81775
|
+
"2025-06-18",
|
|
81776
|
+
"2025-03-26",
|
|
81777
|
+
"2024-11-05"
|
|
81778
|
+
];
|
|
81779
|
+
META_PROTOCOL_VERSION = "io.modelcontextprotocol/protocolVersion";
|
|
81780
|
+
META_CLIENT_CAPABILITIES = "io.modelcontextprotocol/clientCapabilities";
|
|
81781
|
+
META_CLIENT_INFO = "io.modelcontextprotocol/clientInfo";
|
|
81782
|
+
META_SERVER_INFO = "io.modelcontextprotocol/serverInfo";
|
|
81783
|
+
META_LOG_LEVEL = "io.modelcontextprotocol/logLevel";
|
|
81784
|
+
META_SUBSCRIPTION_ID = "io.modelcontextprotocol/subscriptionId";
|
|
81785
|
+
TASKS_EXTENSION = "io.modelcontextprotocol/tasks";
|
|
81786
|
+
ERR_HEADER_MISMATCH = -32020;
|
|
81787
|
+
ERR_UNSUPPORTED_PROTOCOL_VERSION = -32022;
|
|
81788
|
+
ERR_METHOD_NOT_FOUND = -32601;
|
|
81789
|
+
ERR_INVALID_PARAMS = -32602;
|
|
81790
|
+
ERR_INTERNAL = -32603;
|
|
81791
|
+
LEGACY_RESOURCE_NOT_FOUND = -32002;
|
|
81792
|
+
LIST_TTL_MS = 6e4;
|
|
81793
|
+
DISCOVER_TTL_MS = 3e5;
|
|
81794
|
+
CACHE_SCOPE = "private";
|
|
81795
|
+
CACHEABLE_METHODS = /* @__PURE__ */ new Map([
|
|
81796
|
+
["server/discover", DISCOVER_TTL_MS],
|
|
81797
|
+
["tools/list", LIST_TTL_MS],
|
|
81798
|
+
["prompts/list", LIST_TTL_MS],
|
|
81799
|
+
["resources/list", LIST_TTL_MS],
|
|
81800
|
+
["resources/templates/list", LIST_TTL_MS],
|
|
81801
|
+
["resources/read", LIST_TTL_MS]
|
|
81802
|
+
]);
|
|
81803
|
+
FORWARDED_METHODS = /* @__PURE__ */ new Set([
|
|
81804
|
+
"tools/list",
|
|
81805
|
+
"tools/call",
|
|
81806
|
+
"resources/list",
|
|
81807
|
+
"resources/templates/list",
|
|
81808
|
+
"resources/read",
|
|
81809
|
+
"prompts/list",
|
|
81810
|
+
"prompts/get",
|
|
81811
|
+
"completion/complete"
|
|
81812
|
+
]);
|
|
81813
|
+
NAME_HEADER_METHODS = /* @__PURE__ */ new Set(["tools/call", "prompts/get"]);
|
|
81814
|
+
MRTR_METHODS = /* @__PURE__ */ new Set(["tools/call", "prompts/get", "resources/read"]);
|
|
81815
|
+
LONG_RUNNING_TOOLS = /* @__PURE__ */ new Set([
|
|
81816
|
+
"route_nets",
|
|
81817
|
+
"route_diff_pair",
|
|
81818
|
+
"length_match_traces",
|
|
81819
|
+
"fab_prep",
|
|
81820
|
+
"fix_drc",
|
|
81821
|
+
"topology_optimize",
|
|
81822
|
+
"optimize_electrodes",
|
|
81823
|
+
"export_video",
|
|
81824
|
+
"render_sequence",
|
|
81825
|
+
"simulate_flow",
|
|
81826
|
+
"simulate_em",
|
|
81827
|
+
"simulate_photonics",
|
|
81828
|
+
"simulate_charged_particles",
|
|
81829
|
+
"simulate_neutron_shield",
|
|
81830
|
+
"simulate_lattice_gauge",
|
|
81831
|
+
"md_run",
|
|
81832
|
+
"minimize_energy",
|
|
81833
|
+
"homogenize_material",
|
|
81834
|
+
"design_material"
|
|
81835
|
+
]);
|
|
81836
|
+
TASK_TTL_MS = 30 * 6e4;
|
|
81837
|
+
TASK_POLL_INTERVAL_MS = 1e3;
|
|
81838
|
+
MODERN_ONLY_METHODS = /* @__PURE__ */ new Set([
|
|
81839
|
+
"server/discover",
|
|
81840
|
+
"subscriptions/listen",
|
|
81841
|
+
"tasks/get",
|
|
81842
|
+
"tasks/update",
|
|
81843
|
+
"tasks/cancel"
|
|
81844
|
+
]);
|
|
81845
|
+
BASE64_SENTINEL_PREFIX = "=?base64?";
|
|
81846
|
+
BASE64_SENTINEL_SUFFIX = "?=";
|
|
81847
|
+
bus = new EventEmitter();
|
|
81848
|
+
bus.setMaxListeners(100);
|
|
81849
|
+
EVT_TOOLS_CHANGED = "tools_list_changed";
|
|
81850
|
+
EVT_TASK = "task";
|
|
81851
|
+
tasks = /* @__PURE__ */ new Map();
|
|
81852
|
+
taskInputHooks = /* @__PURE__ */ new WeakMap();
|
|
81853
|
+
}
|
|
81854
|
+
});
|
|
81855
|
+
|
|
81100
81856
|
// src/index.ts
|
|
81101
81857
|
var index_exports = {};
|
|
81102
81858
|
async function main() {
|
|
81103
81859
|
const server = await createServer(void 0, { user: null });
|
|
81104
81860
|
const transport = new StdioServerTransport();
|
|
81105
81861
|
await server.connect(transport);
|
|
81862
|
+
const legacyOnMessage = transport.onmessage?.bind(transport);
|
|
81863
|
+
const listens = /* @__PURE__ */ new Map();
|
|
81864
|
+
transport.onmessage = (msg) => {
|
|
81865
|
+
if (!isModernMessage(msg)) {
|
|
81866
|
+
const m = msg;
|
|
81867
|
+
const cancelId = m.params?.requestId;
|
|
81868
|
+
if (m.method === "notifications/cancelled" && cancelId !== void 0 && listens.has(cancelId)) {
|
|
81869
|
+
listens.get(cancelId).close();
|
|
81870
|
+
listens.delete(cancelId);
|
|
81871
|
+
return;
|
|
81872
|
+
}
|
|
81873
|
+
legacyOnMessage?.(msg);
|
|
81874
|
+
return;
|
|
81875
|
+
}
|
|
81876
|
+
const method = msg.method;
|
|
81877
|
+
if (method === "subscriptions/listen") {
|
|
81878
|
+
const id = msg.id;
|
|
81879
|
+
const handle = handleModernListen(msg, {
|
|
81880
|
+
send: (m) => void transport.send(m)
|
|
81881
|
+
});
|
|
81882
|
+
if (id !== void 0) listens.set(id, handle);
|
|
81883
|
+
return;
|
|
81884
|
+
}
|
|
81885
|
+
void handleModernRequest(msg, {
|
|
81886
|
+
createServer: () => createServer(void 0, { user: null }),
|
|
81887
|
+
// Request-scoped notifications share the stdio channel; the SDK
|
|
81888
|
+
// client correlates progress via its progressToken.
|
|
81889
|
+
onNotification: (n) => void transport.send(n)
|
|
81890
|
+
}).then(({ body }) => {
|
|
81891
|
+
if (body !== null) {
|
|
81892
|
+
void transport.send(body);
|
|
81893
|
+
}
|
|
81894
|
+
});
|
|
81895
|
+
};
|
|
81106
81896
|
}
|
|
81107
81897
|
var init_index = __esm({
|
|
81108
81898
|
"src/index.ts"() {
|
|
81109
81899
|
"use strict";
|
|
81110
81900
|
init_stdio2();
|
|
81111
81901
|
init_server3();
|
|
81902
|
+
init_protocol_2026();
|
|
81112
81903
|
console.log = (...args) => console.error(...args);
|
|
81113
81904
|
main().catch((err7) => {
|
|
81114
81905
|
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.
|
|
3
|
+
"version": "0.9.4-main.48",
|
|
4
4
|
"description": "vcad MCP server — parametric CAD + PCB design tools for AI agents (self-contained: bundled server + kernel WASM)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
},
|
|
20
20
|
"homepage": "https://vcad.io",
|
|
21
21
|
"vcadBuild": {
|
|
22
|
-
"sha": "
|
|
23
|
-
"builtAt": "2026-07-
|
|
22
|
+
"sha": "fe62ea0789005cd4b3f00777274323698634167d",
|
|
23
|
+
"builtAt": "2026-07-29T16:12:24.504Z"
|
|
24
24
|
}
|
|
25
25
|
}
|
package/vcad_kernel_wasm_bg.wasm
CHANGED
|
Binary file
|