@vcad/mcp 0.9.4-main.26 → 0.9.4-main.27
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 +168 -26
- 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 cfda229f9a317556e3c9f64a43700549cd5265ef at 2026-07-25T23:24:45.817Z. Source: https://github.com/ecto/vcad
|
package/index.mjs
CHANGED
|
@@ -41079,6 +41079,28 @@ var init_CHANGELOG = __esm({
|
|
|
41079
41079
|
"build_receipt"
|
|
41080
41080
|
]
|
|
41081
41081
|
},
|
|
41082
|
+
{
|
|
41083
|
+
id: "2026-07-25-gym-session-first",
|
|
41084
|
+
version: "0.9.4",
|
|
41085
|
+
date: "2026-07-25",
|
|
41086
|
+
category: "feat",
|
|
41087
|
+
title: "Sim from a session id, not a re-sent document",
|
|
41088
|
+
summary: "create_robot_env and batch_create_envs now take document_id, binding the env to the live session instead of a re-sent IR copy, and observations come back keyed by joint / instance id.",
|
|
41089
|
+
features: [
|
|
41090
|
+
"physics",
|
|
41091
|
+
"simulation",
|
|
41092
|
+
"mcp"
|
|
41093
|
+
],
|
|
41094
|
+
mcpTools: [
|
|
41095
|
+
"create_robot_env",
|
|
41096
|
+
"batch_create_envs",
|
|
41097
|
+
"gym_reset",
|
|
41098
|
+
"gym_observe",
|
|
41099
|
+
"gym_step",
|
|
41100
|
+
"batch_reset",
|
|
41101
|
+
"batch_step"
|
|
41102
|
+
]
|
|
41103
|
+
},
|
|
41082
41104
|
{
|
|
41083
41105
|
id: "2026-07-25-gerber-pad-rotation",
|
|
41084
41106
|
version: "0.9.4",
|
|
@@ -74088,6 +74110,41 @@ function getSimulation(envId) {
|
|
|
74088
74110
|
function getEnvRecord(envId) {
|
|
74089
74111
|
return envRecords.get(envId) ?? null;
|
|
74090
74112
|
}
|
|
74113
|
+
function resolveEnvDocument(args) {
|
|
74114
|
+
const id = typeof args.document_id === "string" ? args.document_id : "";
|
|
74115
|
+
const inline = args.document && typeof args.document === "object" ? args.document : null;
|
|
74116
|
+
if (id && inline) {
|
|
74117
|
+
throw new Error(
|
|
74118
|
+
`Pass either \`document_id\` or \`document\`, not both \u2014 they resolve to different sessions and the inline copy may be stale. Drop \`document\` to simulate session "${id}" in place.`
|
|
74119
|
+
);
|
|
74120
|
+
}
|
|
74121
|
+
if (!id && !inline) {
|
|
74122
|
+
throw new Error(
|
|
74123
|
+
"Pass `document_id` (from open_document) to simulate a resident session \u2014 or an inline `document` object for the stateless flow. Exactly one is required."
|
|
74124
|
+
);
|
|
74125
|
+
}
|
|
74126
|
+
if (id) {
|
|
74127
|
+
return { doc: getSession(id), documentId: id, source: "session" };
|
|
74128
|
+
}
|
|
74129
|
+
return { doc: inline, documentId: null, source: "inline" };
|
|
74130
|
+
}
|
|
74131
|
+
function labelObservation(obs, jointIds, endEffectorIds) {
|
|
74132
|
+
const labeled = { ...obs, joint_ids: jointIds };
|
|
74133
|
+
if (jointIds && jointIds.length === obs.joint_positions.length) {
|
|
74134
|
+
labeled.joints = jointIds.map((id, i) => ({
|
|
74135
|
+
id,
|
|
74136
|
+
position: obs.joint_positions[i],
|
|
74137
|
+
velocity: obs.joint_velocities[i]
|
|
74138
|
+
}));
|
|
74139
|
+
}
|
|
74140
|
+
if (endEffectorIds.length === obs.end_effector_poses.length) {
|
|
74141
|
+
labeled.end_effectors = endEffectorIds.map((id, i) => ({
|
|
74142
|
+
id,
|
|
74143
|
+
pose: obs.end_effector_poses[i]
|
|
74144
|
+
}));
|
|
74145
|
+
}
|
|
74146
|
+
return labeled;
|
|
74147
|
+
}
|
|
74091
74148
|
async function createRobotEnv(input) {
|
|
74092
74149
|
const args = input;
|
|
74093
74150
|
const available = await isPhysicsAvailable2();
|
|
@@ -74106,17 +74163,19 @@ async function createRobotEnv(input) {
|
|
|
74106
74163
|
}
|
|
74107
74164
|
try {
|
|
74108
74165
|
const envId = `sim_${randomBytes4(12).toString("base64url")}`;
|
|
74109
|
-
const
|
|
74166
|
+
const { doc, documentId: sessionId, source } = resolveEnvDocument(args);
|
|
74167
|
+
const env = await PhysicsEnv.create(doc, {
|
|
74110
74168
|
endEffectorIds: args.end_effector_ids,
|
|
74111
74169
|
dt: args.dt,
|
|
74112
74170
|
substeps: args.substeps,
|
|
74113
74171
|
maxSteps: args.max_steps
|
|
74114
74172
|
});
|
|
74115
74173
|
simulations.set(envId, env);
|
|
74116
|
-
const documentId = registerSession(
|
|
74174
|
+
const documentId = sessionId ?? registerSession(doc);
|
|
74117
74175
|
envRecords.set(envId, {
|
|
74118
|
-
document:
|
|
74176
|
+
document: doc,
|
|
74119
74177
|
documentId,
|
|
74178
|
+
endEffectorIds: args.end_effector_ids,
|
|
74120
74179
|
trajectory: [],
|
|
74121
74180
|
rewards: [],
|
|
74122
74181
|
dones: [],
|
|
@@ -74128,7 +74187,19 @@ async function createRobotEnv(input) {
|
|
|
74128
74187
|
});
|
|
74129
74188
|
const info = {
|
|
74130
74189
|
env_id: envId,
|
|
74190
|
+
// Binding contract, spelled out because two handles come back:
|
|
74191
|
+
// · env_id → gym_step / gym_reset / gym_observe / gym_close
|
|
74192
|
+
// · document_id → the replay viewer, get_preview_glb, and every other
|
|
74193
|
+
// session tool (render_view, inspect_cad, update, …)
|
|
74194
|
+
// On the session path document_id IS the id you passed — the env and the
|
|
74195
|
+
// document you keep editing are the same session. On the inline path it
|
|
74196
|
+
// is a NEW session minted from the IR you sent.
|
|
74131
74197
|
document_id: documentId,
|
|
74198
|
+
document_source: source,
|
|
74199
|
+
binds: {
|
|
74200
|
+
env_id: "gym_step, gym_reset, gym_observe, gym_close",
|
|
74201
|
+
document_id: "replay viewer, get_preview_glb, and session tools (render_view, inspect_cad, update)"
|
|
74202
|
+
},
|
|
74132
74203
|
num_joints: env.numJoints,
|
|
74133
74204
|
// Observation ordering contract: joint_positions[i] and
|
|
74134
74205
|
// joint_velocities[i] refer to joint_ids[i]. Null when the loaded
|
|
@@ -74185,8 +74256,16 @@ function gymStep(input) {
|
|
|
74185
74256
|
record2.dones.shift();
|
|
74186
74257
|
}
|
|
74187
74258
|
}
|
|
74259
|
+
const labeled = {
|
|
74260
|
+
...result,
|
|
74261
|
+
observation: labelObservation(
|
|
74262
|
+
result.observation,
|
|
74263
|
+
env.jointIds,
|
|
74264
|
+
record2?.endEffectorIds ?? []
|
|
74265
|
+
)
|
|
74266
|
+
};
|
|
74188
74267
|
return {
|
|
74189
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
74268
|
+
content: [{ type: "text", text: JSON.stringify(labeled, null, 2) }]
|
|
74190
74269
|
};
|
|
74191
74270
|
} catch (err7) {
|
|
74192
74271
|
const message = err7 instanceof Error ? err7.message : String(err7);
|
|
@@ -74218,7 +74297,20 @@ function gymReset(input) {
|
|
|
74218
74297
|
record2.resetEpoch += 1;
|
|
74219
74298
|
}
|
|
74220
74299
|
return {
|
|
74221
|
-
content: [
|
|
74300
|
+
content: [
|
|
74301
|
+
{
|
|
74302
|
+
type: "text",
|
|
74303
|
+
text: JSON.stringify(
|
|
74304
|
+
labelObservation(
|
|
74305
|
+
observation,
|
|
74306
|
+
env.jointIds,
|
|
74307
|
+
record2?.endEffectorIds ?? []
|
|
74308
|
+
),
|
|
74309
|
+
null,
|
|
74310
|
+
2
|
|
74311
|
+
)
|
|
74312
|
+
}
|
|
74313
|
+
]
|
|
74222
74314
|
};
|
|
74223
74315
|
} catch (err7) {
|
|
74224
74316
|
const message = err7 instanceof Error ? err7.message : String(err7);
|
|
@@ -74241,8 +74333,22 @@ function gymObserve(input) {
|
|
|
74241
74333
|
}
|
|
74242
74334
|
try {
|
|
74243
74335
|
const observation = env.observe();
|
|
74336
|
+
const record2 = envRecords.get(args.env_id);
|
|
74244
74337
|
return {
|
|
74245
|
-
content: [
|
|
74338
|
+
content: [
|
|
74339
|
+
{
|
|
74340
|
+
type: "text",
|
|
74341
|
+
text: JSON.stringify(
|
|
74342
|
+
labelObservation(
|
|
74343
|
+
observation,
|
|
74344
|
+
env.jointIds,
|
|
74345
|
+
record2?.endEffectorIds ?? []
|
|
74346
|
+
),
|
|
74347
|
+
null,
|
|
74348
|
+
2
|
|
74349
|
+
)
|
|
74350
|
+
}
|
|
74351
|
+
]
|
|
74246
74352
|
};
|
|
74247
74353
|
} catch (err7) {
|
|
74248
74354
|
const message = err7 instanceof Error ? err7.message : String(err7);
|
|
@@ -74288,6 +74394,7 @@ async function batchCreateEnvs(input) {
|
|
|
74288
74394
|
}
|
|
74289
74395
|
try {
|
|
74290
74396
|
const batchId = `batch_${randomBytes4(12).toString("base64url")}`;
|
|
74397
|
+
const { doc, documentId, source } = resolveEnvDocument(args);
|
|
74291
74398
|
const envOptions = {
|
|
74292
74399
|
endEffectorIds: args.end_effector_ids,
|
|
74293
74400
|
dt: args.dt,
|
|
@@ -74296,22 +74403,33 @@ async function batchCreateEnvs(input) {
|
|
|
74296
74403
|
};
|
|
74297
74404
|
const envPromises = Array.from(
|
|
74298
74405
|
{ length: args.n_envs },
|
|
74299
|
-
() => PhysicsEnv.create(
|
|
74406
|
+
() => PhysicsEnv.create(doc, envOptions)
|
|
74300
74407
|
);
|
|
74301
74408
|
const envs = await Promise.all(envPromises);
|
|
74302
74409
|
const actionDim = envs[0].actionDim;
|
|
74303
74410
|
batchGroups.set(batchId, {
|
|
74304
74411
|
envs,
|
|
74305
74412
|
actionDim,
|
|
74306
|
-
document:
|
|
74307
|
-
options: envOptions
|
|
74413
|
+
document: doc,
|
|
74414
|
+
options: envOptions,
|
|
74415
|
+
jointIds: envs[0].jointIds
|
|
74308
74416
|
});
|
|
74309
74417
|
const info = {
|
|
74310
74418
|
batch_id: batchId,
|
|
74419
|
+
// batch_step / batch_reset bind to batch_id. No document session is
|
|
74420
|
+
// minted here — a batch has no viewer; `document_id` echoes the session
|
|
74421
|
+
// read from, and is null on the inline path.
|
|
74422
|
+
document_id: documentId,
|
|
74423
|
+
document_source: source,
|
|
74311
74424
|
n_envs: args.n_envs,
|
|
74312
74425
|
action_dim: actionDim,
|
|
74313
74426
|
observation_dim: envs[0].observationDim,
|
|
74314
|
-
num_joints: envs[0].numJoints
|
|
74427
|
+
num_joints: envs[0].numJoints,
|
|
74428
|
+
// Observation ordering contract, echoed so batch callers get the same
|
|
74429
|
+
// labeling the single-env path returns per observation.
|
|
74430
|
+
joint_ids: envs[0].jointIds,
|
|
74431
|
+
actuated_joint_ids: envs[0].actuatedJointIds,
|
|
74432
|
+
end_effector_ids: args.end_effector_ids
|
|
74315
74433
|
};
|
|
74316
74434
|
return {
|
|
74317
74435
|
content: [{ type: "text", text: JSON.stringify(info, null, 2) }]
|
|
@@ -74353,7 +74471,13 @@ function batchStep(input) {
|
|
|
74353
74471
|
(env, i) => env.step(args.action_type, args.actions[i])
|
|
74354
74472
|
);
|
|
74355
74473
|
const summary = {
|
|
74356
|
-
observations: results.map(
|
|
74474
|
+
observations: results.map(
|
|
74475
|
+
(r) => labelObservation(
|
|
74476
|
+
r.observation,
|
|
74477
|
+
group.jointIds,
|
|
74478
|
+
group.options.endEffectorIds
|
|
74479
|
+
)
|
|
74480
|
+
),
|
|
74357
74481
|
rewards: results.map((r) => r.reward),
|
|
74358
74482
|
dones: results.map((r) => r.done)
|
|
74359
74483
|
};
|
|
@@ -74380,7 +74504,13 @@ function batchReset(input) {
|
|
|
74380
74504
|
};
|
|
74381
74505
|
}
|
|
74382
74506
|
try {
|
|
74383
|
-
const observations = group.envs.map(
|
|
74507
|
+
const observations = group.envs.map(
|
|
74508
|
+
(env) => labelObservation(
|
|
74509
|
+
env.reset(),
|
|
74510
|
+
group.jointIds,
|
|
74511
|
+
group.options.endEffectorIds
|
|
74512
|
+
)
|
|
74513
|
+
);
|
|
74384
74514
|
return {
|
|
74385
74515
|
content: [{ type: "text", text: JSON.stringify({ observations }, null, 2) }]
|
|
74386
74516
|
};
|
|
@@ -74392,7 +74522,7 @@ function batchReset(input) {
|
|
|
74392
74522
|
};
|
|
74393
74523
|
}
|
|
74394
74524
|
}
|
|
74395
|
-
var simulations, MAX_TRAJECTORY, envRecords, batchGroups, createRobotEnvSchema, gymStepSchema, gymResetSchema, gymObserveSchema, gymCloseSchema, batchCreateEnvsSchema, batchStepSchema, batchResetSchema, toolDefs45;
|
|
74525
|
+
var simulations, MAX_TRAJECTORY, envRecords, batchGroups, INLINE_DOC_DESC, SESSION_DOC_DESC, createRobotEnvSchema, gymStepSchema, gymResetSchema, gymObserveSchema, gymCloseSchema, batchCreateEnvsSchema, batchStepSchema, batchResetSchema, toolDefs45;
|
|
74396
74526
|
var init_gym = __esm({
|
|
74397
74527
|
"src/tools/gym.ts"() {
|
|
74398
74528
|
"use strict";
|
|
@@ -74403,12 +74533,18 @@ var init_gym = __esm({
|
|
|
74403
74533
|
MAX_TRAJECTORY = 600;
|
|
74404
74534
|
envRecords = /* @__PURE__ */ new Map();
|
|
74405
74535
|
batchGroups = /* @__PURE__ */ new Map();
|
|
74536
|
+
INLINE_DOC_DESC = "Inline Document IR describing the robot assembly, used instead of a session. Use this stateless path when no `document_id` is resident (e.g. a cold serverless instance). Exactly one of `document_id` or `document` must be given.";
|
|
74537
|
+
SESSION_DOC_DESC = "Session id of the assembly to simulate (from open_document / create_cad_loon). Preferred: the env binds to this same session, so no IR round-trip is needed and the sim can never run against a stale copy.";
|
|
74406
74538
|
createRobotEnvSchema = {
|
|
74407
74539
|
type: "object",
|
|
74408
74540
|
properties: {
|
|
74541
|
+
document_id: {
|
|
74542
|
+
type: "string",
|
|
74543
|
+
description: SESSION_DOC_DESC
|
|
74544
|
+
},
|
|
74409
74545
|
document: {
|
|
74410
74546
|
type: "object",
|
|
74411
|
-
description:
|
|
74547
|
+
description: INLINE_DOC_DESC
|
|
74412
74548
|
},
|
|
74413
74549
|
end_effector_ids: {
|
|
74414
74550
|
type: "array",
|
|
@@ -74428,7 +74564,7 @@ var init_gym = __esm({
|
|
|
74428
74564
|
description: "Maximum episode length (default: 1000)"
|
|
74429
74565
|
}
|
|
74430
74566
|
},
|
|
74431
|
-
required: ["
|
|
74567
|
+
required: ["end_effector_ids"]
|
|
74432
74568
|
};
|
|
74433
74569
|
gymStepSchema = {
|
|
74434
74570
|
type: "object",
|
|
@@ -74483,9 +74619,13 @@ var init_gym = __esm({
|
|
|
74483
74619
|
batchCreateEnvsSchema = {
|
|
74484
74620
|
type: "object",
|
|
74485
74621
|
properties: {
|
|
74622
|
+
document_id: {
|
|
74623
|
+
type: "string",
|
|
74624
|
+
description: SESSION_DOC_DESC
|
|
74625
|
+
},
|
|
74486
74626
|
document: {
|
|
74487
74627
|
type: "object",
|
|
74488
|
-
description:
|
|
74628
|
+
description: INLINE_DOC_DESC
|
|
74489
74629
|
},
|
|
74490
74630
|
n_envs: {
|
|
74491
74631
|
type: "number",
|
|
@@ -74509,7 +74649,7 @@ var init_gym = __esm({
|
|
|
74509
74649
|
description: "Maximum episode length (default: 1000)"
|
|
74510
74650
|
}
|
|
74511
74651
|
},
|
|
74512
|
-
required: ["
|
|
74652
|
+
required: ["n_envs", "end_effector_ids"]
|
|
74513
74653
|
};
|
|
74514
74654
|
batchStepSchema = {
|
|
74515
74655
|
type: "object",
|
|
@@ -74548,7 +74688,7 @@ var init_gym = __esm({
|
|
|
74548
74688
|
{
|
|
74549
74689
|
name: "create_robot_env",
|
|
74550
74690
|
pack: "physics",
|
|
74551
|
-
description: "Create a physics simulation environment from a vcad assembly.
|
|
74691
|
+
description: "Create a physics simulation environment from a vcad assembly. Session-first: pass the `document_id` of the assembly already open \u2014 the env binds to that same session, so there is no IR round-trip and the sim can't run against a stale copy. Inline `document` IR is the stateless fallback; exactly one of the two is required. Returns env_id (for gym_step / gym_reset / gym_observe / gym_close) and document_id (what the replay viewer and the other session tools bind to). Mounts the inline 3D viewer with a play button \u2014 gym_step rollouts replay right in the chat.",
|
|
74552
74692
|
inputSchema: createRobotEnvSchema,
|
|
74553
74693
|
handler: (a) => createRobotEnv(a),
|
|
74554
74694
|
// writesDoc: the registered session must persist durably (signed-in hosted
|
|
@@ -74559,7 +74699,7 @@ var init_gym = __esm({
|
|
|
74559
74699
|
{
|
|
74560
74700
|
name: "gym_step",
|
|
74561
74701
|
pack: "physics",
|
|
74562
|
-
description: "Step the physics simulation with an action. action_type can be 'torque' (Nm), 'position' (degrees/mm), or 'velocity' (deg/s or mm/s). Returns observation (joint positions/velocities, end effector poses), reward, and done flag.",
|
|
74702
|
+
description: "Step the physics simulation with an action. action_type can be 'torque' (Nm), 'position' (degrees/mm), or 'velocity' (deg/s or mm/s). Returns observation (joint positions/velocities, end effector poses), reward, and done flag. The observation carries both the bare positional arrays and id-keyed views: `joints[i] = {id, position, velocity}` and `end_effectors[i] = {id, pose}`, so no ordering has to be remembered.",
|
|
74563
74703
|
inputSchema: gymStepSchema,
|
|
74564
74704
|
handler: (a) => gymStep(a),
|
|
74565
74705
|
behavior: behavior({})
|
|
@@ -74567,7 +74707,7 @@ var init_gym = __esm({
|
|
|
74567
74707
|
{
|
|
74568
74708
|
name: "gym_reset",
|
|
74569
74709
|
pack: "physics",
|
|
74570
|
-
description: "Reset the simulation environment to its initial state. Returns the initial observation.",
|
|
74710
|
+
description: "Reset the simulation environment to its initial state. Returns the initial observation, with joint values keyed by joint id (`joints`) and end effector poses keyed by instance id (`end_effectors`) alongside the bare arrays.",
|
|
74571
74711
|
inputSchema: gymResetSchema,
|
|
74572
74712
|
handler: (a) => gymReset(a),
|
|
74573
74713
|
behavior: behavior({})
|
|
@@ -74575,7 +74715,7 @@ var init_gym = __esm({
|
|
|
74575
74715
|
{
|
|
74576
74716
|
name: "gym_observe",
|
|
74577
74717
|
pack: "physics",
|
|
74578
|
-
description: "Get the current observation from the simulation without stepping. Returns joint positions, velocities, and end effector poses.",
|
|
74718
|
+
description: "Get the current observation from the simulation without stepping. Returns joint positions, velocities, and end effector poses \u2014 both as bare arrays and keyed by joint / instance id.",
|
|
74579
74719
|
inputSchema: gymObserveSchema,
|
|
74580
74720
|
handler: (a) => gymObserve(a),
|
|
74581
74721
|
behavior: behavior({})
|
|
@@ -74591,7 +74731,7 @@ var init_gym = __esm({
|
|
|
74591
74731
|
{
|
|
74592
74732
|
name: "batch_create_envs",
|
|
74593
74733
|
pack: "physics",
|
|
74594
|
-
description: "Create N parallel simulation environments from a single robot assembly. Returns a batch_id for use with batch_step and batch_reset. Enables parallel RL training across multiple environments.",
|
|
74734
|
+
description: "Create N parallel simulation environments from a single robot assembly. Session-first: pass the `document_id` of the open assembly, or inline `document` IR as the stateless fallback \u2014 exactly one is required. Returns a batch_id for use with batch_step and batch_reset. Enables parallel RL training across multiple environments.",
|
|
74595
74735
|
inputSchema: batchCreateEnvsSchema,
|
|
74596
74736
|
handler: (a) => batchCreateEnvs(a),
|
|
74597
74737
|
behavior: behavior({})
|
|
@@ -77878,6 +78018,8 @@ function buildInstructions(kernelPrompt) {
|
|
|
77878
78018
|
"",
|
|
77879
78019
|
"Sessions and server restarts: a `document_id` is a handle to server-side state, NOT storage. If ANY preview goes dark, a widget stops updating, or a call fails with an unknown `document_id`, check `server_info` FIRST \u2014 a low `uptime_s` or a changed `instance_id` means the SERVER RESTARTED and the feature is fine. That is the fastest way to tell a restart from a bug, and it should be the first check for any preview-shaped complaint. On a restart every live document_id dies at once, so previously-working widgets all go dark together \u2014 that is not a renderer bug. When `server_info` reports `durable:false`, sessions are in-memory only and cannot be recovered: keep the authoring source (your `create_cad_loon` call or record of edits) so a document can be rebuilt, and snapshot long-lived work with `checkpoint_document` / `save_document`. Every tool result also carries `_meta['io.vcad/build']` with `boot_token` + `instance_id`, so a changed value flags a restart without polling.",
|
|
77880
78020
|
"",
|
|
78021
|
+
"Physics/RL workflow: `create_robot_env` (or `batch_create_envs`) \u2192 `gym_reset` \u2192 `gym_step` \u2192 `gym_close`. Both env creators are session-first like everything else: pass the `document_id` of the open assembly \u2014 never re-send its IR. `create_robot_env` binds the env to that same session, so a `document_id` env and the document you keep editing can't drift apart; inline `document` IR stays available as the stateless fallback (it mints a NEW session, returned as `document_id`) and passing both is refused. `env_id` drives gym_step/gym_reset/gym_observe/gym_close; `document_id` is what the replay viewer, `get_preview_glb`, and the other session tools bind to. Observations come back both as bare positional arrays and keyed by id (`joints[i] = {id, position, velocity}`, `end_effectors[i] = {id, pose}`) \u2014 read the keyed views and no ordering has to be remembered.",
|
|
78022
|
+
"",
|
|
77881
78023
|
"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."
|
|
77882
78024
|
].join("\n");
|
|
77883
78025
|
if (!kernelPrompt) return header;
|
|
@@ -78727,8 +78869,8 @@ var init_server3 = __esm({
|
|
|
78727
78869
|
init_order_feed();
|
|
78728
78870
|
init_animate();
|
|
78729
78871
|
PKG_VERSION = (() => {
|
|
78730
|
-
if ("0.9.4-main.
|
|
78731
|
-
return "0.9.4-main.
|
|
78872
|
+
if ("0.9.4-main.27") {
|
|
78873
|
+
return "0.9.4-main.27";
|
|
78732
78874
|
}
|
|
78733
78875
|
try {
|
|
78734
78876
|
const req = createRequire2(import.meta.url);
|
|
@@ -78737,8 +78879,8 @@ var init_server3 = __esm({
|
|
|
78737
78879
|
return "0.0.0";
|
|
78738
78880
|
}
|
|
78739
78881
|
})();
|
|
78740
|
-
BUILD_SHA = "
|
|
78741
|
-
BUILD_TIME = "2026-07-25T23:
|
|
78882
|
+
BUILD_SHA = "cfda229f9a317556e3c9f64a43700549cd5265ef";
|
|
78883
|
+
BUILD_TIME = "2026-07-25T23:24:45.817Z";
|
|
78742
78884
|
SHORT_SHA = BUILD_SHA === "unknown" ? "unknown" : BUILD_SHA.slice(0, 7);
|
|
78743
78885
|
VERSION_WITH_BUILD = SHORT_SHA === "unknown" ? PKG_VERSION : `${PKG_VERSION}+${SHORT_SHA}`;
|
|
78744
78886
|
INSTANCE_ID = randomUUID6().slice(0, 8);
|
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.27",
|
|
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-25T23:
|
|
22
|
+
"sha": "cfda229f9a317556e3c9f64a43700549cd5265ef",
|
|
23
|
+
"builtAt": "2026-07-25T23:24:45.817Z"
|
|
24
24
|
}
|
|
25
25
|
}
|
package/vcad_kernel_wasm_bg.wasm
CHANGED
|
Binary file
|