@instruktlabs/kiln 1.1.0 → 1.2.0

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.
Files changed (47) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codex-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +12 -0
  4. package/dist/agent-run.mjs +220 -150
  5. package/dist/build.json +22 -22
  6. package/dist/cli.mjs +225 -150
  7. package/dist/mcp-engine.mjs +398 -144
  8. package/dist/mcp-server.mjs +1 -1
  9. package/docs/sdk.md +23 -0
  10. package/docs/tools-operations.md +1529 -0
  11. package/lib/discovery/index.d.ts +1 -1
  12. package/lib/discovery/index.js +2 -2
  13. package/lib/discovery/service.d.ts +3 -1
  14. package/lib/discovery/service.js +4 -2
  15. package/lib/engine-identity.d.ts +1 -1
  16. package/lib/engine-identity.js +1 -1
  17. package/lib/mcp-core.d.ts +2 -1
  18. package/lib/mcp-core.js +1 -0
  19. package/lib/mcp-engine.d.ts +2 -0
  20. package/lib/mcp-engine.js +8 -3
  21. package/lib/tools/discovery.d.ts +1 -1
  22. package/lib/tools/discovery.js +11 -9
  23. package/lib/tools/materials.d.ts +1 -1
  24. package/lib/tools/materials.js +44 -14
  25. package/lib/tools/operation-contract.d.ts +8 -0
  26. package/lib/tools/operation-contract.js +37 -0
  27. package/lib/tools/operations.d.ts +3 -0
  28. package/lib/tools/operations.js +154 -0
  29. package/lib/tools/registry.d.ts +6 -2
  30. package/lib/tools/registry.js +71 -23
  31. package/package.json +2 -1
  32. package/plugin.json +1 -1
  33. package/plugins/kiln-engine/.claude-plugin/plugin.json +1 -1
  34. package/plugins/kiln-engine/README.md +3 -3
  35. package/plugins/kiln-engine/package-provenance.json +6 -6
  36. package/plugins/kiln-engine/plugin.json +1 -1
  37. package/plugins/kiln-engine/runtime.json +1 -1
  38. package/src/discovery/index.ts +2 -0
  39. package/src/discovery/service.ts +3 -1
  40. package/src/engine-identity.ts +1 -1
  41. package/src/mcp-core.ts +3 -0
  42. package/src/mcp-engine.ts +11 -1
  43. package/src/tools/discovery.ts +20 -10
  44. package/src/tools/materials.ts +59 -15
  45. package/src/tools/operation-contract.ts +51 -0
  46. package/src/tools/operations.ts +177 -0
  47. package/src/tools/registry.ts +100 -20
@@ -36106,7 +36106,7 @@ import { readFile as readFile8 } from "node:fs/promises";
36106
36106
  var KILN_ASSET_WIDGET_URI = "ui://kiln/asset-v5.html";
36107
36107
 
36108
36108
  // src/engine-identity.ts
36109
- var ENGINE_VERSION = "1.1.0";
36109
+ var ENGINE_VERSION = "1.2.0";
36110
36110
  var ENGINE_INSTALL_URL = new URL("../", import.meta.url).href;
36111
36111
  function engineIdentity() {
36112
36112
  return { version: ENGINE_VERSION, installUrl: ENGINE_INSTALL_URL };
@@ -36117,6 +36117,7 @@ init_requirements_json();
36117
36117
  var MCP_SERVER_NAME = "kiln";
36118
36118
  var MCP_SERVER_VERSION = ENGINE_VERSION;
36119
36119
  var MCP_SERVER_INSTRUCTIONS = "Kiln turns JavaScript you write into GLB assets and returns rendered views. Work by reference: send a program once to kiln_validate or kiln_render, keep the returned programRef exactly, and use it for every later render, inspect, kiln_source and kiln_edit call; never resend a program to change part of it. Call kiln_discover first for helper contracts, with capabilities:true for the host's runtime, project and camera facts. Read viewFidelity before judging materials: a CPU view shows shape, not material; GPU views need the render service in render-service/. Agent Skills for authoring, refining, QA and scenes ship in skills/ beside this server's dist/; read the matching SKILL.md first.";
36120
+ var MCP_OPERATION_INSTRUCTIONS = "Kiln turns authored JavaScript into editable GLB assets and rendered views. Send source once to kiln_validate or kiln_render, then preserve the exact programRef for later reads, edits and renders. Use kiln_discover for helper contracts and full input shapes; use kiln_capabilities for host configuration and limits. Read viewFidelity before judging materials: CPU geometry views are not material evidence. Search saved assets with kiln_assets_search; reopen exact source with kiln_assets_restore. Browse materials with kiln_material_search and create with kiln_material_create. Save only when requested. Pin collection, asset and revision IDs for reuse and export.";
36120
36121
  var KILN_WIDGET_META = {
36121
36122
  ui: { prefersBorder: true, csp: { connectDomains: [], resourceDomains: [] } },
36122
36123
  "openai/widgetDescription": "Inspect the saved 3D asset and download its GLB or editable bundle.",
@@ -36243,7 +36244,7 @@ var PRE_HANDSHAKE_METHODS = new Set(["initialize", "ping"]);
36243
36244
  // src/tools/registry.ts
36244
36245
  init_capture_cache();
36245
36246
  init_assets();
36246
- import { z as z21 } from "zod";
36247
+ import { z as z23 } from "zod";
36247
36248
 
36248
36249
  // src/requirements-assets.ts
36249
36250
  init_requirements_context();
@@ -40400,7 +40401,7 @@ var MAX_RESPONSE_BYTES = 64 * 1024;
40400
40401
  var MAX_TEXT_CHARS = 16 * 1024;
40401
40402
  var DETAIL_RESULT_BUDGET = 15000;
40402
40403
  var compare2 = (a, b) => a < b ? -1 : a > b ? 1 : 0;
40403
- function createDiscoveryService(source, index, capabilities, retirements = {}, notes = async () => []) {
40404
+ function createDiscoveryService(source, index, capabilities, retirements = {}, notes = async () => [], capabilitiesCall = "capabilities:true") {
40404
40405
  const entries = parseCatalog(source);
40405
40406
  const byId = new Map(entries.map((entry) => [entry.id, entry]));
40406
40407
  const byName = new Map(entries.filter((entry) => entry.kind !== "recipe").map((entry) => [entry.name, entry]));
@@ -40423,7 +40424,7 @@ function createDiscoveryService(source, index, capabilities, retirements = {}, n
40423
40424
  "Parts auto-add to opts.parent; do not wrap createPart in parent.add. Part rotation uses degrees.",
40424
40425
  "Custom THREE.BufferGeometry and ordinary JavaScript functions remain available. Recipes offer guidance and never restrict modeling.",
40425
40426
  "Send source once and reuse programRef for reads, edits, inspection and rendering. Review geometry, materials and destination constraints separately.",
40426
- "Search for the operation you need in ordinary language, then fetch exact contracts with ids. Use capabilities:true for the current host."
40427
+ `Search for the operation you need in ordinary language, then fetch exact contracts with ids. Use ${capabilitiesCall} for the current host.`
40427
40428
  ]
40428
40429
  };
40429
40430
  const error = (mode, code, message) => ({
@@ -40604,12 +40605,12 @@ var snapshot2;
40604
40605
  function createDiscovery(capabilities = async () => ({
40605
40606
  host: "No host capability provider was supplied.",
40606
40607
  discovery: { mode: "lexical", offline: true, requiresModel: false }
40607
- }), notes = async () => []) {
40608
+ }), notes = async () => [], capabilitiesCall = "capabilities:true") {
40608
40609
  snapshot2 ??= (() => {
40609
40610
  const entries = listDiscoveryEntries();
40610
40611
  return { entries, index: createLexicalDiscoveryIndex(entries) };
40611
40612
  })();
40612
- return createDiscoveryService(snapshot2.entries, snapshot2.index, capabilities, REMOVED_AUTHORING_HELPERS, notes);
40613
+ return createDiscoveryService(snapshot2.entries, snapshot2.index, capabilities, REMOVED_AUTHORING_HELPERS, notes, capabilitiesCall);
40613
40614
  }
40614
40615
 
40615
40616
  // src/tools/discovery.ts
@@ -40618,8 +40619,8 @@ init_capture_limits();
40618
40619
  init_geometry_export();
40619
40620
  init_community_exporter();
40620
40621
  init_material_resources();
40621
- function createKilnDiscoveryDef(context) {
40622
- const run = createDiscovery(() => currentCapabilities(context), async () => projectNotes(context));
40622
+ function createKilnDiscoveryDef(context, presentation = "grouped") {
40623
+ const run = createDiscovery(() => currentCapabilities(context, presentation), async () => projectNotes(context), presentation === "operations" ? "kiln_capabilities({})" : "capabilities:true");
40623
40624
  return {
40624
40625
  name: "kiln_discover",
40625
40626
  description: "Discover Kiln operations, assemblies, recipes, tool-input shapes and current host capabilities. Omit arguments for a compact overview. Search with ordinary modeling language using query; refine with family, kind or tags. Fetch complete contracts/examples with ids (up to six exact IDs, executable names or shape: ids). Overview/search pages default to six summaries. Recipes guide construction without restricting the asset. Search runs locally without models or network calls.",
@@ -40643,7 +40644,7 @@ function projectNotes(context) {
40643
40644
  `Project ${projectId} is configured for this workspace and applies when projectId is omitted: read its brief, design, inventory and material pins first with kiln_project { action: "get", projectId: "${projectId}" }; projectId: null selects standalone work.`
40644
40645
  ] : [];
40645
40646
  }
40646
- async function currentCapabilities(context) {
40647
+ async function currentCapabilities(context, presentation) {
40647
40648
  const externalEvaluator = Boolean(context.evaluatorPort) || context.evaluatorProfile === "evaluator-required";
40648
40649
  const approvedTextures = context.approvedTextureResources ? context.approvedTextureResources() : externalEvaluator ? null : approvedTextureCatalogV1();
40649
40650
  const textureSlots = approvedTextures === null ? null : {};
@@ -40672,7 +40673,7 @@ async function currentCapabilities(context) {
40672
40673
  requiresModel: false
40673
40674
  },
40674
40675
  engine: engineIdentity(),
40675
- project: {
40676
+ project: presentation === "operations" && !context.projectStore ? { available: false } : {
40676
40677
  configured: configuredProject(context),
40677
40678
  select: "projectId per call; omitted uses the configured project, null selects standalone work",
40678
40679
  read: 'kiln_project { action: "get", projectId } returns the brief, design, inventory and material pins'
@@ -40691,7 +40692,7 @@ async function currentCapabilities(context) {
40691
40692
  available: Boolean(context.assetLibrary),
40692
40693
  collections: context.assetLibrary?.collections() ?? [],
40693
40694
  save: "kiln_save persists exact GLB, source and provenance; draft renders do not populate collections",
40694
- resume: "kiln_assets action=restore imports a saved revision into the current program store",
40695
+ resume: `${presentation === "operations" ? "kiln_assets_restore" : "kiln_assets action=restore"} imports a saved revision into the current program store`,
40695
40696
  downloads: "kiln_export returns GLB/source/ZIP resource links; client presentation varies",
40696
40697
  viewer: "kiln_present opens a saved revision in supporting chat clients with 3D viewing and downloads; kiln view opens a local collection or standalone GLB/ZIP"
40697
40698
  },
@@ -41080,13 +41081,39 @@ var MATERIAL_ACTIONS = [
41080
41081
  "create-procedural",
41081
41082
  "import"
41082
41083
  ];
41083
- var REQUIREMENTS2 = {
41084
- presets: { required: [] },
41085
- "create-preset": { required: ["presetId", "seed", "creator", "license"] },
41086
- list: { required: [] },
41087
- get: { required: ["materialId"] },
41088
- "create-procedural": { required: ["draft"], shapes: { draft: "shape:material-draft" } },
41089
- import: { required: ["payload"], shapes: { payload: "shape:material-import" } }
41084
+ var MATERIAL_CONTRACT = {
41085
+ presets: {
41086
+ fields: ["tag"],
41087
+ required: [],
41088
+ description: "List shipped material recipes, optionally filtered by tag."
41089
+ },
41090
+ "create-preset": {
41091
+ fields: ["presetId", "seed", "creator", "license", "size", "materialId"],
41092
+ required: ["presetId", "seed", "creator", "license"],
41093
+ description: "Create an immutable material from a shipped preset with explicit seed, creator and license."
41094
+ },
41095
+ list: {
41096
+ fields: ["tag"],
41097
+ required: [],
41098
+ description: "List saved immutable material revisions, optionally filtered by tag."
41099
+ },
41100
+ get: {
41101
+ fields: ["materialId", "revisionId"],
41102
+ required: ["materialId"],
41103
+ description: "Read a material revision, provenance and portable specification. Omit revisionId only when exactly one revision exists."
41104
+ },
41105
+ "create-procedural": {
41106
+ fields: ["draft"],
41107
+ required: ["draft"],
41108
+ shapes: { draft: "shape:material-draft" },
41109
+ description: "Create an immutable material from a bounded procedural draft. Does not fetch URLs or execute source."
41110
+ },
41111
+ import: {
41112
+ fields: ["payload"],
41113
+ required: ["payload"],
41114
+ shapes: { payload: "shape:material-import" },
41115
+ description: "Import complete normalized material records and embedded PNGs. Does not fetch URLs or execute source."
41116
+ }
41090
41117
  };
41091
41118
  var record7 = (description) => z18.record(z18.string(), z18.unknown()).optional().describe(description);
41092
41119
  var tag = z18.string().min(1).max(80).optional().describe("presets, list: keep one tag.");
@@ -41108,13 +41135,13 @@ var materialResult = (material) => ({
41108
41135
  material,
41109
41136
  portableSpec: materialLibraryPortableSpec(material)
41110
41137
  });
41111
- var unknownMaterial = (materialId, revisionId) => new Error(`Unknown material ${materialId}${revisionId ? ` at ${revisionId}` : ""}. kiln_material { action: 'list' } lists the materials in this workspace with their revision IDs.`);
41112
- async function onlyRevision(library, materialId) {
41138
+ var unknownMaterial = (materialId, revisionId, listCall) => new Error(`Unknown material ${materialId}${revisionId ? ` at ${revisionId}` : ""}. ${listCall} lists the materials in this workspace with their revision IDs.`);
41139
+ async function onlyRevision(library, materialId, listCall) {
41113
41140
  const revisions = (await library.list()).filter((material) => material.materialId === materialId).map((material) => material.revisionId).sort();
41114
41141
  if (revisions.length === 1)
41115
41142
  return revisions[0];
41116
41143
  if (revisions.length === 0)
41117
- throw unknownMaterial(materialId);
41144
+ throw unknownMaterial(materialId, undefined, listCall);
41118
41145
  throw new Error(`Material ${materialId} has ${revisions.length} revisions: ${revisions.join(", ")}. Pass revisionId to get one.`);
41119
41146
  }
41120
41147
  function aliasMaterialId(raw) {
@@ -41127,11 +41154,13 @@ function aliasMaterialId(raw) {
41127
41154
  throw new Error(`kiln_material: resourceId (${String(resourceId)}) and materialId (${String(rest.materialId)}) name different materials; pass materialId alone.`);
41128
41155
  return { ...rest, materialId: resourceId };
41129
41156
  }
41130
- function createKilnMaterialDef(library) {
41157
+ function createKilnMaterialDef(library, presentation = "grouped") {
41158
+ const listCall = presentation === "operations" ? 'kiln_material_search({ scope: "saved" })' : "kiln_material { action: 'list' }";
41131
41159
  return {
41132
41160
  name: "kiln_material",
41133
41161
  description: "Manage optional immutable material resources in this workspace. presets discovers shipped architecture, wood, metal, fabric and ground recipes; create-preset bakes one with an explicit seed, creator and license; list returns compact material/revision summaries; get returns full provenance, hashes, map conventions, physical repeat scale and a code-ready portable material spec; create-procedural bakes bounded editable layer recipes including optional height-derived normals; import accepts complete normalized records with embedded PNG bytes. No action downloads URLs or executes source. Pin the returned materialId/revisionId through per-invocation materialDependencies or project dependencies before authored evaluation resolves the resources; no project is required.",
41134
41162
  inputSchema: materialToolInput,
41163
+ actionContract: MATERIAL_CONTRACT,
41135
41164
  annotations: {
41136
41165
  readOnlyHint: false,
41137
41166
  destructiveHint: false,
@@ -41141,7 +41170,7 @@ function createKilnMaterialDef(library) {
41141
41170
  async run(raw) {
41142
41171
  assertMaterialJson(raw);
41143
41172
  const input = materialToolInput.parse(aliasMaterialId(raw));
41144
- requireActionFields("kiln_material", input.action, input, REQUIREMENTS2[input.action]);
41173
+ requireActionFields("kiln_material", input.action, input, MATERIAL_CONTRACT[input.action]);
41145
41174
  switch (input.action) {
41146
41175
  case "presets":
41147
41176
  return {
@@ -41177,12 +41206,12 @@ function createKilnMaterialDef(library) {
41177
41206
  };
41178
41207
  }
41179
41208
  case "get": {
41180
- const revisionId = input.revisionId ?? await onlyRevision(library, input.materialId);
41209
+ const revisionId = input.revisionId ?? await onlyRevision(library, input.materialId, listCall);
41181
41210
  try {
41182
41211
  return materialResult((await library.read(input.materialId, revisionId)).manifest);
41183
41212
  } catch (error) {
41184
41213
  if (error.code === "ENOENT")
41185
- throw unknownMaterial(input.materialId, revisionId);
41214
+ throw unknownMaterial(input.materialId, revisionId, listCall);
41186
41215
  throw error;
41187
41216
  }
41188
41217
  }
@@ -41233,7 +41262,7 @@ async function persistedPreviewFidelity(fidelity, glb) {
41233
41262
  var operationId = z19.string().regex(/^op_[a-f0-9-]{36}$/);
41234
41263
  var assetId = z19.string().regex(/^[a-z][a-z0-9_-]{0,79}$/);
41235
41264
  var REVIEW_ACTIONS = ["list", "get", "pin", "save"];
41236
- var REQUIREMENTS3 = {
41265
+ var REQUIREMENTS2 = {
41237
41266
  list: { required: [] },
41238
41267
  get: { required: ["operationId"] },
41239
41268
  pin: { required: ["operationId", "pinned"] },
@@ -41315,7 +41344,7 @@ function createKilnReviewDef(context) {
41315
41344
  },
41316
41345
  async run(raw) {
41317
41346
  const input = reviewToolInput.parse(raw);
41318
- requireActionFields("kiln_review", input.action, input, REQUIREMENTS3[input.action]);
41347
+ requireActionFields("kiln_review", input.action, input, REQUIREMENTS2[input.action]);
41319
41348
  if (input.action === "list") {
41320
41349
  const snapshot = await store.snapshot({ projectId: input.projectId });
41321
41350
  const offset = input.offset ?? 0;
@@ -41487,6 +41516,191 @@ function withWorkspaceContext(def, context) {
41487
41516
  };
41488
41517
  }
41489
41518
 
41519
+ // src/tools/operations.ts
41520
+ import { z as z22 } from "zod";
41521
+
41522
+ // src/tools/operation-contract.ts
41523
+ import { z as z21 } from "zod";
41524
+ function projectActionSchemas(name, input, actions) {
41525
+ if (input.def.checks?.length)
41526
+ throw new Error(`${name}: cross-field constraints require an explicit operation schema`);
41527
+ const json = z21.toJSONSchema(input, { io: "input" });
41528
+ const selector = json.properties?.action;
41529
+ const expected = Object.keys(actions).sort();
41530
+ if (!Array.isArray(selector?.enum) || JSON.stringify([...selector.enum].sort()) !== JSON.stringify(expected))
41531
+ throw new Error(`${name}: unclassified actions`);
41532
+ const classified = new Set([
41533
+ "action",
41534
+ ...Object.values(actions).flatMap((contract) => [...contract.fields])
41535
+ ]);
41536
+ const unclassified = Object.keys(input.shape).filter((field) => !classified.has(field));
41537
+ if (unclassified.length)
41538
+ throw new Error(`${name}: Unclassified fields: ${unclassified.join(", ")}`);
41539
+ const schemas = [];
41540
+ for (const [action, contract] of Object.entries(actions)) {
41541
+ for (const field of contract.required)
41542
+ if (!contract.fields.includes(field))
41543
+ throw new Error(`${name}.${action}: unmapped required ${field}`);
41544
+ for (const field of json.required ?? [])
41545
+ if (field !== "action" && !contract.fields.includes(field))
41546
+ throw new Error(`${name}.${action}: would omit required ${field}`);
41547
+ const shape = {};
41548
+ for (const field of contract.fields) {
41549
+ if (field === "action" || !Object.hasOwn(input.shape, field))
41550
+ throw new Error(`${name}.${action}: missing or reserved field ${field}`);
41551
+ classified.add(field);
41552
+ const source = input.shape[field];
41553
+ shape[field] = contract.required.includes(field) ? source.nonoptional() : source;
41554
+ }
41555
+ schemas.push([action, z21.strictObject(shape)]);
41556
+ }
41557
+ return Object.fromEntries(schemas);
41558
+ }
41559
+
41560
+ // src/tools/operations.ts
41561
+ function projectOperationDefs(defs, context) {
41562
+ return defs.flatMap((def) => {
41563
+ if (!context.assetLibrary && ["kiln_save", "kiln_assets", "kiln_present", "kiln_export", "kiln_import"].includes(def.name))
41564
+ return [];
41565
+ if (def.name === "kiln_renderer")
41566
+ return rendererOperations(def, context);
41567
+ if (def.name === "kiln_discover")
41568
+ return discoveryOperations(def);
41569
+ if (def.name === "kiln_material")
41570
+ return materialOperations(def);
41571
+ if (def.name === "kiln_assets")
41572
+ return assetOperations(def, context);
41573
+ return [def];
41574
+ });
41575
+ }
41576
+ function actionTools(def) {
41577
+ if (!(def.inputSchema instanceof z22.ZodObject) || !def.actionContract)
41578
+ throw new Error(`${def.name}: missing shared action contract`);
41579
+ const schemas = projectActionSchemas(def.name, def.inputSchema, def.actionContract);
41580
+ return Object.fromEntries(Object.entries(def.actionContract).map(([action, contract]) => [
41581
+ action,
41582
+ {
41583
+ ...def,
41584
+ actionContract: undefined,
41585
+ name: `${def.name}_${action.replaceAll("-", "_")}`,
41586
+ description: contract.description,
41587
+ inputSchema: schemas[action],
41588
+ run: async (raw) => def.run({ ...schemas[action].parse(raw), action })
41589
+ }
41590
+ ]));
41591
+ }
41592
+ function assetOperations(def, context) {
41593
+ const tools = actionTools(def);
41594
+ const list = tools.list;
41595
+ const listSchema = list.inputSchema;
41596
+ const collection = listSchema.shape.collection.unwrap().optional().describe("Collection ID from kiln_assets_collections; omitted searches all configured collections.");
41597
+ const inputSchema = listSchema.extend({ collection });
41598
+ const search = {
41599
+ ...list,
41600
+ name: "kiln_assets_search",
41601
+ description: "Search saved assets with pagination. Omit collection for all configured collections, or select one explicitly. Results identify exact collection, asset and revision; partial read failures are reported.",
41602
+ inputSchema,
41603
+ run: async (raw) => {
41604
+ const input = inputSchema.parse(raw);
41605
+ const { collection: _collection, ...filters } = input;
41606
+ if (input.collection === undefined)
41607
+ return tools.catalog.run(filters);
41608
+ if (!context.assetLibrary?.collections().some((item) => item.id === input.collection))
41609
+ throw new Error("Unknown collection. Use kiln_assets_collections to list available destinations.");
41610
+ const result = await list.run(input);
41611
+ return {
41612
+ errors: [],
41613
+ total: result.total,
41614
+ nextOffset: result.nextOffset,
41615
+ assets: result.assets.map((asset) => ({ ...asset, collection: input.collection }))
41616
+ };
41617
+ }
41618
+ };
41619
+ return [tools.collections, search, tools.get, tools.restore];
41620
+ }
41621
+ function materialOperations(def) {
41622
+ const tools = actionTools(def);
41623
+ const presets = tools.presets;
41624
+ const inputSchema = presets.inputSchema.extend({
41625
+ scope: z22.enum(["all", "presets", "saved"]).default("all")
41626
+ });
41627
+ const search = {
41628
+ ...presets,
41629
+ name: "kiln_material_search",
41630
+ description: "Browse shipped material recipes and saved material revisions, optionally filtered by exact tag. Recipe IDs and saved material IDs are distinct. Use scope to select presets, saved or both.",
41631
+ inputSchema,
41632
+ run: async (raw) => {
41633
+ const { scope, ...input } = inputSchema.parse(raw);
41634
+ const recipes = scope === "saved" ? { presets: [] } : await presets.run(input);
41635
+ const saved = scope === "presets" ? { materials: [] } : await tools.list.run(input);
41636
+ return {
41637
+ ok: true,
41638
+ presets: recipes.presets.map((item) => ({ ...item, kind: "preset" })),
41639
+ materials: saved.materials.map((item) => ({ ...item, kind: "saved" }))
41640
+ };
41641
+ }
41642
+ };
41643
+ const preset = tools["create-preset"];
41644
+ const procedural = tools["create-procedural"];
41645
+ const createInput = z22.strictObject({
41646
+ definition: z22.discriminatedUnion("kind", [
41647
+ preset.inputSchema.extend({ kind: z22.literal("preset") }),
41648
+ procedural.inputSchema.extend({ kind: z22.literal("procedural") })
41649
+ ])
41650
+ });
41651
+ const create = {
41652
+ ...preset,
41653
+ name: "kiln_material_create",
41654
+ description: "Create and retain one immutable material from a preset or procedural definition. Presets require explicit seed, creator and license. Pin the returned materialId/revisionId for rendering. Does not fetch URLs or execute source.",
41655
+ inputSchema: createInput,
41656
+ run: async (raw) => {
41657
+ const { kind, ...input } = createInput.parse(raw).definition;
41658
+ return (kind === "preset" ? preset : procedural).run(input);
41659
+ }
41660
+ };
41661
+ return [search, tools.get, create, tools.import];
41662
+ }
41663
+ function rendererOperations(def, context) {
41664
+ if (!context.renderCapabilities)
41665
+ return [];
41666
+ return ["status", ...context.reprobeRenderer ? ["reprobe"] : []].map((action) => {
41667
+ const inputSchema = z22.strictObject({});
41668
+ return {
41669
+ ...def,
41670
+ name: `kiln_renderer_${action}`,
41671
+ inputSchema,
41672
+ run: async (raw) => {
41673
+ inputSchema.parse(raw);
41674
+ return def.run({ action });
41675
+ }
41676
+ };
41677
+ });
41678
+ }
41679
+ function discoveryOperations(def) {
41680
+ const original = def.inputSchema;
41681
+ const { capabilities: _capabilities, ...fields } = original.shape;
41682
+ const inputSchema = z22.strictObject(fields);
41683
+ const empty = z22.strictObject({});
41684
+ return [
41685
+ {
41686
+ ...def,
41687
+ description: "Discover geometry helpers, assemblies, recipes and complete input shapes. Search with modeling language or fetch up to six exact IDs. Use kiln_capabilities for current host facts.",
41688
+ inputSchema,
41689
+ run: async (raw) => def.run(inputSchema.parse(raw))
41690
+ },
41691
+ {
41692
+ ...def,
41693
+ name: "kiln_capabilities",
41694
+ description: "Inspect declared host capabilities, execution limits and available storage. Observed rendering fidelity is returned by each render.",
41695
+ inputSchema: empty,
41696
+ run: async (raw) => {
41697
+ empty.parse(raw);
41698
+ return def.run({ capabilities: true });
41699
+ }
41700
+ }
41701
+ ];
41702
+ }
41703
+
41490
41704
  // src/tools/registry.ts
41491
41705
  init_validation();
41492
41706
  init_source_check();
@@ -41952,11 +42166,11 @@ function derivativeReviewFidelity(receipts) {
41952
42166
  ...reasonCodes.length ? { reasonCodes } : {}
41953
42167
  };
41954
42168
  }
41955
- var validateInput = z21.object({
41956
- code: z21.string().describe("Kiln source code (defines `meta` + `build()`, optional `animate()`).")
42169
+ var validateInput = z23.object({
42170
+ code: z23.string().describe("Kiln source code (defines `meta` + `build()`, optional `animate()`).")
41957
42171
  });
41958
- var renderInput = z21.object({
41959
- code: z21.string().describe("Kiln source code to execute and render to an in-memory GLB.")
42172
+ var renderInput = z23.object({
42173
+ code: z23.string().describe("Kiln source code to execute and render to an in-memory GLB.")
41960
42174
  });
41961
42175
  var renderViewsInput = renderInput.extend({ capture: captureInput, detail: reviewDetailInput });
41962
42176
  var reviewDetailOptions = (context) => ({
@@ -41969,26 +42183,26 @@ var reviewDetailOptions = (context) => ({
41969
42183
  }
41970
42184
  });
41971
42185
  var renderViewsBufferInput = renderViewsInput.omit({ code: true });
41972
- var screenshotAnimationInput = z21.object({
42186
+ var screenshotAnimationInput = z23.object({
41973
42187
  shot: cameraShotInput.optional(),
41974
- measureParts: z21.array(cameraShotInput.shape.subject.unwrap()).min(1).max(16).optional().describe("Exact names or paths of subtrees measured together at each phase, independent of camera selection."),
41975
- frames: z21.number().int().min(2).max(6).optional(),
41976
- frameTimes: z21.array(z21.number().min(0).max(1)).min(1).max(9).optional().describe("Ordered phase fractions 0..1; mutually exclusive with frames."),
41977
- framing: z21.enum(["locked", "follow"]).optional(),
41978
- size: z21.number().int().min(128).max(1024).optional().describe("Frame size in px; default 256."),
42188
+ measureParts: z23.array(cameraShotInput.shape.subject.unwrap()).min(1).max(16).optional().describe("Exact names or paths of subtrees measured together at each phase, independent of camera selection."),
42189
+ frames: z23.number().int().min(2).max(6).optional(),
42190
+ frameTimes: z23.array(z23.number().min(0).max(1)).min(1).max(9).optional().describe("Ordered phase fractions 0..1; mutually exclusive with frames."),
42191
+ framing: z23.enum(["locked", "follow"]).optional(),
42192
+ size: z23.number().int().min(128).max(1024).optional().describe("Frame size in px; default 256."),
41979
42193
  detail: reviewDetailInput,
41980
- code: z21.string().describe("Kiln source code to execute; must define animate() returning the named clip."),
41981
- clip: z21.string().describe('The animation clip to view, by name (e.g. "walk", "attack"). Must be one your animate() returns.'),
41982
- camera: z21.string().optional().describe("Camera angle: right (default — side profile, best for leg swing + knee bend direction), front " + "(reveals sideways/lateral motion), back, left, top, or three-quarter."),
41983
- perFrame: z21.boolean().optional().describe("Return the frames as separate high-res images instead of one composite grid. Default false.")
42194
+ code: z23.string().describe("Kiln source code to execute; must define animate() returning the named clip."),
42195
+ clip: z23.string().describe('The animation clip to view, by name (e.g. "walk", "attack"). Must be one your animate() returns.'),
42196
+ camera: z23.string().optional().describe("Camera angle: right (default — side profile, best for leg swing + knee bend direction), front " + "(reveals sideways/lateral motion), back, left, top, or three-quarter."),
42197
+ perFrame: z23.boolean().optional().describe("Return the frames as separate high-res images instead of one composite grid. Default false.")
41984
42198
  });
41985
42199
  var screenshotAnimationAdvertisedInput = screenshotAnimationInput.extend({
41986
42200
  shot: cameraShotRecordInput
41987
42201
  });
41988
- var viewInteriorInput = z21.object({
42202
+ var viewInteriorInput = z23.object({
41989
42203
  capture: advancedCaptureInput.optional(),
41990
- code: z21.string().describe("Kiln source code to execute and render with the roof hidden."),
41991
- nodeName: z21.string().optional().describe("Override: lift the roof by exact node name instead of by role. Matches that node and its " + "children. Normally OMIT it — Kiln finds the roof from its semantic role (anything built " + 'with createRoofPlanes/createGableRoof), falling back to historical "Roof" naming.')
42204
+ code: z23.string().describe("Kiln source code to execute and render with the roof hidden."),
42205
+ nodeName: z23.string().optional().describe("Override: lift the roof by exact node name instead of by role. Matches that node and its " + "children. Normally OMIT it — Kiln finds the roof from its semantic role (anything built " + 'with createRoofPlanes/createGableRoof), falling back to historical "Roof" naming.')
41992
42206
  });
41993
42207
  var viewInteriorAdvertisedInput = viewInteriorInput.extend({ capture: captureRecordInput });
41994
42208
  function runValidate(input, context) {
@@ -42003,11 +42217,11 @@ function runValidate(input, context) {
42003
42217
  warnings: result.warnings.map((w) => w.fixHint ? `${w.message} (${w.fixHint})` : w.message)
42004
42218
  };
42005
42219
  }
42006
- var partListInput = z21.object({
42007
- query: z21.string().max(4096).optional().describe("Case-insensitive substring of name or exact encoded path; not a regex."),
42008
- offset: z21.number().int().min(0).optional(),
42009
- limit: z21.number().int().min(1).max(100).optional(),
42010
- placement: z21.boolean().optional().describe("Add world position, rotation, scale, mirroring and bounds per part; pages of 50.")
42220
+ var partListInput = z23.object({
42221
+ query: z23.string().max(4096).optional().describe("Case-insensitive substring of name or exact encoded path; not a regex."),
42222
+ offset: z23.number().int().min(0).optional(),
42223
+ limit: z23.number().int().min(1).max(100).optional(),
42224
+ placement: z23.boolean().optional().describe("Add world position, rotation, scale, mirroring and bounds per part; pages of 50.")
42011
42225
  }).strict();
42012
42226
  var PLACEMENT_PAGE_LIMIT = 50;
42013
42227
  async function listPartNames2(root, options = {}) {
@@ -42474,35 +42688,35 @@ function createKilnViewInteriorDef(context = {}) {
42474
42688
  };
42475
42689
  }
42476
42690
  var kilnViewInteriorDef = createKilnViewInteriorDef();
42477
- var attachmentEndpointInput = z21.object({
42478
- subject: z21.object({ path: z21.string().optional(), name: z21.string().optional() }).strict(),
42691
+ var attachmentEndpointInput = z23.object({
42692
+ subject: z23.object({ path: z23.string().optional(), name: z23.string().optional() }).strict(),
42479
42693
  point: cameraVec3Input.optional()
42480
42694
  }).strict();
42481
- var surfacePairInput = z21.array(z21.string().max(4096)).length(2);
42482
- var inspectInput = z21.object({
42483
- image: z21.boolean().optional().describe("False: requires listParts/measure/surfacePairs/compare; no image or camera controls. Default true."),
42695
+ var surfacePairInput = z23.array(z23.string().max(4096)).length(2);
42696
+ var inspectInput = z23.object({
42697
+ image: z23.boolean().optional().describe("False: requires listParts/measure/surfacePairs/compare; no image or camera controls. Default true."),
42484
42698
  detail: reviewDetailInput,
42485
42699
  listParts: partListInput.optional().describe("List exported-scene paths, including nested parts. Default 80, max 100 per page. Follow partListing.nextOffset with the same programRef/query. image:false avoids rendering."),
42486
- surfacePairs: z21.array(surfacePairInput).min(1).max(12).optional().describe("[from,to] pairs of exact listParts paths or unambiguous node names; check surfaceMeasurements.status and each result."),
42487
- compare: z21.object({
42488
- programRef: z21.string().regex(programRefPattern),
42489
- offset: z21.number().int().min(0).optional(),
42490
- limit: z21.number().int().min(1).max(100).optional(),
42491
- paths: z21.array(z21.string().max(4096)).min(1).max(12).optional().describe("Exact baseline node paths, scene-prefixed without primitive children. Complete subtree summaries.")
42700
+ surfacePairs: z23.array(surfacePairInput).min(1).max(12).optional().describe("[from,to] pairs of exact listParts paths or unambiguous node names; check surfaceMeasurements.status and each result."),
42701
+ compare: z23.object({
42702
+ programRef: z23.string().regex(programRefPattern),
42703
+ offset: z23.number().int().min(0).optional(),
42704
+ limit: z23.number().int().min(1).max(100).optional(),
42705
+ paths: z23.array(z23.string().max(4096)).min(1).max(12).optional().describe("Exact baseline node paths, scene-prefixed without primitive children. Complete subtree summaries.")
42492
42706
  }).strict().optional().describe("Static geometry/material/transform/bounds under current host settings. Follow nextOffset; paths adds complete subtrees."),
42493
- measure: z21.object({
42494
- mode: z21.enum(["anchors", "surface"]).optional(),
42707
+ measure: z23.object({
42708
+ mode: z23.enum(["anchors", "surface"]).optional(),
42495
42709
  from: attachmentEndpointInput,
42496
42710
  to: attachmentEndpointInput
42497
42711
  }).strict().optional().describe("Default anchors: origin/local-point distance. Surface: disjoint mesh triangles, omit points. Rest pose, asset units. Check status/bounds; no solid clearance/attachment proof."),
42498
42712
  shot: cameraShotInput.optional().describe("Exact shot; omit part/view/orbit controls."),
42499
- code: z21.string().describe("Kiln source code to execute and inspect."),
42500
- part: z21.string().optional().describe("Frame named part and descendants (case-insensitive, substring fallback). Omit for whole asset."),
42501
- view: z21.string().optional().describe("front/right/back/left/top/three-quarter (default). Orbit angles override."),
42502
- azimuthDeg: z21.number().optional().describe("Orbit degrees: 0 front, 90 right, 180 back, 270 left. Wraps."),
42503
- elevationDeg: z21.number().optional().describe("Elevation degrees: 0 eye level, positive above. Clamped -89..89."),
42504
- zoom: z21.number().optional().describe("Bounds padding 1..4; default 1.2. Larger = more context."),
42505
- isolate: z21.boolean().optional().describe("Hide surrounding geometry. Requires part; default false.")
42713
+ code: z23.string().describe("Kiln source code to execute and inspect."),
42714
+ part: z23.string().optional().describe("Frame named part and descendants (case-insensitive, substring fallback). Omit for whole asset."),
42715
+ view: z23.string().optional().describe("front/right/back/left/top/three-quarter (default). Orbit angles override."),
42716
+ azimuthDeg: z23.number().optional().describe("Orbit degrees: 0 front, 90 right, 180 back, 270 left. Wraps."),
42717
+ elevationDeg: z23.number().optional().describe("Elevation degrees: 0 eye level, positive above. Clamped -89..89."),
42718
+ zoom: z23.number().optional().describe("Bounds padding 1..4; default 1.2. Larger = more context."),
42719
+ isolate: z23.boolean().optional().describe("Hide surrounding geometry. Requires part; default false.")
42506
42720
  });
42507
42721
  var inspectBufferInput = inspectInput.omit({ code: true });
42508
42722
  var inspectAdvertisedInput = inspectInput.extend({ shot: cameraShotRecordInput });
@@ -42662,15 +42876,15 @@ function createKilnInspectDef(context = {}) {
42662
42876
  };
42663
42877
  }
42664
42878
  var kilnInspectDef = createKilnInspectDef();
42665
- var editOperationInput = z21.object({
42666
- oldString: z21.string().describe("The exact text to replace, copied verbatim from the program (including whitespace and indentation, and with no line-number prefixes). Must be unique unless replaceAll is true."),
42667
- newString: z21.string().describe("The replacement text. Use an empty string to delete."),
42668
- replaceAll: z21.boolean().optional().describe("Replace every occurrence instead of failing when oldString matches more than once.")
42879
+ var editOperationInput = z23.object({
42880
+ oldString: z23.string().describe("The exact text to replace, copied verbatim from the program (including whitespace and indentation, and with no line-number prefixes). Must be unique unless replaceAll is true."),
42881
+ newString: z23.string().describe("The replacement text. Use an empty string to delete."),
42882
+ replaceAll: z23.boolean().optional().describe("Replace every occurrence instead of failing when oldString matches more than once.")
42669
42883
  });
42670
- var editInput = z21.object({
42671
- code: z21.string().describe("The Kiln program to patch. The full current source."),
42672
- edits: z21.array(editOperationInput).min(1).max(20).describe("Edits applied in order against the program. If any one fails to match, none are applied and the reply says which. Batch related changes into a single call."),
42673
- render: z21.boolean().optional().describe("Render the patched program and return the views (default true). false = patch only."),
42884
+ var editInput = z23.object({
42885
+ code: z23.string().describe("The Kiln program to patch. The full current source."),
42886
+ edits: z23.array(editOperationInput).min(1).max(20).describe("Edits applied in order against the program. If any one fails to match, none are applied and the reply says which. Batch related changes into a single call."),
42887
+ render: z23.boolean().optional().describe("Render the patched program and return the views (default true). false = patch only."),
42674
42888
  capture: captureInput,
42675
42889
  detail: reviewDetailInput
42676
42890
  });
@@ -42896,24 +43110,27 @@ function withCaptureCache(context) {
42896
43110
  } : {}
42897
43111
  };
42898
43112
  }
42899
- var rendererInput = z21.strictObject({
42900
- action: z21.enum(["status", "reprobe"]).default("status")
43113
+ var rendererInput = z23.strictObject({
43114
+ action: z23.enum(["status", "reprobe"]).default("status")
42901
43115
  });
42902
- async function readSavedRevision(target, collection, assetId, revisionId) {
43116
+ async function readSavedRevision(target, collection, assetId, revisionId, listCall = `kiln_assets { action: 'list', collection: '${collection}' }`) {
42903
43117
  try {
42904
43118
  return await target.read(collection, assetId, revisionId);
42905
43119
  } catch (error) {
42906
43120
  if (error.code === "ENOENT")
42907
- throw new Error(`Unknown asset ${assetId} revision ${revisionId} in collection ${collection}. kiln_assets { action: 'list', collection: '${collection}' } lists the saved revisions.`);
43121
+ throw new Error(`Unknown asset ${assetId} revision ${revisionId} in collection ${collection}. ${listCall} lists the saved revisions.`);
42908
43122
  throw error;
42909
43123
  }
42910
43124
  }
42911
43125
  function createKilnProgramToolRegistry(suppliedContext = {}) {
43126
+ return createProgramDefs(suppliedContext, "grouped");
43127
+ }
43128
+ function createProgramDefs(suppliedContext, presentation) {
42912
43129
  toolRequirements(suppliedContext);
42913
43130
  const store = suppliedContext.programStore ?? new MemoryProgramStore;
42914
43131
  const context = withCaptureCache(withBuildCache({ ...suppliedContext, programStore: store }));
42915
43132
  return [
42916
- createKilnDiscoveryDef({ ...suppliedContext, programStore: store }),
43133
+ createKilnDiscoveryDef({ ...suppliedContext, programStore: store }, presentation),
42917
43134
  {
42918
43135
  name: "kiln_renderer",
42919
43136
  description: "Inspect status, or reprobe after renderer setup/repair to refresh this session and reset failed starts. Never installs, starts, stops or renders. Preserves CPU/local/remote selection; environment/credential changes require a host restart. Read viewFidelity after rendering.",
@@ -42953,7 +43170,7 @@ function createKilnProgramToolRegistry(suppliedContext = {}) {
42953
43170
  ].map((def) => withProgramReferences(def, store, context.readSourceFile)),
42954
43171
  createKilnSourceDef(store),
42955
43172
  ...context.projectStore ? [createKilnProjectDef(context.projectStore, context.projectBundleReader)] : [],
42956
- ...context.materialLibrary ? [createKilnMaterialDef(context.materialLibrary)] : [],
43173
+ ...context.materialLibrary ? [createKilnMaterialDef(context.materialLibrary, presentation)] : [],
42957
43174
  ...context.reviewStore && context.assetLibrary ? [
42958
43175
  createKilnReviewDef({
42959
43176
  reviewStore: context.reviewStore,
@@ -42961,7 +43178,7 @@ function createKilnProgramToolRegistry(suppliedContext = {}) {
42961
43178
  requirements: context.requirements
42962
43179
  })
42963
43180
  ] : [],
42964
- ...createKilnAssetDefs({ ...context, programStore: store })
43181
+ ...createKilnAssetDefs({ ...context, programStore: store }, presentation)
42965
43182
  ].map((def) => withWorkspaceContext(def, context)).map((def) => ({
42966
43183
  ...def,
42967
43184
  annotations: {
@@ -42973,15 +43190,45 @@ function createKilnProgramToolRegistry(suppliedContext = {}) {
42973
43190
  }
42974
43191
  }));
42975
43192
  }
43193
+ function createKilnOperationToolRegistry(context = {}) {
43194
+ return projectOperationDefs(createProgramDefs(context, "operations"), context);
43195
+ }
42976
43196
  var assetSelector = {
42977
- collection: z21.string().regex(/^[a-z][a-z0-9_-]{0,79}$/).describe("Collection ID (list with kiln_assets action=collections): the user destination, else project.").default("project"),
42978
- assetId: z21.string().regex(/^[a-z][a-z0-9_-]{0,79}$/),
42979
- revisionId: z21.string().regex(/^[a-z][a-z0-9_-]{0,79}$/)
43197
+ collection: z23.string().regex(/^[a-z][a-z0-9_-]{0,79}$/).describe("Collection ID (list with kiln_assets action=collections): the user destination, else project.").default("project"),
43198
+ assetId: z23.string().regex(/^[a-z][a-z0-9_-]{0,79}$/),
43199
+ revisionId: z23.string().regex(/^[a-z][a-z0-9_-]{0,79}$/)
42980
43200
  };
42981
- async function newestRevision(target, collection, assetId) {
43201
+ var ASSET_CONTRACT = {
43202
+ collections: {
43203
+ fields: [],
43204
+ required: [],
43205
+ description: "List configured asset storage collections. Collection is not project membership."
43206
+ },
43207
+ catalog: {
43208
+ fields: ["assetId", "query", "offset", "limit"],
43209
+ required: [],
43210
+ description: "Search saved assets across configured collections with pagination."
43211
+ },
43212
+ list: {
43213
+ fields: ["collection", "assetId", "query", "offset", "limit"],
43214
+ required: [],
43215
+ description: "Search saved assets in one collection with pagination."
43216
+ },
43217
+ get: {
43218
+ fields: ["collection", "assetId", "revisionId"],
43219
+ required: ["assetId"],
43220
+ description: "Read a saved revision and its download descriptors. Omit revisionId for the newest revision."
43221
+ },
43222
+ restore: {
43223
+ fields: ["collection", "assetId", "revisionId"],
43224
+ required: ["assetId"],
43225
+ description: "Restore exact saved source as programRef for editing or rendering. Omit revisionId for the newest revision. Does not overwrite the saved revision. Binary-only assets have no source."
43226
+ }
43227
+ };
43228
+ async function newestRevision(target, collection, assetId, listCall = `kiln_assets { action: 'list', collection: '${collection}' }`) {
42982
43229
  const revisions = (await target.list(collection)).filter((a) => a.assetId === assetId);
42983
43230
  if (!revisions.length)
42984
- throw new Error(`Unknown asset ${assetId} in ${collection}; kiln_assets { action: 'list', collection: '${collection}' } lists them.`);
43231
+ throw new Error(`Unknown asset ${assetId} in ${collection}; ${listCall} lists them.`);
42985
43232
  const parents = new Set(revisions.map((a) => a.parentRevision).filter(Boolean));
42986
43233
  const heads = revisions.filter((a) => !parents.has(a.revisionId));
42987
43234
  const newest = (heads.length ? heads : revisions).sort((a, b) => a.createdAt < b.createdAt ? 1 : a.createdAt > b.createdAt ? -1 : 0);
@@ -43046,7 +43293,12 @@ async function buildProgramAssetDraft(code, context, backdrop) {
43046
43293
  }
43047
43294
  };
43048
43295
  }
43049
- function createKilnAssetDefs(context) {
43296
+ function createKilnAssetDefs(context, presentation = "grouped") {
43297
+ const listCall = (collection) => presentation === "operations" ? `kiln_assets_search({ collection: '${collection}' })` : `kiln_assets { action: 'list', collection: '${collection}' }`;
43298
+ const selector = presentation === "operations" ? {
43299
+ ...assetSelector,
43300
+ collection: assetSelector.collection.describe("Collection ID from kiln_assets_collections; default project.")
43301
+ } : assetSelector;
43050
43302
  const library = () => {
43051
43303
  if (!context.assetLibrary)
43052
43304
  throw new Error("No asset library configured. The local CLI/MCP host supplies workspace collections; embedded hosts must inject assetLibrary.");
@@ -43074,38 +43326,38 @@ function createKilnAssetDefs(context) {
43074
43326
  resources: (await Promise.resolve().then(() => (init_assets_resources(), exports_assets_resources))).assetLinks(collection, asset),
43075
43327
  downloadUrls: await context.assetDownloadUrls?.(collection, asset.assetId, asset.revisionId)
43076
43328
  });
43077
- const saveInput = z21.object({
43078
- collection: assetSelector.collection,
43079
- programRef: z21.string(),
43080
- name: z21.string().min(1).max(200),
43081
- assetId: assetSelector.assetId.optional(),
43082
- parentRevision: assetSelector.revisionId.optional(),
43083
- tags: z21.array(z21.string().max(80)).max(30).optional(),
43084
- brief: z21.string().max(8000).optional(),
43085
- description: z21.string().max(4000).optional(),
43086
- attribution: z21.object({
43087
- model: z21.string().max(200).optional(),
43088
- harness: z21.string().max(200).optional(),
43089
- author: z21.string().max(200).optional()
43329
+ const saveInput = z23.object({
43330
+ collection: selector.collection,
43331
+ programRef: z23.string(),
43332
+ name: z23.string().min(1).max(200),
43333
+ assetId: selector.assetId.optional(),
43334
+ parentRevision: selector.revisionId.optional(),
43335
+ tags: z23.array(z23.string().max(80)).max(30).optional(),
43336
+ brief: z23.string().max(8000).optional(),
43337
+ description: z23.string().max(4000).optional(),
43338
+ attribution: z23.object({
43339
+ model: z23.string().max(200).optional(),
43340
+ harness: z23.string().max(200).optional(),
43341
+ author: z23.string().max(200).optional()
43090
43342
  }).optional(),
43091
- backdrop: z21.enum(BACKDROP_IDS).optional().describe("Preview backdrop: the one the reviewed sheet used.")
43092
- });
43093
- const assetsInput = z21.object({
43094
- action: z21.enum(["collections", "catalog", "list", "get", "restore"]).default("list"),
43095
- collection: assetSelector.collection,
43096
- assetId: assetSelector.assetId.optional(),
43097
- revisionId: assetSelector.revisionId.optional().describe("get, restore: the saved revision; omitted, the newest."),
43098
- query: z21.string().max(200).optional(),
43099
- offset: z21.number().int().min(0).default(0),
43100
- limit: z21.number().int().min(1).max(50).default(20)
43101
- });
43102
- const exportInput = z21.object(assetSelector);
43343
+ backdrop: z23.enum(BACKDROP_IDS).optional().describe("Preview backdrop: the one the reviewed sheet used.")
43344
+ });
43345
+ const assetsInput = z23.object({
43346
+ action: z23.enum(["collections", "catalog", "list", "get", "restore"]).default("list"),
43347
+ collection: selector.collection,
43348
+ assetId: selector.assetId.optional(),
43349
+ revisionId: selector.revisionId.optional().describe("get, restore: the saved revision; omitted, the newest."),
43350
+ query: z23.string().max(200).optional(),
43351
+ offset: z23.number().int().min(0).default(0),
43352
+ limit: z23.number().int().min(1).max(50).default(20)
43353
+ });
43354
+ const exportInput = z23.object(selector);
43103
43355
  const profileExportInput = exportInput.extend({
43104
- profile: z21.enum(["editable", "runtime"]).default("editable").describe("editable preserves canonical source/GLB/build resources. runtime returns a standalone GLB and versioned review-metadata sidecar; no source bundle or geometry optimization.")
43356
+ profile: z23.enum(["editable", "runtime"]).default("editable").describe("editable preserves canonical source/GLB/build resources. runtime returns a standalone GLB and versioned review-metadata sidecar; no source bundle or geometry optimization.")
43105
43357
  });
43106
- const importInput = z21.object({
43107
- ...assetSelector,
43108
- sourceCollection: assetSelector.collection
43358
+ const importInput = z23.object({
43359
+ ...selector,
43360
+ sourceCollection: selector.collection
43109
43361
  });
43110
43362
  return [
43111
43363
  {
@@ -43121,7 +43373,7 @@ function createKilnAssetDefs(context) {
43121
43373
  requirements: activeRequirements.binding
43122
43374
  };
43123
43375
  if (input.assetId && input.parentRevision) {
43124
- const previous = await readSavedRevision(target, input.collection, input.assetId, input.parentRevision);
43376
+ const previous = await readSavedRevision(target, input.collection, input.assetId, input.parentRevision, listCall(input.collection));
43125
43377
  assertSavedRequirementsAuthorized(previous.manifest, activeRequirements);
43126
43378
  }
43127
43379
  const code = await context.programStore.get(input.programRef);
@@ -43136,6 +43388,7 @@ function createKilnAssetDefs(context) {
43136
43388
  name: "kiln_assets",
43137
43389
  description: "Browse saved assets: collections discovers storage; catalog searches all configured collections; list searches one. Both searches paginate. get returns a build record/downloads; restore loads exact source for kiln_source/kiln_edit. Collection is not project membership. Binary-only assets cannot restore source.",
43138
43390
  inputSchema: assetsInput,
43391
+ actionContract: ASSET_CONTRACT,
43139
43392
  run: async (raw) => {
43140
43393
  const input = assetsInput.parse(raw);
43141
43394
  const activeRequirements = toolRequirements(context);
@@ -43180,10 +43433,10 @@ function createKilnAssetDefs(context) {
43180
43433
  }))
43181
43434
  };
43182
43435
  }
43183
- if (!input.assetId)
43436
+ if (ASSET_CONTRACT[input.action].required.some((field) => !input[field]))
43184
43437
  throw new Error(`kiln_assets ${input.action} requires assetId; kiln_assets { action: 'list', collection: '${input.collection}' } lists them.`);
43185
- const revisionId = input.revisionId ?? await newestRevision(target, input.collection, input.assetId);
43186
- const record = await readSavedRevision(target, input.collection, input.assetId, revisionId);
43438
+ const revisionId = input.revisionId ?? await newestRevision(target, input.collection, input.assetId, listCall(input.collection));
43439
+ const record = await readSavedRevision(target, input.collection, input.assetId, revisionId, listCall(input.collection));
43187
43440
  if (input.action === "get")
43188
43441
  return links(input.collection, record.manifest);
43189
43442
  const saved = assertSavedRequirementsAuthorized(record.manifest, activeRequirements);
@@ -43203,9 +43456,9 @@ function createKilnAssetDefs(context) {
43203
43456
  name: "kiln_present",
43204
43457
  description: "Present one exact saved revision. Supporting MCP App clients show an interactive 3D card with GLB, editable ZIP, and source downloads. Every host receives exact artifact descriptors with resource URIs in the JSON result; verified hosts may also receive core MCP resource-link blocks. This tool does not launch a local browser in coding harnesses. Call after saving or when the user wants to see or download an asset.",
43205
43458
  inputSchema: exportInput,
43206
- outputSchema: z21.object({
43207
- ok: z21.literal(true),
43208
- collection: z21.string(),
43459
+ outputSchema: z23.object({
43460
+ ok: z23.literal(true),
43461
+ collection: z23.string(),
43209
43462
  asset: assetManifestSchema.pick({
43210
43463
  assetId: true,
43211
43464
  revisionId: true,
@@ -43216,25 +43469,25 @@ function createKilnAssetDefs(context) {
43216
43469
  editable: true,
43217
43470
  files: true
43218
43471
  }).extend({
43219
- build: z21.object({
43220
- engine: z21.string(),
43221
- rebuild: z21.enum(["engine-required", "external-dependencies-required"]),
43222
- warningCount: z21.number().int(),
43223
- warnings: z21.array(z21.string())
43472
+ build: z23.object({
43473
+ engine: z23.string(),
43474
+ rebuild: z23.enum(["engine-required", "external-dependencies-required"]),
43475
+ warningCount: z23.number().int(),
43476
+ warnings: z23.array(z23.string())
43224
43477
  }).optional()
43225
43478
  }),
43226
- resources: z21.array(z21.object({
43227
- type: z21.literal("resource_link"),
43228
- name: z21.string(),
43229
- uri: z21.string(),
43230
- mimeType: z21.string(),
43231
- size: z21.number().int().nonnegative(),
43232
- annotations: z21.object({
43233
- audience: z21.array(z21.enum(["user", "assistant"])),
43234
- priority: z21.number()
43479
+ resources: z23.array(z23.object({
43480
+ type: z23.literal("resource_link"),
43481
+ name: z23.string(),
43482
+ uri: z23.string(),
43483
+ mimeType: z23.string(),
43484
+ size: z23.number().int().nonnegative(),
43485
+ annotations: z23.object({
43486
+ audience: z23.array(z23.enum(["user", "assistant"])),
43487
+ priority: z23.number()
43235
43488
  })
43236
43489
  })),
43237
- downloadUrls: z21.record(z21.string(), z21.string()).optional()
43490
+ downloadUrls: z23.record(z23.string(), z23.string()).optional()
43238
43491
  }),
43239
43492
  ui: {
43240
43493
  resourceUri: KILN_ASSET_WIDGET_URI2,
@@ -43251,7 +43504,7 @@ function createKilnAssetDefs(context) {
43251
43504
  inputSchema: profileExportInput,
43252
43505
  run: async (raw) => {
43253
43506
  const input = profileExportInput.parse(raw);
43254
- const record = await readSavedRevision(library(), input.collection, input.assetId, input.revisionId);
43507
+ const record = await readSavedRevision(library(), input.collection, input.assetId, input.revisionId, listCall(input.collection));
43255
43508
  await Promise.resolve().then(() => init_draw_diagnostics());
43256
43509
  if (input.profile === "runtime") {
43257
43510
  const output = await (await Promise.resolve().then(() => (init_asset_export(), exports_asset_export))).exportAssetGlb(record, {
@@ -43285,7 +43538,7 @@ function createKilnAssetDefs(context) {
43285
43538
  run: async (raw) => {
43286
43539
  const input = importInput.parse(raw);
43287
43540
  const target = library();
43288
- const record = await readSavedRevision(target, input.sourceCollection, input.assetId, input.revisionId);
43541
+ const record = await readSavedRevision(target, input.sourceCollection, input.assetId, input.revisionId, listCall(input.sourceCollection));
43289
43542
  await target.import(input.collection, [record]);
43290
43543
  return links(input.collection, record.manifest);
43291
43544
  }
@@ -43409,7 +43662,7 @@ function createKilnToolHost(context = {}, options = {}) {
43409
43662
  };
43410
43663
  }
43411
43664
  };
43412
- const defs = kilnMcpToolDefs(requestContext);
43665
+ const defs = options.toolPresentation === "operations" ? createKilnOperationToolRegistry(requestContext) : kilnMcpToolDefs(requestContext);
43413
43666
  const byName = new Map(defs.map((def) => [def.name, def]));
43414
43667
  return {
43415
43668
  defs,
@@ -43476,6 +43729,7 @@ function resourceText(name, bytes) {
43476
43729
  function createKilnMcpServer(context = {}, options = {}) {
43477
43730
  const host = createKilnToolHost(context, options);
43478
43731
  return createKilnServer({
43732
+ ...options.toolPresentation === "operations" ? { instructions: MCP_OPERATION_INSTRUCTIONS } : {},
43479
43733
  manifest: buildMcpManifest(host.defs, context),
43480
43734
  host: async () => host
43481
43735
  });