From 27213b0fa45c60986a817b1a3a9bdb764af2bfdc Mon Sep 17 00:00:00 2001 From: Andrii Shylenko <14119286+w1ne@users.noreply.github.com> Date: Thu, 9 Jul 2026 14:51:12 +0200 Subject: [PATCH] Extract core MCP registry tools --- src/agent/mcp/registry/coreRuntimeTools.ts | 107 ++++++ .../registry/inspectionVerificationTools.ts | 166 +++++++++ .../mcp/registry/referenceExportTools.ts | 93 +++++ .../mcp/toolRegistry.publicContract.test.ts | 92 +++++ src/agent/mcp/toolRegistry.ts | 334 +----------------- 5 files changed, 474 insertions(+), 318 deletions(-) create mode 100644 src/agent/mcp/registry/coreRuntimeTools.ts create mode 100644 src/agent/mcp/registry/inspectionVerificationTools.ts create mode 100644 src/agent/mcp/registry/referenceExportTools.ts diff --git a/src/agent/mcp/registry/coreRuntimeTools.ts b/src/agent/mcp/registry/coreRuntimeTools.ts new file mode 100644 index 00000000..1a972fd2 --- /dev/null +++ b/src/agent/mcp/registry/coreRuntimeTools.ts @@ -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: "" } or { code: "" }. ' + + '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[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[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[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, +]; diff --git a/src/agent/mcp/registry/inspectionVerificationTools.ts b/src/agent/mcp/registry/inspectionVerificationTools.ts new file mode 100644 index 00000000..5bfe3a07 --- /dev/null +++ b/src/agent/mcp/registry/inspectionVerificationTools.ts @@ -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[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[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[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[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, +]; diff --git a/src/agent/mcp/registry/referenceExportTools.ts b/src/agent/mcp/registry/referenceExportTools.ts new file mode 100644 index 00000000..29463eff --- /dev/null +++ b/src/agent/mcp/registry/referenceExportTools.ts @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2026 Andrii Shylenko and kernelCAD contributors +import { exportTool } from '../tools/export'; +import { listApiTool } from '../tools/listApi'; +import { listDiagnosticCodesTool } from '../tools/listDiagnosticCodes'; +import type { ToolRegistryEntry } from './types'; + +export const referenceExportToolEntries: ToolRegistryEntry[] = [ + { + definition: { + name: 'lookup_api', + description: + 'Use this when you need to list the kernelCAD script-runtime surface: global functions (box, path, selectEdges, helix, etc), Shape methods (fillet, sweep, lower, etc), Sketch methods (extrude, revolve, sweep), PathBuilder methods, EdgeQuery/FaceQuery key sets, and featureKindFaceLabels (which globals accept opts.faceLabels and valid value shapes). Use this to discover what is callable from a .kcad.ts script.' + + ' Call this BEFORE concluding kernelCAD lacks a capability — its NURBS freeform surfacing (loft, sweep, boundary-fill, G2 blend) is easy to miss from tool names alone.', + inputSchema: { + type: 'object', + properties: {}, + }, + }, + handler: input => listApiTool(input as Parameters[0]), + }, + { + definition: { + name: 'lookup_diagnostics', + description: + 'Use this when you need the kernelCAD 26-code diagnostic catalogue with hint templates. ' + + 'Tiny one-shot call; useful for an agent that wants to pre-populate ' + + 'retry strategies. Hints are also inline on every emitted diagnostic — ' + + 'this tool just gives you the canonical list up front.', + inputSchema: { + type: 'object', + properties: {}, + }, + }, + handler: input => listDiagnosticCodesTool(input as Parameters[0]), + }, + { + definition: { + name: 'export', + description: + 'Use this when you need to export geometry to a file. One exporter, selected by `target`:\n' + + "- target:'model' — export the script geometry to one file. Pass { file | code }, a required { output_path }, and { format }. " + + 'Supported formats: stl (binary STL mesh), step (BREP CAD interchange), dxf (planar laser/waterjet profile from a Region or planar face), ' + + '3mf (slicer-friendly mesh with per-part colors), glb (web-viewer / AR with PBR materials), ' + + 'svg-drawing (third-angle engineering-drawing sheet: front/top/left + isometric views, hidden edges dashed, tangent edges thin, ' + + 'overall bounding-box dimensions, title block; assemblies are drawn with inter-part occlusion). ' + + 'Robot descriptions: urdf (tree-topology robot description), srdf (motion-planning semantics layered over the URDF), sdf-gazebo (SDFormat 1.10 with native ball joints, closed loops, and solved per-link poses). ' + + 'urdf and sdf-gazebo also write one meshes/.stl per link next to output_path (reported in mesh_files) — ship the whole directory to the consumer. ' + + 'STL exports run a watertight verify by default; failures return ok: false with export.mesh.not-watertight ' + + '(open-edge count + up to 5 crack-cluster locations) but the file is still written so the broken mesh can be inspected. ' + + 'Optional { feature_id } selects which feature to export (default: last). ' + + 'Optional { options } carries per-format options bag (see the kernelcad-mcp skill for the per-format keys: dxf layers/tolerance/unit, 3mf printUnit/embedSource, glb axis/draco).\n' + + "- target:'part' — export solved-assembly parts as individual binary STL files in their modeled (world-frame) positions. " + + 'Pass { file | code }, plus { part, output_path } for one part or { output_dir } for all parts ' + + '(files land at /.stl). A watertight verify runs on every exported mesh by default ' + + 'and fails the call with export.mesh.not-watertight; unknown part names fail with export.part.not-found listing the valid names.\n' + + 'Pass { no_verify: true } to skip the watertight gate. All params except `target` are forwarded verbatim; each target fails closed on its own missing required params.', + inputSchema: { + type: 'object', + properties: { + target: { + type: 'string', + enum: ['model', 'part'], + description: "Which exporter to run: 'model' (whole-script geometry to one file) or 'part' (per-part STLs from a solved assembly).", + }, + file: { type: 'string', description: 'Path to a .kcad.ts script file.' }, + code: { type: 'string', description: 'Inline kernelCAD script source.' }, + output_path: { type: 'string', description: "Destination path. target:'model' — the export file (required). target:'part' — single-part .stl path." }, + format: { + type: 'string', + enum: ['stl', 'step', 'dxf', '3mf', 'glb', 'svg-drawing', 'urdf', 'srdf', 'sdf-gazebo'], + description: "target:'model' — output file format (required for that target).", + }, + feature_id: { type: 'string', description: "target:'model' — optional FeatureId to export; defaults to last." }, + options: { + type: 'object', + description: + "target:'model' — optional per-format options bag. Discriminator options.format must equal top-level format. " + + 'dxf: { layers?, unit?: "mm"|"cm"|"in", tolerance? }. ' + + '3mf: { printUnit?: "mm"|"cm"|"in", embedSource? }. ' + + 'glb: { axis?: "y-up"|"z-up", draco?: false }. ' + + 'svg-drawing: { sheet?: "a4"|"a3", modelName?, date? }.', + }, + part: { type: 'string', description: "target:'part' — part name for single-part export, or 'all'." }, + output_dir: { type: 'string', description: "target:'part' — destination directory (all-parts mode); files are /.stl." }, + no_verify: { type: 'boolean', description: 'Skip the STL watertight verify gate.', default: false }, + }, + required: ['target'], + }, + }, + handler: input => exportTool(input as unknown as Parameters[0]), + }, +]; diff --git a/src/agent/mcp/toolRegistry.publicContract.test.ts b/src/agent/mcp/toolRegistry.publicContract.test.ts index b9e4bd42..1d6a8160 100644 --- a/src/agent/mcp/toolRegistry.publicContract.test.ts +++ b/src/agent/mcp/toolRegistry.publicContract.test.ts @@ -13,7 +13,10 @@ import { type McpToolDefinition, } from './toolRegistry'; import { catalogToolEntries } from './registry/catalogTools'; +import { coreRuntimeToolEntries } from './registry/coreRuntimeTools'; import { geometryAuthoringToolEntries } from './registry/geometryAuthoringTools'; +import { inspectionVerificationToolEntries } from './registry/inspectionVerificationTools'; +import { referenceExportToolEntries } from './registry/referenceExportTools'; import { reviewPipelineToolEntries } from './registry/reviewPipelineTools'; import { sketchAssemblyToolEntries } from './registry/sketchAssemblyTools'; @@ -62,6 +65,7 @@ const PUBLIC_CONTRACT_FIXTURE = new URL( '../../../tests/fixtures/mcp/toolRegistry.publicContract.json', import.meta.url, ); +const TOOL_REGISTRY_SOURCE = new URL('./toolRegistry.ts', import.meta.url); function serializedPublicTools(): unknown { return JSON.parse(JSON.stringify(TOOLS)); @@ -98,6 +102,94 @@ describe('toolRegistry public contract', () => { expect(serializedPublicTools()).toEqual(fixture); }); + it('keeps toolRegistry as the composition and dispatch boundary', () => { + expect(readFileSync(TOOL_REGISTRY_SOURCE, 'utf8')).not.toContain('definition: {'); + }); + + it('composes core runtime tools from the core runtime registry module', () => { + const names = coreRuntimeToolEntries.map(entry => entry.definition.name); + + expect(names).toEqual(['evaluate_script', 'diff_scripts', 'set_param']); + expect([TOOL_REGISTRY[0], TOOL_REGISTRY[1], TOOL_REGISTRY[5]]).toEqual(coreRuntimeToolEntries); + }); + + it('composes inspection and verification tools from the inspection verification registry module', () => { + const names = inspectionVerificationToolEntries.map(entry => entry.definition.name); + + expect(names).toEqual(['inspect', 'verify', 'why_did_this_fail', 'query']); + expect([TOOL_REGISTRY[2], TOOL_REGISTRY[3], TOOL_REGISTRY[4], TOOL_REGISTRY[16]]).toEqual( + inspectionVerificationToolEntries, + ); + }); + + it('composes reference and export tools from the reference export registry module', () => { + const names = referenceExportToolEntries.map(entry => entry.definition.name); + + expect(names).toEqual(['lookup_api', 'lookup_diagnostics', 'export']); + expect(TOOL_REGISTRY.slice(17, 20)).toEqual(referenceExportToolEntries); + }); + + it('wires the moved core runtime handlers through the public dispatcher', async () => { + const evalResult = await callMcpTool('evaluate_script', { + code: 'return box(10, 10, 10);', + dryRun: true, + }); + expect(evalResult).toMatchObject({ ok: true, dryRun: true, featureCount: 1 }); + + const diffResult = await callMcpTool('diff_scripts', {}); + expect(diffResult).toMatchObject({ + ok: false, + side: 'base', + errorCode: 'cli.invalid-args', + }); + + const setParamResult = await callMcpTool('set_param', { + code: "const width = param('width', 1);\nreturn box(width, 1, 1);", + param_name: 'missing', + new_value: 2, + }); + expect(setParamResult).toMatchObject({ ok: false }); + expect((setParamResult as { error?: string }).error).toContain('missing'); + }); + + it('wires the moved inspection and verification handlers through the public dispatcher', async () => { + await expect(callMcpTool('inspect', { of: 'nope' })).rejects.toThrow( + /Unknown inspect subject: nope/, + ); + + await expect(callMcpTool('verify', { check: 'nope' })).rejects.toThrow( + /Unknown verify check: nope/, + ); + + const whyResult = await callMcpTool('why_did_this_fail', { + code: 'return box(10, 10, 10);', + }); + expect(whyResult).toMatchObject({ + ok: true, + chain: [{ feature_id: 'box_1', kind: 'box', health: 'healthy' }], + }); + + await expect(callMcpTool('query', { mode: 'nope' })).rejects.toThrow( + /Unknown query mode: nope/, + ); + }); + + it('wires the moved reference and export handlers through the public dispatcher', async () => { + const apiResult = await callMcpTool('lookup_api', {}); + expect((apiResult as { globals?: Array<{ name: string }> }).globals).toContainEqual( + expect.objectContaining({ name: 'box' }), + ); + + const diagnosticResult = await callMcpTool('lookup_diagnostics', {}); + expect((diagnosticResult as { codes?: Array<{ code: string }> }).codes).toContainEqual( + expect.objectContaining({ code: 'cli.invalid-args' }), + ); + + await expect(callMcpTool('export', { target: 'nope' })).rejects.toThrow( + /Unknown export target: nope/, + ); + }); + it('composes catalog tools from the catalog registry module', () => { const names = catalogToolEntries.map(entry => entry.definition.name); diff --git a/src/agent/mcp/toolRegistry.ts b/src/agent/mcp/toolRegistry.ts index f3393ce7..72fb5cf9 100644 --- a/src/agent/mcp/toolRegistry.ts +++ b/src/agent/mcp/toolRegistry.ts @@ -1,19 +1,18 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2026 Andrii Shylenko and kernelCAD contributors -import { evaluateScriptTool } from './tools/evaluateScript'; -import { diffScriptsTool } from './tools/diffScripts'; -import { exportTool } from './tools/export'; -import { listApiTool } from './tools/listApi'; -import { listDiagnosticCodesTool } from './tools/listDiagnosticCodes'; -import { setParamValueTool } from './tools/setParamValue'; -import { whyDidThisFailTool } from './tools/whyDidThisFail'; -import { verifyTool } from './tools/verify'; -import { inspectTool } from './tools/inspect'; -import { queryTool } from './tools/query'; import { TOOL_ANNOTATIONS } from './toolAnnotations'; import { TOOL_OUTPUT_SCHEMAS } from './toolOutputSchemas'; import { catalogToolEntries } from './registry/catalogTools'; +import { + coreRuntimeParameterToolEntries, + coreRuntimePreludeToolEntries, +} from './registry/coreRuntimeTools'; import { geometryAuthoringToolEntries } from './registry/geometryAuthoringTools'; +import { + inspectionVerificationPreludeToolEntries, + inspectionVerificationQueryToolEntries, +} from './registry/inspectionVerificationTools'; +import { referenceExportToolEntries } from './registry/referenceExportTools'; import { reviewPipelineToolEntries } from './registry/reviewPipelineTools'; import { sketchAssemblyToolEntries } from './registry/sketchAssemblyTools'; import type { McpToolDefinition, ToolRegistryEntry } from './registry/types'; @@ -31,315 +30,14 @@ export type { McpToolDefinition } from './registry/types'; * Do NOT change the entry shape or remove entries without bumping the consumer SHA explicitly. */ export const TOOL_REGISTRY: 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: "" } or { code: "" }. ' + - '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[0]), - }, - { - 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[0]), - }, - { - 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[0]), - }, - { - 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[0]), - }, - { - 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[0]), - }, - { - 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[0]), - }, + // Some extracted families are split here because kernelCAD-server consumes + // the historical registry order as part of this public contract. + ...coreRuntimePreludeToolEntries, + ...inspectionVerificationPreludeToolEntries, + ...coreRuntimeParameterToolEntries, ...geometryAuthoringToolEntries, - { - 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[0]), - }, - { - definition: { - name: 'lookup_api', - description: - 'Use this when you need to list the kernelCAD script-runtime surface: global functions (box, path, selectEdges, helix, etc), Shape methods (fillet, sweep, lower, etc), Sketch methods (extrude, revolve, sweep), PathBuilder methods, EdgeQuery/FaceQuery key sets, and featureKindFaceLabels (which globals accept opts.faceLabels and valid value shapes). Use this to discover what is callable from a .kcad.ts script.' + - ' Call this BEFORE concluding kernelCAD lacks a capability — its NURBS freeform surfacing (loft, sweep, boundary-fill, G2 blend) is easy to miss from tool names alone.', - inputSchema: { - type: 'object', - properties: {}, - }, - }, - handler: input => listApiTool(input as Parameters[0]), - }, - { - definition: { - name: 'lookup_diagnostics', - description: - 'Use this when you need the kernelCAD 26-code diagnostic catalogue with hint templates. ' + - 'Tiny one-shot call; useful for an agent that wants to pre-populate ' + - 'retry strategies. Hints are also inline on every emitted diagnostic — ' + - 'this tool just gives you the canonical list up front.', - inputSchema: { - type: 'object', - properties: {}, - }, - }, - handler: input => listDiagnosticCodesTool(input as Parameters[0]), - }, - { - definition: { - name: 'export', - description: - 'Use this when you need to export geometry to a file. One exporter, selected by `target`:\n' + - "- target:'model' — export the script geometry to one file. Pass { file | code }, a required { output_path }, and { format }. " + - 'Supported formats: stl (binary STL mesh), step (BREP CAD interchange), dxf (planar laser/waterjet profile from a Region or planar face), ' + - '3mf (slicer-friendly mesh with per-part colors), glb (web-viewer / AR with PBR materials), ' + - 'svg-drawing (third-angle engineering-drawing sheet: front/top/left + isometric views, hidden edges dashed, tangent edges thin, ' + - 'overall bounding-box dimensions, title block; assemblies are drawn with inter-part occlusion). ' + - 'Robot descriptions: urdf (tree-topology robot description), srdf (motion-planning semantics layered over the URDF), sdf-gazebo (SDFormat 1.10 with native ball joints, closed loops, and solved per-link poses). ' + - 'urdf and sdf-gazebo also write one meshes/.stl per link next to output_path (reported in mesh_files) — ship the whole directory to the consumer. ' + - 'STL exports run a watertight verify by default; failures return ok: false with export.mesh.not-watertight ' + - '(open-edge count + up to 5 crack-cluster locations) but the file is still written so the broken mesh can be inspected. ' + - 'Optional { feature_id } selects which feature to export (default: last). ' + - 'Optional { options } carries per-format options bag (see the kernelcad-mcp skill for the per-format keys: dxf layers/tolerance/unit, 3mf printUnit/embedSource, glb axis/draco).\n' + - "- target:'part' — export solved-assembly parts as individual binary STL files in their modeled (world-frame) positions. " + - 'Pass { file | code }, plus { part, output_path } for one part or { output_dir } for all parts ' + - '(files land at /.stl). A watertight verify runs on every exported mesh by default ' + - 'and fails the call with export.mesh.not-watertight; unknown part names fail with export.part.not-found listing the valid names.\n' + - 'Pass { no_verify: true } to skip the watertight gate. All params except `target` are forwarded verbatim; each target fails closed on its own missing required params.', - inputSchema: { - type: 'object', - properties: { - target: { - type: 'string', - enum: ['model', 'part'], - description: "Which exporter to run: 'model' (whole-script geometry to one file) or 'part' (per-part STLs from a solved assembly).", - }, - file: { type: 'string', description: 'Path to a .kcad.ts script file.' }, - code: { type: 'string', description: 'Inline kernelCAD script source.' }, - output_path: { type: 'string', description: "Destination path. target:'model' — the export file (required). target:'part' — single-part .stl path." }, - format: { - type: 'string', - enum: ['stl', 'step', 'dxf', '3mf', 'glb', 'svg-drawing', 'urdf', 'srdf', 'sdf-gazebo'], - description: "target:'model' — output file format (required for that target).", - }, - feature_id: { type: 'string', description: "target:'model' — optional FeatureId to export; defaults to last." }, - options: { - type: 'object', - description: - "target:'model' — optional per-format options bag. Discriminator options.format must equal top-level format. " + - 'dxf: { layers?, unit?: "mm"|"cm"|"in", tolerance? }. ' + - '3mf: { printUnit?: "mm"|"cm"|"in", embedSource? }. ' + - 'glb: { axis?: "y-up"|"z-up", draco?: false }. ' + - 'svg-drawing: { sheet?: "a4"|"a3", modelName?, date? }.', - }, - part: { type: 'string', description: "target:'part' — part name for single-part export, or 'all'." }, - output_dir: { type: 'string', description: "target:'part' — destination directory (all-parts mode); files are /.stl." }, - no_verify: { type: 'boolean', description: 'Skip the STL watertight verify gate.', default: false }, - }, - required: ['target'], - }, - }, - handler: input => exportTool(input as unknown as Parameters[0]), - }, + ...inspectionVerificationQueryToolEntries, + ...referenceExportToolEntries, ...catalogToolEntries, ...sketchAssemblyToolEntries, ...reviewPipelineToolEntries,