Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 107 additions & 0 deletions src/agent/mcp/registry/coreRuntimeTools.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2026 Andrii Shylenko and kernelCAD contributors
import { evaluateScriptTool } from '../tools/evaluateScript';
import { diffScriptsTool } from '../tools/diffScripts';
import { setParamValueTool } from '../tools/setParamValue';
import type { ToolRegistryEntry } from './types';

const evaluateScriptToolEntry: ToolRegistryEntry = {
definition: {
name: 'evaluate_script',
description:
'Use this when you need to run a script and check it compiles. ' +
'Run a kernelCAD .kcad.ts script and report pass/fail + feature count + diagnostics. ' +
'When the scene is assembly-built (assembly().part(...) → .model()/.solvedModel()), ' +
'also returns a parts summary { count, names } AND runs the mechanism-truth gate by ' +
'default: the `mechanism` field reports real/broken/unverified and a broken mechanism ' +
'(self-collision, fastened drift, dof-mismatch) makes ok:false with the failures in ' +
'diagnostics. Pass { skipMechanismCheck: true } to opt out. ' +
'Pass either { file: "<path>" } or { code: "<inline source>" }. ' +
'Set { dryRun: true } for fast validation while iterating: transpile + capture + ' +
'capture-light checks WITHOUT OCCT lowering, DFM gates, or meshing — milliseconds ' +
'instead of seconds (100x+ on boolean/fillet-heavy scripts). A dry run catches script ' +
'throws, capture-time API misuse, and assembly validity-gate failures, but NOT ' +
'lowering failures or dfmSpec diagnostics; it leaves the active session untouched, ' +
'so finish with a full (non-dry) evaluate_script before using session-dependent tools.',
inputSchema: {
type: 'object',
properties: {
file: { type: 'string', description: 'Path to a .kcad.ts script file.' },
code: { type: 'string', description: 'Inline kernelCAD script source.' },
dryRun: {
type: 'boolean',
description:
'Fast validation only: skip OCCT lowering, DFM gates, and meshing. ' +
'Does not set or clear the active session.',
},
skipMechanismCheck: {
type: 'boolean',
description:
'Opt out of the default mechanism-truth gate. By default a full ' +
'evaluation of an assembly-built scene runs checkMechanismTruth and ' +
'returns a `mechanism` verdict (real/broken/unverified); a broken ' +
"mechanism makes ok:false. Set true to skip the sweep entirely (no " +
'`mechanism` field, no cost). Ignored for dryRun and non-assembly scripts.',
},
},
},
},
handler: input => evaluateScriptTool(input as Parameters<typeof evaluateScriptTool>[0]),
};

const diffScriptsToolEntry: ToolRegistryEntry = {
definition: {
name: 'diff_scripts',
description:
'Use this when you need to see exactly what changed between two script versions. ' +
'Structured geometric delta between two versions of a kernelCAD script — a baseline ' +
'({ baseFile } or { baseCode }) and a revision ({ file } or { code }). Returns ' +
'agent-readable JSON: per-part added/removed/renamed/changed (volume mm³ + exact bbox ' +
"deltas, numbers matching inspect({ of: 'part-stats' })), total interference-volume delta with " +
'per-pair detail, mate-graph changes (added/removed/changed mates incl. type, ' +
'connectors, pose, limits), and param changes (value/min/max). Single-shape scripts ' +
'diff as one "(root)" pseudo-part. Use after editing a script to verify exactly what ' +
'changed physically before re-rendering. Read-only — never touches the active session.',
inputSchema: {
type: 'object',
properties: {
baseFile: { type: 'string', description: 'Baseline script — path to a .kcad.ts file.' },
baseCode: { type: 'string', description: 'Baseline script — inline source.' },
file: { type: 'string', description: 'Revised script — path to a .kcad.ts file.' },
code: { type: 'string', description: 'Revised script — inline source.' },
},
},
},
handler: input => diffScriptsTool(input as Parameters<typeof diffScriptsTool>[0]),
};

const setParamToolEntry: ToolRegistryEntry = {
definition: {
name: 'set_param',
description: 'Use this when you need to edit a param() default value in a kernelCAD script. Returns the modified code as text plus diagnostics from re-evaluating the result. Caller persists the new code via standard file-write tools (this tool has no side effects).',
inputSchema: {
type: 'object',
properties: {
code: { type: 'string', description: 'The .kcad.ts source code.' },
param_name: { type: 'string', description: 'The string literal name of the param (first arg to param()).' },
new_value: { description: 'The new default value — number for numeric params, string for expressions.' },
},
required: ['code', 'param_name', 'new_value'],
},
},
handler: input => setParamValueTool(input as unknown as Parameters<typeof setParamValueTool>[0]),
};

export const coreRuntimePreludeToolEntries: ToolRegistryEntry[] = [
evaluateScriptToolEntry,
diffScriptsToolEntry,
];

export const coreRuntimeParameterToolEntries: ToolRegistryEntry[] = [setParamToolEntry];

// Aggregate export for tests and family-level audits. Production composition
// intentionally splits these entries to preserve the historical public order.
export const coreRuntimeToolEntries: ToolRegistryEntry[] = [
...coreRuntimePreludeToolEntries,
...coreRuntimeParameterToolEntries,
];
166 changes: 166 additions & 0 deletions src/agent/mcp/registry/inspectionVerificationTools.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2026 Andrii Shylenko and kernelCAD contributors
import { inspectTool } from '../tools/inspect';
import { queryTool } from '../tools/query';
import { verifyTool } from '../tools/verify';
import { whyDidThisFailTool } from '../tools/whyDidThisFail';
import type { ToolRegistryEntry } from './types';

const inspectToolEntry: ToolRegistryEntry = {
definition: {
name: 'inspect',
description:
'Use this when you need to read facts about a model. One reader, selected by `of`:\n' +
"- 'assembly' — physical assembly inventory (parts, bboxes, connectors, mates, disconnected solids).\n" +
"- 'robot' — URDF/SDFormat export preview (links, joints, planning groups, end-effectors, issues).\n" +
"- 'step' — inspect an imported STEP file.\n" +
"- 'shape' — volume / surfaceArea / bbox for one feature ({ feature_id? }).\n" +
"- 'features' — features captured by the script (kind, id, params, transforms, suppression).\n" +
"- 'assemblies' — assembly intent (assemblies, parts, connectors, joints).\n" +
"- 'topology' — canonical face names + edge count for a feature ({ feature_id? }).\n" +
"- 'edges' — edges of a shape with optional EdgeQuery ({ feature_id?, query? }); returns @kc[...] refs.\n" +
"- 'face-edges' — boundary edges of a named canonical face ({ feature_id?, face_name }).\n" +
"- 'faces' — faces of a shape with optional FaceQuery ({ feature_id?, query? }); returns @kc[...] refs.\n" +
"- 'face-labels' — user-applied labels visible in the script.\n" +
"- 'mates' — mates captured by the script.\n" +
"- 'constraints' — sketch constraints captured by the script.\n" +
"- 'part-stats' — bundled parts-catalog statistics.\n" +
"- 'bend-table' — sheet-metal bend table for a flattened pattern.\n" +
"- 'params' — declared model parameters.\n" +
"- 'part-categories' — top-level part-catalog categories available in the bundled (and configured remote) catalog.\n" +
"- 'part-families' — part families within a category ({ category? }); count + exemplar ids per family.\n" +
'All params except `of` are subject-specific and forwarded verbatim. Most subjects accept { file | code }.',
inputSchema: {
type: 'object',
properties: {
of: {
type: 'string',
enum: ['assembly', 'robot', 'step', 'shape', 'features', 'assemblies', 'topology', 'edges', 'face-edges', 'faces', 'face-labels', 'mates', 'constraints', 'part-stats', 'bend-table', 'params', 'part-categories', 'part-families'],
description: 'Which facts to read.',
},
file: { type: 'string', description: 'Path to a .kcad.ts script file.' },
code: { type: 'string', description: 'Inline kernelCAD script source.' },
assembly: { type: 'string', description: "of:'assembly'|'robot' — assembly name; defaults to the first captured assembly." },
feature_id: { type: 'string', description: "of:'shape'|'topology'|'edges'|'faces'|'face-edges'|'face-labels' — FeatureId; defaults to the last returned shape." },
face_name: { type: 'string', enum: ['top', 'bottom', 'left', 'right', 'front', 'back'], description: "of:'face-edges' — canonical face name (required for that subject)." },
query: { type: 'object', description: "of:'edges'|'faces' — optional EdgeQuery/FaceQuery filter." },
category: { type: 'string', description: "of:'part-families' — optional top-level category to filter families by." },
},
required: ['of'],
},
},
handler: input => inspectTool(input as unknown as Parameters<typeof inspectTool>[0]),
};

const verifyToolEntry: ToolRegistryEntry = {
definition: {
name: 'verify',
description:
'Use this when you need to check a design against a rule set. One verifier, selected by `check`:\n' +
"- 'assembly' — mate-aware assembly validator on the active session (run evaluate_script first).\n" +
"- 'urdf' — structural validity of a .urdf file ({ urdf_path }).\n" +
"- 'dfm' — print-readiness gates declared by dfmSpec() ({ file | code }).\n" +
"- 'dfm-preflight' — sheet-metal flat pattern vs a job-shop's ordering rules ({ vendor, material, thicknessIn|thicknessMm, ... }).\n" +
"- 'swept-collision' — sweep declared joint range(s) and report colliding poses.\n" +
"- 'reachable' — inverse-kinematics reachability for an end-effector ({ tip_link, target_position, ... }).\n" +
"- 'mounting-holes' — fastened mates expose matching hole diameters on both sides.\n" +
"- 'load-capacity' — closed-form Euler-Bernoulli beam stress / safety-factor check ({ loads, materials, ... }).\n" +
'All params except `check` are check-specific and forwarded verbatim; each check fails closed on its own missing required params.',
inputSchema: {
type: 'object',
properties: {
check: {
type: 'string',
enum: ['assembly', 'urdf', 'dfm', 'dfm-preflight', 'swept-collision', 'reachable', 'mounting-holes', 'load-capacity'],
description: 'Which verification to run.',
},
file: { type: 'string', description: 'Path to a .kcad.ts script (assembly/dfm/dfm-preflight/swept-collision/reachable/mounting-holes/load-capacity).' },
code: { type: 'string', description: 'Inline kernelCAD script source (same checks as `file`).' },
assembly: { type: 'string', description: 'Assembly name; defaults to the first captured assembly.' },
urdf_path: { type: 'string', description: "check:'urdf' — path to the .urdf file." },
dxf: { type: 'string', description: "check:'dfm-preflight' — path to a DXF file." },
featureId: { type: 'string', description: "check:'dfm-preflight' — FeatureId to scope to." },
vendor: { type: 'string', description: "check:'dfm-preflight' — vendor SKU (required for that check)." },
material: { type: 'string', description: "check:'dfm-preflight' — material SKU (required for that check)." },
thicknessIn: { type: 'number', description: "check:'dfm-preflight' — material thickness in inches." },
thicknessMm: { type: 'number', description: "check:'dfm-preflight' — material thickness in millimeters." },
service: { type: 'string', enum: ['laser', 'cnc-router', 'waterjet', 'bending'], description: "check:'dfm-preflight' — service." },
refreshCatalog: { type: 'boolean', description: "check:'dfm-preflight' — force vendor catalog refresh." },
joint: { type: 'string', description: "check:'swept-collision' — joint to sweep; omit to sweep every declared joint." },
range: { type: 'array', items: { type: 'number' }, minItems: 3, maxItems: 3, description: "check:'swept-collision' — [lower, upper, step] in joint-native units." },
collision_tolerance_mm3: { type: 'number', description: "check:'swept-collision' — BREP intersection volume tolerance (mm^3)." },
tip_link: { type: 'string', description: "check:'reachable' — end-effector part name (required for that check)." },
target_position: { type: 'array', items: { type: 'number' }, minItems: 3, maxItems: 3, description: "check:'reachable' — target [x, y, z] mm (world frame)." },
target_orientation: { type: 'array', items: { type: 'number' }, minItems: 3, maxItems: 3, description: "check:'reachable' — target XYZ Euler angles in radians." },
position_tolerance_mm: { type: 'number', description: "check:'reachable' — position tolerance in mm." },
orientation_tolerance_rad: { type: 'number', description: "check:'reachable' — orientation tolerance in radians." },
prefer_solver: { type: 'string', enum: ['analytical', 'numeric', 'auto'], description: "check:'reachable' — force the IK path ('auto' default)." },
max_iterations: { type: 'number', description: "check:'reachable' — numeric-path iteration cap." },
seed: { type: 'object', description: "check:'reachable' — numeric IK seed pose (joint name -> deg/mm)." },
loads: { type: 'object', description: "check:'load-capacity' — partName -> { force?: [Fx,Fy,Fz] N, torque?: [Tx,Ty,Tz] N*m }." },
materials: { type: 'object', description: "check:'load-capacity' — partName -> material declaration." },
mode: { type: 'string', enum: ['stub', 'beam'], description: "check:'load-capacity' — 'beam' (default) or 'stub'." },
safety_factor_threshold: { type: 'number', description: "check:'load-capacity' — pass/fail safety-factor floor (default 1.5)." },
},
required: ['check'],
},
},
handler: input => verifyTool(input as unknown as Parameters<typeof verifyTool>[0]),
};

const whyDidThisFailToolEntry: ToolRegistryEntry = {
definition: {
name: 'why_did_this_fail',
description: "Use this when you need to trace why a feature failed. Walk the upstream chain of a failing feature. Returns the diagnostics of the requested feature plus the diagnostics of every upstream feature in topological order (the requested feature is the last entry). Per-code hints are inline on every diagnostic — call lookup_diagnostics for the full catalogue. Pass { file?, code?, feature_id? }.",
inputSchema: {
type: 'object',
properties: {
file: { type: 'string' },
code: { type: 'string' },
feature_id: { type: 'string' },
},
},
},
handler: input => whyDidThisFailTool(input as Parameters<typeof whyDidThisFailTool>[0]),
};

const queryToolEntry: ToolRegistryEntry = {
definition: {
name: 'query',
description:
"Use this when you need to resolve or inspect topology against a script's lowered geometry. " +
"Selected by `mode` (default 'evaluate'):\n" +
"- 'evaluate' — inspect a Query (@kc[...] ref, @kcq[...] DSL, or { ast }); returns matched entities. Pass expect:'unique' to assert exactly-one.\n" +
"- 'resolve' — resolve a single @kc[...] / @kcq[...] ref to one entity ({ ref }).\n" +
"- 'lineage' — walk the HistoryMap for a named face ref ({ feature_id, ref }).\n" +
'All params except `mode` are forwarded verbatim.',
inputSchema: {
type: 'object',
properties: {
mode: { type: 'string', enum: ['evaluate', 'resolve', 'lineage'], description: "Resolution mode (default 'evaluate')." },
file: { type: 'string', description: 'Path to a .kcad.ts script file.' },
code: { type: 'string', description: 'Inline kernelCAD script source.' },
query: { description: "mode:'evaluate' — Query input: @kc[...] / @kcq[...] string or { ast } object." },
ref: { type: 'string', description: "mode:'resolve'|'lineage' — topology ref string." },
expect: { type: 'string', enum: ['any', 'unique'], description: "mode:'evaluate' — 'unique' asserts exactly-one." },
feature_id: { type: 'string', description: 'Optional FeatureId; defaults to the last lowered shape (use "auto" for lineage).' },
},
},
},
handler: input => queryTool(input as unknown as Parameters<typeof queryTool>[0]),
};

export const inspectionVerificationPreludeToolEntries: ToolRegistryEntry[] = [
inspectToolEntry,
verifyToolEntry,
whyDidThisFailToolEntry,
];

export const inspectionVerificationQueryToolEntries: ToolRegistryEntry[] = [queryToolEntry];

// Aggregate export for tests and family-level audits. Production composition
// intentionally splits these entries to preserve the historical public order.
export const inspectionVerificationToolEntries: ToolRegistryEntry[] = [
...inspectionVerificationPreludeToolEntries,
...inspectionVerificationQueryToolEntries,
];
Loading
Loading