@instruktlabs/kiln 1.1.0 → 1.2.1

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 (56) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codex-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +28 -0
  4. package/dist/agent-run.mjs +224 -151
  5. package/dist/build.json +22 -22
  6. package/dist/cli.mjs +229 -151
  7. package/dist/mcp-engine.mjs +400 -147
  8. package/dist/mcp-server.mjs +6 -6
  9. package/dist/viewer/app.js +180 -180
  10. package/dist/viewer/chat.html +183 -183
  11. package/docs/sdk.md +23 -0
  12. package/docs/tools-operations.md +1529 -0
  13. package/lib/asset-widget-uri.d.ts +1 -1
  14. package/lib/asset-widget-uri.js +1 -1
  15. package/lib/discovery/index.d.ts +1 -1
  16. package/lib/discovery/index.js +2 -2
  17. package/lib/discovery/service.d.ts +3 -1
  18. package/lib/discovery/service.js +4 -2
  19. package/lib/engine-identity.d.ts +1 -1
  20. package/lib/engine-identity.js +1 -1
  21. package/lib/mcp-core.d.ts +2 -1
  22. package/lib/mcp-core.js +1 -0
  23. package/lib/mcp-engine.d.ts +2 -0
  24. package/lib/mcp-engine.js +8 -3
  25. package/lib/tools/discovery.d.ts +1 -1
  26. package/lib/tools/discovery.js +11 -9
  27. package/lib/tools/materials.d.ts +1 -1
  28. package/lib/tools/materials.js +44 -14
  29. package/lib/tools/operation-contract.d.ts +8 -0
  30. package/lib/tools/operation-contract.js +37 -0
  31. package/lib/tools/operations.d.ts +3 -0
  32. package/lib/tools/operations.js +154 -0
  33. package/lib/tools/registry.d.ts +7 -4
  34. package/lib/tools/registry.js +73 -24
  35. package/package.json +2 -1
  36. package/plugin.json +1 -1
  37. package/plugins/kiln-engine/.claude-plugin/plugin.json +1 -1
  38. package/plugins/kiln-engine/README.md +3 -3
  39. package/plugins/kiln-engine/package-provenance.json +6 -6
  40. package/plugins/kiln-engine/plugin.json +1 -1
  41. package/plugins/kiln-engine/runtime.json +1 -1
  42. package/src/asset-widget-uri.ts +1 -1
  43. package/src/discovery/index.ts +2 -0
  44. package/src/discovery/service.ts +3 -1
  45. package/src/engine-identity.ts +1 -1
  46. package/src/generated/mcp-manifest.json +4 -4
  47. package/src/mcp-core.ts +3 -0
  48. package/src/mcp-engine.ts +11 -1
  49. package/src/tools/discovery.ts +20 -10
  50. package/src/tools/materials.ts +59 -15
  51. package/src/tools/operation-contract.ts +51 -0
  52. package/src/tools/operations.ts +177 -0
  53. package/src/tools/registry.ts +102 -21
  54. package/src/viewer/chat-app.ts +6 -2
  55. package/src/viewer/glb-loader.ts +78 -0
  56. package/src/viewer/scene.ts +9 -26
@@ -13,6 +13,7 @@ import { createKilnMaterialDef } from "./materials.js";
13
13
  import { createKilnReviewDef } from "./review.js";
14
14
  import { withWorkspaceContext } from "./workspace.js";
15
15
  import { describeInputError } from "./actions.js";
16
+ import { projectOperationDefs } from "./operations.js";
16
17
  import { advancedCaptureInput, cameraShotInput, cameraShotRecordInput, cameraVec3Input, captureInput, captureRecordInput, } from "./capture-input.js";
17
18
  import { compactEditResult, compactReviewResult, partsHint, requestedDetail, reviewDetailInput, } from "./review-detail.js";
18
19
  import { createCachedEvaluatorPort, MemoryBuildCache } from "../build-cache.js";
@@ -34,7 +35,8 @@ import { persistedPreviewFidelity } from "./preview-fidelity.js";
34
35
  // =============================================================================
35
36
  // Tool definition contract
36
37
  // =============================================================================
37
- export const KILN_ASSET_WIDGET_URI = 'ui://kiln/asset-v5.html';
38
+ export { KILN_ASSET_WIDGET_URI } from "../asset-widget-uri.js";
39
+ import { KILN_ASSET_WIDGET_URI } from "../asset-widget-uri.js";
38
40
  function proceduralTextureMaterialContract(rendered, context) {
39
41
  const required = [...new Set(context.requiredProceduralTextureUsages ?? [])];
40
42
  if (required.length === 0)
@@ -1605,22 +1607,25 @@ const rendererInput = z.strictObject({
1605
1607
  action: z.enum(['status', 'reprobe']).default('status'),
1606
1608
  });
1607
1609
  /** A saved-revision read whose failure names the listing call instead of a filesystem path (rule 9). */
1608
- async function readSavedRevision(target, collection, assetId, revisionId) {
1610
+ async function readSavedRevision(target, collection, assetId, revisionId, listCall = `kiln_assets { action: 'list', collection: '${collection}' }`) {
1609
1611
  try {
1610
1612
  return await target.read(collection, assetId, revisionId);
1611
1613
  }
1612
1614
  catch (error) {
1613
1615
  if (error.code === 'ENOENT')
1614
- throw new Error(`Unknown asset ${assetId} revision ${revisionId} in collection ${collection}. kiln_assets { action: 'list', collection: '${collection}' } lists the saved revisions.`);
1616
+ throw new Error(`Unknown asset ${assetId} revision ${revisionId} in collection ${collection}. ${listCall} lists the saved revisions.`);
1615
1617
  throw error;
1616
1618
  }
1617
1619
  }
1618
1620
  export function createKilnProgramToolRegistry(suppliedContext = {}) {
1621
+ return createProgramDefs(suppliedContext, 'grouped');
1622
+ }
1623
+ function createProgramDefs(suppliedContext, presentation) {
1619
1624
  toolRequirements(suppliedContext);
1620
1625
  const store = suppliedContext.programStore ?? new MemoryProgramStore();
1621
1626
  const context = withCaptureCache(withBuildCache({ ...suppliedContext, programStore: store }));
1622
1627
  return [
1623
- createKilnDiscoveryDef({ ...suppliedContext, programStore: store }),
1628
+ createKilnDiscoveryDef({ ...suppliedContext, programStore: store }, presentation),
1624
1629
  {
1625
1630
  name: 'kiln_renderer',
1626
1631
  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.',
@@ -1665,7 +1670,9 @@ export function createKilnProgramToolRegistry(suppliedContext = {}) {
1665
1670
  ...(context.projectStore
1666
1671
  ? [createKilnProjectDef(context.projectStore, context.projectBundleReader)]
1667
1672
  : []),
1668
- ...(context.materialLibrary ? [createKilnMaterialDef(context.materialLibrary)] : []),
1673
+ ...(context.materialLibrary
1674
+ ? [createKilnMaterialDef(context.materialLibrary, presentation)]
1675
+ : []),
1669
1676
  ...(context.reviewStore && context.assetLibrary
1670
1677
  ? [
1671
1678
  createKilnReviewDef({
@@ -1675,7 +1682,7 @@ export function createKilnProgramToolRegistry(suppliedContext = {}) {
1675
1682
  }),
1676
1683
  ]
1677
1684
  : []),
1678
- ...createKilnAssetDefs({ ...context, programStore: store }),
1685
+ ...createKilnAssetDefs({ ...context, programStore: store }, presentation),
1679
1686
  ]
1680
1687
  .map((def) => withWorkspaceContext(def, context))
1681
1688
  .map((def) => ({
@@ -1689,6 +1696,10 @@ export function createKilnProgramToolRegistry(suppliedContext = {}) {
1689
1696
  },
1690
1697
  }));
1691
1698
  }
1699
+ /** Opt-in named domain operations; grouped local/native contracts remain unchanged. */
1700
+ export function createKilnOperationToolRegistry(context = {}) {
1701
+ return projectOperationDefs(createProgramDefs(context, 'operations'), context);
1702
+ }
1692
1703
  /** Canonical native workflow: shared definitions plus one host-owned terminal. */
1693
1704
  export function createKilnNativeToolRegistry(suppliedContext, completion) {
1694
1705
  const active = toolRequirements(suppliedContext);
@@ -1772,15 +1783,42 @@ const assetSelector = {
1772
1783
  assetId: z.string().regex(/^[a-z][a-z0-9_-]{0,79}$/),
1773
1784
  revisionId: z.string().regex(/^[a-z][a-z0-9_-]{0,79}$/),
1774
1785
  };
1786
+ const ASSET_CONTRACT = {
1787
+ collections: {
1788
+ fields: [],
1789
+ required: [],
1790
+ description: 'List configured asset storage collections. Collection is not project membership.',
1791
+ },
1792
+ catalog: {
1793
+ fields: ['assetId', 'query', 'offset', 'limit'],
1794
+ required: [],
1795
+ description: 'Search saved assets across configured collections with pagination.',
1796
+ },
1797
+ list: {
1798
+ fields: ['collection', 'assetId', 'query', 'offset', 'limit'],
1799
+ required: [],
1800
+ description: 'Search saved assets in one collection with pagination.',
1801
+ },
1802
+ get: {
1803
+ fields: ['collection', 'assetId', 'revisionId'],
1804
+ required: ['assetId'],
1805
+ description: 'Read a saved revision and its download descriptors. Omit revisionId for the newest revision.',
1806
+ },
1807
+ restore: {
1808
+ fields: ['collection', 'assetId', 'revisionId'],
1809
+ required: ['assetId'],
1810
+ 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.',
1811
+ },
1812
+ };
1775
1813
  /**
1776
1814
  * The revision a get or restore without `revisionId` means: the newest, the
1777
1815
  * one no later revision names as its parent, ties to the latest `createdAt`
1778
1816
  * (c40 of 1 October 2026 called get with the asset id alone and was refused).
1779
1817
  */
1780
- export async function newestRevision(target, collection, assetId) {
1818
+ export async function newestRevision(target, collection, assetId, listCall = `kiln_assets { action: 'list', collection: '${collection}' }`) {
1781
1819
  const revisions = (await target.list(collection)).filter((a) => a.assetId === assetId);
1782
1820
  if (!revisions.length)
1783
- throw new Error(`Unknown asset ${assetId} in ${collection}; kiln_assets { action: 'list', collection: '${collection}' } lists them.`);
1821
+ throw new Error(`Unknown asset ${assetId} in ${collection}; ${listCall} lists them.`);
1784
1822
  const parents = new Set(revisions.map((a) => a.parentRevision).filter(Boolean));
1785
1823
  const heads = revisions.filter((a) => !parents.has(a.revisionId));
1786
1824
  const newest = (heads.length ? heads : revisions).sort((a, b) => a.createdAt < b.createdAt ? 1 : a.createdAt > b.createdAt ? -1 : 0);
@@ -1857,7 +1895,16 @@ export async function buildProgramAssetDraft(code, context, backdrop) {
1857
1895
  };
1858
1896
  }
1859
1897
  /** All asset operation schemas live here, alongside the existing tool definitions. */
1860
- export function createKilnAssetDefs(context) {
1898
+ export function createKilnAssetDefs(context, presentation = 'grouped') {
1899
+ const listCall = (collection) => presentation === 'operations'
1900
+ ? `kiln_assets_search({ collection: '${collection}' })`
1901
+ : `kiln_assets { action: 'list', collection: '${collection}' }`;
1902
+ const selector = presentation === 'operations'
1903
+ ? {
1904
+ ...assetSelector,
1905
+ collection: assetSelector.collection.describe('Collection ID from kiln_assets_collections; default project.'),
1906
+ }
1907
+ : assetSelector;
1861
1908
  const library = () => {
1862
1909
  if (!context.assetLibrary)
1863
1910
  throw new Error('No asset library configured. The local CLI/MCP host supplies workspace collections; embedded hosts must inject assetLibrary.');
@@ -1886,11 +1933,11 @@ export function createKilnAssetDefs(context) {
1886
1933
  downloadUrls: await context.assetDownloadUrls?.(collection, asset.assetId, asset.revisionId),
1887
1934
  });
1888
1935
  const saveInput = z.object({
1889
- collection: assetSelector.collection,
1936
+ collection: selector.collection,
1890
1937
  programRef: z.string(),
1891
1938
  name: z.string().min(1).max(200),
1892
- assetId: assetSelector.assetId.optional(),
1893
- parentRevision: assetSelector.revisionId.optional(),
1939
+ assetId: selector.assetId.optional(),
1940
+ parentRevision: selector.revisionId.optional(),
1894
1941
  tags: z.array(z.string().max(80)).max(30).optional(),
1895
1942
  brief: z.string().max(8000).optional(),
1896
1943
  description: z.string().max(4000).optional(),
@@ -1908,16 +1955,16 @@ export function createKilnAssetDefs(context) {
1908
1955
  });
1909
1956
  const assetsInput = z.object({
1910
1957
  action: z.enum(['collections', 'catalog', 'list', 'get', 'restore']).default('list'),
1911
- collection: assetSelector.collection,
1912
- assetId: assetSelector.assetId.optional(),
1913
- revisionId: assetSelector.revisionId
1958
+ collection: selector.collection,
1959
+ assetId: selector.assetId.optional(),
1960
+ revisionId: selector.revisionId
1914
1961
  .optional()
1915
1962
  .describe('get, restore: the saved revision; omitted, the newest.'),
1916
1963
  query: z.string().max(200).optional(),
1917
1964
  offset: z.number().int().min(0).default(0),
1918
1965
  limit: z.number().int().min(1).max(50).default(20),
1919
1966
  });
1920
- const exportInput = z.object(assetSelector);
1967
+ const exportInput = z.object(selector);
1921
1968
  const profileExportInput = exportInput.extend({
1922
1969
  profile: z
1923
1970
  .enum(['editable', 'runtime'])
@@ -1925,8 +1972,8 @@ export function createKilnAssetDefs(context) {
1925
1972
  .describe('editable preserves canonical source/GLB/build resources. runtime returns a standalone GLB and versioned review-metadata sidecar; no source bundle or geometry optimization.'),
1926
1973
  });
1927
1974
  const importInput = z.object({
1928
- ...assetSelector,
1929
- sourceCollection: assetSelector.collection,
1975
+ ...selector,
1976
+ sourceCollection: selector.collection,
1930
1977
  });
1931
1978
  return [
1932
1979
  {
@@ -1942,7 +1989,7 @@ export function createKilnAssetDefs(context) {
1942
1989
  requirements: activeRequirements.binding,
1943
1990
  };
1944
1991
  if (input.assetId && input.parentRevision) {
1945
- const previous = await readSavedRevision(target, input.collection, input.assetId, input.parentRevision);
1992
+ const previous = await readSavedRevision(target, input.collection, input.assetId, input.parentRevision, listCall(input.collection));
1946
1993
  assertSavedRequirementsAuthorized(previous.manifest, activeRequirements);
1947
1994
  }
1948
1995
  const code = await context.programStore.get(input.programRef);
@@ -1957,6 +2004,7 @@ export function createKilnAssetDefs(context) {
1957
2004
  name: 'kiln_assets',
1958
2005
  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.',
1959
2006
  inputSchema: assetsInput,
2007
+ actionContract: ASSET_CONTRACT,
1960
2008
  run: async (raw) => {
1961
2009
  const input = assetsInput.parse(raw);
1962
2010
  const activeRequirements = toolRequirements(context);
@@ -2005,10 +2053,11 @@ export function createKilnAssetDefs(context) {
2005
2053
  })),
2006
2054
  };
2007
2055
  }
2008
- if (!input.assetId)
2056
+ if (ASSET_CONTRACT[input.action].required.some((field) => !input[field]))
2009
2057
  throw new Error(`kiln_assets ${input.action} requires assetId; kiln_assets { action: 'list', collection: '${input.collection}' } lists them.`);
2010
- const revisionId = input.revisionId ?? (await newestRevision(target, input.collection, input.assetId));
2011
- const record = await readSavedRevision(target, input.collection, input.assetId, revisionId);
2058
+ const revisionId = input.revisionId ??
2059
+ (await newestRevision(target, input.collection, input.assetId, listCall(input.collection)));
2060
+ const record = await readSavedRevision(target, input.collection, input.assetId, revisionId, listCall(input.collection));
2012
2061
  if (input.action === 'get')
2013
2062
  return links(input.collection, record.manifest);
2014
2063
  const saved = assertSavedRequirementsAuthorized(record.manifest, activeRequirements);
@@ -2080,7 +2129,7 @@ export function createKilnAssetDefs(context) {
2080
2129
  inputSchema: profileExportInput,
2081
2130
  run: async (raw) => {
2082
2131
  const input = profileExportInput.parse(raw);
2083
- const record = await readSavedRevision(library(), input.collection, input.assetId, input.revisionId);
2132
+ const record = await readSavedRevision(library(), input.collection, input.assetId, input.revisionId, listCall(input.collection));
2084
2133
  const { inspectDrawDiagnostics } = await import("../draw-diagnostics.js");
2085
2134
  if (input.profile === 'runtime') {
2086
2135
  const output = await (await import("../asset-export.js")).exportAssetGlb(record, {
@@ -2114,7 +2163,7 @@ export function createKilnAssetDefs(context) {
2114
2163
  run: async (raw) => {
2115
2164
  const input = importInput.parse(raw);
2116
2165
  const target = library();
2117
- const record = await readSavedRevision(target, input.sourceCollection, input.assetId, input.revisionId);
2166
+ const record = await readSavedRevision(target, input.sourceCollection, input.assetId, input.revisionId, listCall(input.sourceCollection));
2118
2167
  await target.import(input.collection, [record]);
2119
2168
  return links(input.collection, record.manifest);
2120
2169
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@instruktlabs/kiln",
3
- "version": "1.1.0",
3
+ "version": "1.2.1",
4
4
  "description": "Create and refine procedural 3D assets with JavaScript, image feedback, and reusable source revisions.",
5
5
  "license": "MIT",
6
6
  "author": "Matthew Kissinger",
@@ -299,6 +299,7 @@
299
299
  "docs/runtime.md",
300
300
  "docs/sdk.md",
301
301
  "docs/tools.md",
302
+ "docs/tools-operations.md",
302
303
  "README.md",
303
304
  "CONTRIBUTING.md",
304
305
  "LICENSE",
package/plugin.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "kiln",
3
3
  "displayName": "Kiln",
4
4
  "description": "Build and revise 3D assets with your coding agent. Write JavaScript, inspect selected views, edit saved source revisions, and export GLB files locally.",
5
- "version": "1.1.0",
5
+ "version": "1.2.1",
6
6
  "license": "MIT",
7
7
  "homepage": "https://github.com/instruktlabs/kiln",
8
8
  "keywords": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kiln-engine",
3
- "version": "1.1.0",
3
+ "version": "1.2.1",
4
4
  "description": "Set up a local Kiln workspace to create, inspect and revise editable 3D assets with Claude Code or Codex.",
5
5
  "author": {
6
6
  "name": "Instrukt Labs",
@@ -1,7 +1,7 @@
1
1
  # Kiln local plugin
2
2
 
3
- Plugin version 1.1.0, maintained by Instrukt Labs under the MIT license.
4
- This plugin pins engine 1.1.0. Registry setup requires that exact version
3
+ Plugin version 1.2.1, maintained by Instrukt Labs under the MIT license.
4
+ This plugin pins engine 1.2.1. Registry setup requires that exact version
5
5
  to be published. Use a published release tag for normal installation or a reviewed
6
6
  local archive for development qualification. The installation ID remains
7
7
  `kiln-engine@instruktlabs`.
@@ -31,7 +31,7 @@ codex plugin add kiln-engine@instruktlabs
31
31
  Restart or reload your coding agent's plugins as its installation message directs.
32
32
  Then ask it to set up a Kiln workspace. The maintained setup skill runs this
33
33
  plugin's `bin/kiln-setup-workspace.mjs` with Node, installs
34
- `@instruktlabs/kiln@1.1.0` outside the project and configures your chosen
34
+ `@instruktlabs/kiln@1.2.1` outside the project and configures your chosen
35
35
  workspace. The same initializer supports existing projects and new workspaces.
36
36
 
37
37
  The plugin registers setup only. The workspace supplies authoring skills and one
@@ -1,20 +1,20 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "kind": "kiln-local-plugin",
4
- "pluginVersion": "1.1.0",
5
- "engineVersion": "1.1.0",
4
+ "pluginVersion": "1.2.1",
5
+ "engineVersion": "1.2.1",
6
6
  "executables": [
7
7
  "bin/kiln-setup-workspace.mjs"
8
8
  ],
9
9
  "files": {
10
10
  ".claude-plugin/icon.png": "sha256:b4b651c402793b7e27cab780f04b14f80daec6d0cd5b7e4e90313930581a54e9",
11
- ".claude-plugin/plugin.json": "sha256:189394253cf57f988fd82b31dad05012d693615a50126eebb29bab3e80e3a2a9",
11
+ ".claude-plugin/plugin.json": "sha256:bef717ea5e0c96411c2db93d54e298dc3798512df9909ba74c950a6c1c03f6cb",
12
12
  "bin/kiln-setup-workspace.mjs": "sha256:3d9a30236919a6ca91acb6b26f808cec442e6edc3dd88fe010645b010d76e838",
13
13
  "LICENSE": "sha256:cdba278ebf1d6de618869936226bf03be4887179c128efc7aaa13d22034826a5",
14
- "plugin.json": "sha256:428ab085c6257ccc0e2dec3c7d4f9b6e02db0024a5f0b355410a30d6282dcd1d",
14
+ "plugin.json": "sha256:9ef79b862a25ae3efe86c03a37e09cd622912b97a0165a08fdf55cff8b71a010",
15
15
  "PRIVACY.md": "sha256:5f58d981667e40441e21c2dec411f269a9a67a6141cec61976d27088fb12573b",
16
- "README.md": "sha256:6a7fc436113f7493c884b2c495629351b01b2cd7e82e7b808f3abe73b9eb9af4",
17
- "runtime.json": "sha256:253f76044dcc162295ca760ff584d1d6894068357c727a56b077b44278f38eb3",
16
+ "README.md": "sha256:029732d7bb9e04561105727f8e46118d3106f513b4575a8d74d57cbd1bb68e7a",
17
+ "runtime.json": "sha256:b6d078f1223cb5ed3a522b0ef5a3cb778e051afc0115237cfd9cfe04c1f42025",
18
18
  "skills/kiln-setup-workspace/references/plugin-install.md": "sha256:c338fb792e7fad0364c3e1a13c94c506afa1395489ec1b6e83b36d04728935fb",
19
19
  "skills/kiln-setup-workspace/references/renderer-setup.md": "sha256:b743b1b2827fa814385990e1e5b849391cb427413e9c016d273aa19cebd46031",
20
20
  "skills/kiln-setup-workspace/SKILL.md": "sha256:65ed926b3503a33f4fe793e84e4a045ce36e3967f0e6b340e456a15dd010ce54"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
3
3
  "name": "kiln-engine",
4
- "version": "1.1.0",
4
+ "version": "1.2.1",
5
5
  "description": "Set up a local Kiln workspace to create, inspect and revise editable 3D assets with Claude Code or Codex.",
6
6
  "author": {
7
7
  "name": "Instrukt Labs",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@instruktlabs/kiln",
3
- "version": "1.1.0",
3
+ "version": "1.2.1",
4
4
  "harnesses": [
5
5
  "claude",
6
6
  "codex",
@@ -6,4 +6,4 @@
6
6
  * engine has loaded. Neither of those may import the other, so the literal
7
7
  * lives here on its own.
8
8
  */
9
- export const KILN_ASSET_WIDGET_URI = 'ui://kiln/asset-v5.html';
9
+ export const KILN_ASSET_WIDGET_URI = 'ui://kiln/asset-v6.html';
@@ -21,6 +21,7 @@ export function createDiscovery(
21
21
  discovery: { mode: 'lexical', offline: true, requiresModel: false },
22
22
  }),
23
23
  notes: () => Promise<string[]> = async () => [],
24
+ capabilitiesCall = 'capabilities:true',
24
25
  ) {
25
26
  snapshot ??= (() => {
26
27
  const entries = listDiscoveryEntries();
@@ -32,5 +33,6 @@ export function createDiscovery(
32
33
  capabilities,
33
34
  REMOVED_AUTHORING_HELPERS,
34
35
  notes,
36
+ capabilitiesCall,
35
37
  );
36
38
  }
@@ -63,6 +63,8 @@ export function createDiscoveryService(
63
63
  retirements: Readonly<Record<string, string>> = {},
64
64
  /** Host notes for the overview, such as the project an omitted `projectId` selects. */
65
65
  notes: () => Promise<string[]> = async () => [],
66
+ /** Host-selected capability entrypoint, never a model-provided instruction. */
67
+ capabilitiesCall = 'capabilities:true',
66
68
  ): (input: unknown) => Promise<DiscoveryResponse> {
67
69
  const entries = parseCatalog(source);
68
70
  const byId = new Map(entries.map((entry) => [entry.id, entry]));
@@ -94,7 +96,7 @@ export function createDiscoveryService(
94
96
  'Parts auto-add to opts.parent; do not wrap createPart in parent.add. Part rotation uses degrees.',
95
97
  'Custom THREE.BufferGeometry and ordinary JavaScript functions remain available. Recipes offer guidance and never restrict modeling.',
96
98
  'Send source once and reuse programRef for reads, edits, inspection and rendering. Review geometry, materials and destination constraints separately.',
97
- 'Search for the operation you need in ordinary language, then fetch exact contracts with ids. Use capabilities:true for the current host.',
99
+ `Search for the operation you need in ordinary language, then fetch exact contracts with ids. Use ${capabilitiesCall} for the current host.`,
98
100
  ],
99
101
  };
100
102
  const error = (
@@ -27,7 +27,7 @@
27
27
  * `src/__tests__/package-contents.test.ts` fails if it disagrees with the manifest
28
28
  * or with any plugin manifest, which is what keeps the literal honest.
29
29
  */
30
- export const ENGINE_VERSION = '1.1.0';
30
+ export const ENGINE_VERSION = '1.2.1';
31
31
 
32
32
  /**
33
33
  * Where this engine was loaded from, as a `file://` URL of the package root.
@@ -1733,13 +1733,13 @@
1733
1733
  },
1734
1734
  "_meta": {
1735
1735
  "ui": {
1736
- "resourceUri": "ui://kiln/asset-v5.html",
1736
+ "resourceUri": "ui://kiln/asset-v6.html",
1737
1737
  "visibility": [
1738
1738
  "model"
1739
1739
  ]
1740
1740
  },
1741
- "openai/outputTemplate": "ui://kiln/asset-v5.html",
1742
- "ui/resourceUri": "ui://kiln/asset-v5.html"
1741
+ "openai/outputTemplate": "ui://kiln/asset-v6.html",
1742
+ "ui/resourceUri": "ui://kiln/asset-v6.html"
1743
1743
  },
1744
1744
  "outputSchema": {
1745
1745
  "type": "object",
@@ -2018,7 +2018,7 @@
2018
2018
  ],
2019
2019
  "resources": [
2020
2020
  {
2021
- "uri": "ui://kiln/asset-v5.html",
2021
+ "uri": "ui://kiln/asset-v6.html",
2022
2022
  "name": "kiln-asset-viewer",
2023
2023
  "mimeType": "text/html;profile=mcp-app",
2024
2024
  "description": "Interactive Kiln asset viewer and downloads",
package/src/mcp-core.ts CHANGED
@@ -65,6 +65,9 @@ export const MCP_SERVER_VERSION = ENGINE_VERSION;
65
65
  export const MCP_SERVER_INSTRUCTIONS =
66
66
  "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.";
67
67
 
68
+ export const MCP_OPERATION_INSTRUCTIONS =
69
+ '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.';
70
+
68
71
  /** One MCP content block. Mirrors the SDK's `CallToolResult['content']` element. */
69
72
  export type KilnContentBlock =
70
73
  | AssetLink
package/src/mcp-engine.ts CHANGED
@@ -26,6 +26,7 @@ import {
26
26
  type KilnResourceContents,
27
27
  type KilnToolResult,
28
28
  withoutLocalPaths,
29
+ MCP_OPERATION_INSTRUCTIONS,
29
30
  } from './mcp-core';
30
31
  import { buildMcpManifest } from './mcp-manifest';
31
32
  import { localProgramStore } from './program-store-node';
@@ -33,6 +34,7 @@ import { validateRequirementsBinding } from './requirements-context';
33
34
  import { describeInputError, schemaFields } from './tools/actions';
34
35
  import {
35
36
  createKilnProgramToolRegistry,
37
+ createKilnOperationToolRegistry,
36
38
  type KilnToolContext,
37
39
  type KilnToolDef,
38
40
  } from './tools/registry';
@@ -42,6 +44,8 @@ export type { KilnToolResult } from './mcp-core';
42
44
 
43
45
  /** Optional wire features whose support is not negotiated by core MCP. */
44
46
  export type KilnMcpCompatibilityOptions = {
47
+ /** Opt-in domain operation presentation generated from the shared registry. */
48
+ toolPresentation?: 'grouped' | 'operations';
45
49
  /**
46
50
  * Add core `resource_link` blocks for saved artifact files.
47
51
  *
@@ -160,7 +164,10 @@ export function createKilnToolHost(
160
164
  };
161
165
  },
162
166
  };
163
- const defs = kilnMcpToolDefs(requestContext);
167
+ const defs =
168
+ options.toolPresentation === 'operations'
169
+ ? createKilnOperationToolRegistry(requestContext)
170
+ : kilnMcpToolDefs(requestContext);
164
171
  const byName = new Map(defs.map((def) => [def.name, def] as const));
165
172
  return {
166
173
  defs,
@@ -259,6 +266,9 @@ export function createKilnMcpServer(
259
266
  ): Server {
260
267
  const host = createKilnToolHost(context, options);
261
268
  return createKilnServer({
269
+ ...(options.toolPresentation === 'operations'
270
+ ? { instructions: MCP_OPERATION_INSTRUCTIONS }
271
+ : {}),
262
272
  manifest: buildMcpManifest(host.defs, context),
263
273
  host: async () => host,
264
274
  });
@@ -11,10 +11,14 @@ import { resolveGltfExporter } from '../community-exporter';
11
11
  import { approvedTextureCatalogV1 } from '../material-resources';
12
12
 
13
13
  /** Shared native tool definition; all transports use the same Discovery service. */
14
- export function createKilnDiscoveryDef(context: KilnToolContext): KilnToolDef {
14
+ export function createKilnDiscoveryDef(
15
+ context: KilnToolContext,
16
+ presentation: 'grouped' | 'operations' = 'grouped',
17
+ ): KilnToolDef {
15
18
  const run = createDiscovery(
16
- () => currentCapabilities(context),
19
+ () => currentCapabilities(context, presentation),
17
20
  async () => projectNotes(context),
21
+ presentation === 'operations' ? 'kiln_capabilities({})' : 'capabilities:true',
18
22
  );
19
23
  return {
20
24
  name: 'kiln_discover',
@@ -48,7 +52,10 @@ function projectNotes(context: KilnToolContext): string[] {
48
52
  : [];
49
53
  }
50
54
 
51
- async function currentCapabilities(context: KilnToolContext) {
55
+ async function currentCapabilities(
56
+ context: KilnToolContext,
57
+ presentation: 'grouped' | 'operations',
58
+ ) {
52
59
  const externalEvaluator =
53
60
  Boolean(context.evaluatorPort) || context.evaluatorProfile === 'evaluator-required';
54
61
  const approvedTextures = context.approvedTextureResources
@@ -102,12 +109,15 @@ async function currentCapabilities(context: KilnToolContext) {
102
109
  // you -- so the workspace guide's "do not substitute it silently" had
103
110
  // nothing to check against. Compare with `runtime` in .kiln/workspace.json.
104
111
  engine: engineIdentity(),
105
- project: {
106
- configured: configuredProject(context),
107
- select:
108
- 'projectId per call; omitted uses the configured project, null selects standalone work',
109
- read: 'kiln_project { action: "get", projectId } returns the brief, design, inventory and material pins',
110
- },
112
+ project:
113
+ presentation === 'operations' && !context.projectStore
114
+ ? { available: false }
115
+ : {
116
+ configured: configuredProject(context),
117
+ select:
118
+ 'projectId per call; omitted uses the configured project, null selects standalone work',
119
+ read: 'kiln_project { action: "get", projectId } returns the brief, design, inventory and material pins',
120
+ },
111
121
  renderer,
112
122
  execution:
113
123
  context.localExecution ??
@@ -128,7 +138,7 @@ async function currentCapabilities(context: KilnToolContext) {
128
138
  available: Boolean(context.assetLibrary),
129
139
  collections: context.assetLibrary?.collections() ?? [],
130
140
  save: 'kiln_save persists exact GLB, source and provenance; draft renders do not populate collections',
131
- resume: 'kiln_assets action=restore imports a saved revision into the current program store',
141
+ resume: `${presentation === 'operations' ? 'kiln_assets_restore' : 'kiln_assets action=restore'} imports a saved revision into the current program store`,
132
142
  downloads: 'kiln_export returns GLB/source/ZIP resource links; client presentation varies',
133
143
  viewer:
134
144
  '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',