@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.
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/CHANGELOG.md +12 -0
- package/dist/agent-run.mjs +220 -150
- package/dist/build.json +22 -22
- package/dist/cli.mjs +225 -150
- package/dist/mcp-engine.mjs +398 -144
- package/dist/mcp-server.mjs +1 -1
- package/docs/sdk.md +23 -0
- package/docs/tools-operations.md +1529 -0
- package/lib/discovery/index.d.ts +1 -1
- package/lib/discovery/index.js +2 -2
- package/lib/discovery/service.d.ts +3 -1
- package/lib/discovery/service.js +4 -2
- package/lib/engine-identity.d.ts +1 -1
- package/lib/engine-identity.js +1 -1
- package/lib/mcp-core.d.ts +2 -1
- package/lib/mcp-core.js +1 -0
- package/lib/mcp-engine.d.ts +2 -0
- package/lib/mcp-engine.js +8 -3
- package/lib/tools/discovery.d.ts +1 -1
- package/lib/tools/discovery.js +11 -9
- package/lib/tools/materials.d.ts +1 -1
- package/lib/tools/materials.js +44 -14
- package/lib/tools/operation-contract.d.ts +8 -0
- package/lib/tools/operation-contract.js +37 -0
- package/lib/tools/operations.d.ts +3 -0
- package/lib/tools/operations.js +154 -0
- package/lib/tools/registry.d.ts +6 -2
- package/lib/tools/registry.js +71 -23
- package/package.json +2 -1
- package/plugin.json +1 -1
- package/plugins/kiln-engine/.claude-plugin/plugin.json +1 -1
- package/plugins/kiln-engine/README.md +3 -3
- package/plugins/kiln-engine/package-provenance.json +6 -6
- package/plugins/kiln-engine/plugin.json +1 -1
- package/plugins/kiln-engine/runtime.json +1 -1
- package/src/discovery/index.ts +2 -0
- package/src/discovery/service.ts +3 -1
- package/src/engine-identity.ts +1 -1
- package/src/mcp-core.ts +3 -0
- package/src/mcp-engine.ts +11 -1
- package/src/tools/discovery.ts +20 -10
- package/src/tools/materials.ts +59 -15
- package/src/tools/operation-contract.ts +51 -0
- package/src/tools/operations.ts +177 -0
- package/src/tools/registry.ts +100 -20
package/lib/tools/registry.js
CHANGED
|
@@ -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";
|
|
@@ -1605,22 +1606,25 @@ const rendererInput = z.strictObject({
|
|
|
1605
1606
|
action: z.enum(['status', 'reprobe']).default('status'),
|
|
1606
1607
|
});
|
|
1607
1608
|
/** 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) {
|
|
1609
|
+
async function readSavedRevision(target, collection, assetId, revisionId, listCall = `kiln_assets { action: 'list', collection: '${collection}' }`) {
|
|
1609
1610
|
try {
|
|
1610
1611
|
return await target.read(collection, assetId, revisionId);
|
|
1611
1612
|
}
|
|
1612
1613
|
catch (error) {
|
|
1613
1614
|
if (error.code === 'ENOENT')
|
|
1614
|
-
throw new Error(`Unknown asset ${assetId} revision ${revisionId} in collection ${collection}.
|
|
1615
|
+
throw new Error(`Unknown asset ${assetId} revision ${revisionId} in collection ${collection}. ${listCall} lists the saved revisions.`);
|
|
1615
1616
|
throw error;
|
|
1616
1617
|
}
|
|
1617
1618
|
}
|
|
1618
1619
|
export function createKilnProgramToolRegistry(suppliedContext = {}) {
|
|
1620
|
+
return createProgramDefs(suppliedContext, 'grouped');
|
|
1621
|
+
}
|
|
1622
|
+
function createProgramDefs(suppliedContext, presentation) {
|
|
1619
1623
|
toolRequirements(suppliedContext);
|
|
1620
1624
|
const store = suppliedContext.programStore ?? new MemoryProgramStore();
|
|
1621
1625
|
const context = withCaptureCache(withBuildCache({ ...suppliedContext, programStore: store }));
|
|
1622
1626
|
return [
|
|
1623
|
-
createKilnDiscoveryDef({ ...suppliedContext, programStore: store }),
|
|
1627
|
+
createKilnDiscoveryDef({ ...suppliedContext, programStore: store }, presentation),
|
|
1624
1628
|
{
|
|
1625
1629
|
name: 'kiln_renderer',
|
|
1626
1630
|
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 +1669,9 @@ export function createKilnProgramToolRegistry(suppliedContext = {}) {
|
|
|
1665
1669
|
...(context.projectStore
|
|
1666
1670
|
? [createKilnProjectDef(context.projectStore, context.projectBundleReader)]
|
|
1667
1671
|
: []),
|
|
1668
|
-
...(context.materialLibrary
|
|
1672
|
+
...(context.materialLibrary
|
|
1673
|
+
? [createKilnMaterialDef(context.materialLibrary, presentation)]
|
|
1674
|
+
: []),
|
|
1669
1675
|
...(context.reviewStore && context.assetLibrary
|
|
1670
1676
|
? [
|
|
1671
1677
|
createKilnReviewDef({
|
|
@@ -1675,7 +1681,7 @@ export function createKilnProgramToolRegistry(suppliedContext = {}) {
|
|
|
1675
1681
|
}),
|
|
1676
1682
|
]
|
|
1677
1683
|
: []),
|
|
1678
|
-
...createKilnAssetDefs({ ...context, programStore: store }),
|
|
1684
|
+
...createKilnAssetDefs({ ...context, programStore: store }, presentation),
|
|
1679
1685
|
]
|
|
1680
1686
|
.map((def) => withWorkspaceContext(def, context))
|
|
1681
1687
|
.map((def) => ({
|
|
@@ -1689,6 +1695,10 @@ export function createKilnProgramToolRegistry(suppliedContext = {}) {
|
|
|
1689
1695
|
},
|
|
1690
1696
|
}));
|
|
1691
1697
|
}
|
|
1698
|
+
/** Opt-in named domain operations; grouped local/native contracts remain unchanged. */
|
|
1699
|
+
export function createKilnOperationToolRegistry(context = {}) {
|
|
1700
|
+
return projectOperationDefs(createProgramDefs(context, 'operations'), context);
|
|
1701
|
+
}
|
|
1692
1702
|
/** Canonical native workflow: shared definitions plus one host-owned terminal. */
|
|
1693
1703
|
export function createKilnNativeToolRegistry(suppliedContext, completion) {
|
|
1694
1704
|
const active = toolRequirements(suppliedContext);
|
|
@@ -1772,15 +1782,42 @@ const assetSelector = {
|
|
|
1772
1782
|
assetId: z.string().regex(/^[a-z][a-z0-9_-]{0,79}$/),
|
|
1773
1783
|
revisionId: z.string().regex(/^[a-z][a-z0-9_-]{0,79}$/),
|
|
1774
1784
|
};
|
|
1785
|
+
const ASSET_CONTRACT = {
|
|
1786
|
+
collections: {
|
|
1787
|
+
fields: [],
|
|
1788
|
+
required: [],
|
|
1789
|
+
description: 'List configured asset storage collections. Collection is not project membership.',
|
|
1790
|
+
},
|
|
1791
|
+
catalog: {
|
|
1792
|
+
fields: ['assetId', 'query', 'offset', 'limit'],
|
|
1793
|
+
required: [],
|
|
1794
|
+
description: 'Search saved assets across configured collections with pagination.',
|
|
1795
|
+
},
|
|
1796
|
+
list: {
|
|
1797
|
+
fields: ['collection', 'assetId', 'query', 'offset', 'limit'],
|
|
1798
|
+
required: [],
|
|
1799
|
+
description: 'Search saved assets in one collection with pagination.',
|
|
1800
|
+
},
|
|
1801
|
+
get: {
|
|
1802
|
+
fields: ['collection', 'assetId', 'revisionId'],
|
|
1803
|
+
required: ['assetId'],
|
|
1804
|
+
description: 'Read a saved revision and its download descriptors. Omit revisionId for the newest revision.',
|
|
1805
|
+
},
|
|
1806
|
+
restore: {
|
|
1807
|
+
fields: ['collection', 'assetId', 'revisionId'],
|
|
1808
|
+
required: ['assetId'],
|
|
1809
|
+
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.',
|
|
1810
|
+
},
|
|
1811
|
+
};
|
|
1775
1812
|
/**
|
|
1776
1813
|
* The revision a get or restore without `revisionId` means: the newest, the
|
|
1777
1814
|
* one no later revision names as its parent, ties to the latest `createdAt`
|
|
1778
1815
|
* (c40 of 1 October 2026 called get with the asset id alone and was refused).
|
|
1779
1816
|
*/
|
|
1780
|
-
export async function newestRevision(target, collection, assetId) {
|
|
1817
|
+
export async function newestRevision(target, collection, assetId, listCall = `kiln_assets { action: 'list', collection: '${collection}' }`) {
|
|
1781
1818
|
const revisions = (await target.list(collection)).filter((a) => a.assetId === assetId);
|
|
1782
1819
|
if (!revisions.length)
|
|
1783
|
-
throw new Error(`Unknown asset ${assetId} in ${collection};
|
|
1820
|
+
throw new Error(`Unknown asset ${assetId} in ${collection}; ${listCall} lists them.`);
|
|
1784
1821
|
const parents = new Set(revisions.map((a) => a.parentRevision).filter(Boolean));
|
|
1785
1822
|
const heads = revisions.filter((a) => !parents.has(a.revisionId));
|
|
1786
1823
|
const newest = (heads.length ? heads : revisions).sort((a, b) => a.createdAt < b.createdAt ? 1 : a.createdAt > b.createdAt ? -1 : 0);
|
|
@@ -1857,7 +1894,16 @@ export async function buildProgramAssetDraft(code, context, backdrop) {
|
|
|
1857
1894
|
};
|
|
1858
1895
|
}
|
|
1859
1896
|
/** All asset operation schemas live here, alongside the existing tool definitions. */
|
|
1860
|
-
export function createKilnAssetDefs(context) {
|
|
1897
|
+
export function createKilnAssetDefs(context, presentation = 'grouped') {
|
|
1898
|
+
const listCall = (collection) => presentation === 'operations'
|
|
1899
|
+
? `kiln_assets_search({ collection: '${collection}' })`
|
|
1900
|
+
: `kiln_assets { action: 'list', collection: '${collection}' }`;
|
|
1901
|
+
const selector = presentation === 'operations'
|
|
1902
|
+
? {
|
|
1903
|
+
...assetSelector,
|
|
1904
|
+
collection: assetSelector.collection.describe('Collection ID from kiln_assets_collections; default project.'),
|
|
1905
|
+
}
|
|
1906
|
+
: assetSelector;
|
|
1861
1907
|
const library = () => {
|
|
1862
1908
|
if (!context.assetLibrary)
|
|
1863
1909
|
throw new Error('No asset library configured. The local CLI/MCP host supplies workspace collections; embedded hosts must inject assetLibrary.');
|
|
@@ -1886,11 +1932,11 @@ export function createKilnAssetDefs(context) {
|
|
|
1886
1932
|
downloadUrls: await context.assetDownloadUrls?.(collection, asset.assetId, asset.revisionId),
|
|
1887
1933
|
});
|
|
1888
1934
|
const saveInput = z.object({
|
|
1889
|
-
collection:
|
|
1935
|
+
collection: selector.collection,
|
|
1890
1936
|
programRef: z.string(),
|
|
1891
1937
|
name: z.string().min(1).max(200),
|
|
1892
|
-
assetId:
|
|
1893
|
-
parentRevision:
|
|
1938
|
+
assetId: selector.assetId.optional(),
|
|
1939
|
+
parentRevision: selector.revisionId.optional(),
|
|
1894
1940
|
tags: z.array(z.string().max(80)).max(30).optional(),
|
|
1895
1941
|
brief: z.string().max(8000).optional(),
|
|
1896
1942
|
description: z.string().max(4000).optional(),
|
|
@@ -1908,16 +1954,16 @@ export function createKilnAssetDefs(context) {
|
|
|
1908
1954
|
});
|
|
1909
1955
|
const assetsInput = z.object({
|
|
1910
1956
|
action: z.enum(['collections', 'catalog', 'list', 'get', 'restore']).default('list'),
|
|
1911
|
-
collection:
|
|
1912
|
-
assetId:
|
|
1913
|
-
revisionId:
|
|
1957
|
+
collection: selector.collection,
|
|
1958
|
+
assetId: selector.assetId.optional(),
|
|
1959
|
+
revisionId: selector.revisionId
|
|
1914
1960
|
.optional()
|
|
1915
1961
|
.describe('get, restore: the saved revision; omitted, the newest.'),
|
|
1916
1962
|
query: z.string().max(200).optional(),
|
|
1917
1963
|
offset: z.number().int().min(0).default(0),
|
|
1918
1964
|
limit: z.number().int().min(1).max(50).default(20),
|
|
1919
1965
|
});
|
|
1920
|
-
const exportInput = z.object(
|
|
1966
|
+
const exportInput = z.object(selector);
|
|
1921
1967
|
const profileExportInput = exportInput.extend({
|
|
1922
1968
|
profile: z
|
|
1923
1969
|
.enum(['editable', 'runtime'])
|
|
@@ -1925,8 +1971,8 @@ export function createKilnAssetDefs(context) {
|
|
|
1925
1971
|
.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
1972
|
});
|
|
1927
1973
|
const importInput = z.object({
|
|
1928
|
-
...
|
|
1929
|
-
sourceCollection:
|
|
1974
|
+
...selector,
|
|
1975
|
+
sourceCollection: selector.collection,
|
|
1930
1976
|
});
|
|
1931
1977
|
return [
|
|
1932
1978
|
{
|
|
@@ -1942,7 +1988,7 @@ export function createKilnAssetDefs(context) {
|
|
|
1942
1988
|
requirements: activeRequirements.binding,
|
|
1943
1989
|
};
|
|
1944
1990
|
if (input.assetId && input.parentRevision) {
|
|
1945
|
-
const previous = await readSavedRevision(target, input.collection, input.assetId, input.parentRevision);
|
|
1991
|
+
const previous = await readSavedRevision(target, input.collection, input.assetId, input.parentRevision, listCall(input.collection));
|
|
1946
1992
|
assertSavedRequirementsAuthorized(previous.manifest, activeRequirements);
|
|
1947
1993
|
}
|
|
1948
1994
|
const code = await context.programStore.get(input.programRef);
|
|
@@ -1957,6 +2003,7 @@ export function createKilnAssetDefs(context) {
|
|
|
1957
2003
|
name: 'kiln_assets',
|
|
1958
2004
|
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
2005
|
inputSchema: assetsInput,
|
|
2006
|
+
actionContract: ASSET_CONTRACT,
|
|
1960
2007
|
run: async (raw) => {
|
|
1961
2008
|
const input = assetsInput.parse(raw);
|
|
1962
2009
|
const activeRequirements = toolRequirements(context);
|
|
@@ -2005,10 +2052,11 @@ export function createKilnAssetDefs(context) {
|
|
|
2005
2052
|
})),
|
|
2006
2053
|
};
|
|
2007
2054
|
}
|
|
2008
|
-
if (
|
|
2055
|
+
if (ASSET_CONTRACT[input.action].required.some((field) => !input[field]))
|
|
2009
2056
|
throw new Error(`kiln_assets ${input.action} requires assetId; kiln_assets { action: 'list', collection: '${input.collection}' } lists them.`);
|
|
2010
|
-
const revisionId = input.revisionId ??
|
|
2011
|
-
|
|
2057
|
+
const revisionId = input.revisionId ??
|
|
2058
|
+
(await newestRevision(target, input.collection, input.assetId, listCall(input.collection)));
|
|
2059
|
+
const record = await readSavedRevision(target, input.collection, input.assetId, revisionId, listCall(input.collection));
|
|
2012
2060
|
if (input.action === 'get')
|
|
2013
2061
|
return links(input.collection, record.manifest);
|
|
2014
2062
|
const saved = assertSavedRequirementsAuthorized(record.manifest, activeRequirements);
|
|
@@ -2080,7 +2128,7 @@ export function createKilnAssetDefs(context) {
|
|
|
2080
2128
|
inputSchema: profileExportInput,
|
|
2081
2129
|
run: async (raw) => {
|
|
2082
2130
|
const input = profileExportInput.parse(raw);
|
|
2083
|
-
const record = await readSavedRevision(library(), input.collection, input.assetId, input.revisionId);
|
|
2131
|
+
const record = await readSavedRevision(library(), input.collection, input.assetId, input.revisionId, listCall(input.collection));
|
|
2084
2132
|
const { inspectDrawDiagnostics } = await import("../draw-diagnostics.js");
|
|
2085
2133
|
if (input.profile === 'runtime') {
|
|
2086
2134
|
const output = await (await import("../asset-export.js")).exportAssetGlb(record, {
|
|
@@ -2114,7 +2162,7 @@ export function createKilnAssetDefs(context) {
|
|
|
2114
2162
|
run: async (raw) => {
|
|
2115
2163
|
const input = importInput.parse(raw);
|
|
2116
2164
|
const target = library();
|
|
2117
|
-
const record = await readSavedRevision(target, input.sourceCollection, input.assetId, input.revisionId);
|
|
2165
|
+
const record = await readSavedRevision(target, input.sourceCollection, input.assetId, input.revisionId, listCall(input.sourceCollection));
|
|
2118
2166
|
await target.import(input.collection, [record]);
|
|
2119
2167
|
return links(input.collection, record.manifest);
|
|
2120
2168
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@instruktlabs/kiln",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
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.
|
|
5
|
+
"version": "1.2.0",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"homepage": "https://github.com/instruktlabs/kiln",
|
|
8
8
|
"keywords": [
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Kiln local plugin
|
|
2
2
|
|
|
3
|
-
Plugin version 1.
|
|
4
|
-
This plugin pins engine 1.
|
|
3
|
+
Plugin version 1.2.0, maintained by Instrukt Labs under the MIT license.
|
|
4
|
+
This plugin pins engine 1.2.0. 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.
|
|
34
|
+
`@instruktlabs/kiln@1.2.0` 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.
|
|
5
|
-
"engineVersion": "1.
|
|
4
|
+
"pluginVersion": "1.2.0",
|
|
5
|
+
"engineVersion": "1.2.0",
|
|
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:
|
|
11
|
+
".claude-plugin/plugin.json": "sha256:616a6a61da072fcfdd4d0e1cf513e0e91bf1e56fb61b9b02d23e72fa291556bc",
|
|
12
12
|
"bin/kiln-setup-workspace.mjs": "sha256:3d9a30236919a6ca91acb6b26f808cec442e6edc3dd88fe010645b010d76e838",
|
|
13
13
|
"LICENSE": "sha256:cdba278ebf1d6de618869936226bf03be4887179c128efc7aaa13d22034826a5",
|
|
14
|
-
"plugin.json": "sha256:
|
|
14
|
+
"plugin.json": "sha256:3437107d46ad4fef4a710a8e821e56b43894cc1722504fe4e8ff0120fa6a8d8d",
|
|
15
15
|
"PRIVACY.md": "sha256:5f58d981667e40441e21c2dec411f269a9a67a6141cec61976d27088fb12573b",
|
|
16
|
-
"README.md": "sha256:
|
|
17
|
-
"runtime.json": "sha256:
|
|
16
|
+
"README.md": "sha256:0b82e6eb8b679f8b991c1a5567957574ed7b3341a87ef989403e5521f83ab50e",
|
|
17
|
+
"runtime.json": "sha256:5ebac8387f4bedcc7cac02a36f73c5ffba969ce6c8c6e2b0e356ef569168d0a5",
|
|
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.
|
|
4
|
+
"version": "1.2.0",
|
|
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",
|
package/src/discovery/index.ts
CHANGED
|
@@ -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
|
}
|
package/src/discovery/service.ts
CHANGED
|
@@ -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
|
-
|
|
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 = (
|
package/src/engine-identity.ts
CHANGED
|
@@ -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.
|
|
30
|
+
export const ENGINE_VERSION = '1.2.0';
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
33
|
* Where this engine was loaded from, as a `file://` URL of the package root.
|
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 =
|
|
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
|
});
|
package/src/tools/discovery.ts
CHANGED
|
@@ -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(
|
|
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(
|
|
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
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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',
|
package/src/tools/materials.ts
CHANGED
|
@@ -14,6 +14,7 @@ import { createMaterialRecordV1, decodeMaterialLibraryPayload } from '../materia
|
|
|
14
14
|
import { createMaterialPresetDraft, listMaterialPresets } from '../material-presets';
|
|
15
15
|
import { nestedRecordDescription, requireActionFields } from './actions';
|
|
16
16
|
import type { KilnToolDef } from './registry';
|
|
17
|
+
import type { KilnActionContract } from './operation-contract';
|
|
17
18
|
|
|
18
19
|
const MATERIAL_ACTIONS = [
|
|
19
20
|
'presets',
|
|
@@ -24,13 +25,43 @@ const MATERIAL_ACTIONS = [
|
|
|
24
25
|
'import',
|
|
25
26
|
] as const;
|
|
26
27
|
type MaterialAction = (typeof MATERIAL_ACTIONS)[number];
|
|
27
|
-
const
|
|
28
|
-
presets: {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
28
|
+
const MATERIAL_CONTRACT: Record<MaterialAction, KilnActionContract> = {
|
|
29
|
+
presets: {
|
|
30
|
+
fields: ['tag'],
|
|
31
|
+
required: [],
|
|
32
|
+
description: 'List shipped material recipes, optionally filtered by tag.',
|
|
33
|
+
},
|
|
34
|
+
'create-preset': {
|
|
35
|
+
fields: ['presetId', 'seed', 'creator', 'license', 'size', 'materialId'],
|
|
36
|
+
required: ['presetId', 'seed', 'creator', 'license'],
|
|
37
|
+
description:
|
|
38
|
+
'Create an immutable material from a shipped preset with explicit seed, creator and license.',
|
|
39
|
+
},
|
|
40
|
+
list: {
|
|
41
|
+
fields: ['tag'],
|
|
42
|
+
required: [],
|
|
43
|
+
description: 'List saved immutable material revisions, optionally filtered by tag.',
|
|
44
|
+
},
|
|
45
|
+
get: {
|
|
46
|
+
fields: ['materialId', 'revisionId'],
|
|
47
|
+
required: ['materialId'],
|
|
48
|
+
description:
|
|
49
|
+
'Read a material revision, provenance and portable specification. Omit revisionId only when exactly one revision exists.',
|
|
50
|
+
},
|
|
51
|
+
'create-procedural': {
|
|
52
|
+
fields: ['draft'],
|
|
53
|
+
required: ['draft'],
|
|
54
|
+
shapes: { draft: 'shape:material-draft' },
|
|
55
|
+
description:
|
|
56
|
+
'Create an immutable material from a bounded procedural draft. Does not fetch URLs or execute source.',
|
|
57
|
+
},
|
|
58
|
+
import: {
|
|
59
|
+
fields: ['payload'],
|
|
60
|
+
required: ['payload'],
|
|
61
|
+
shapes: { payload: 'shape:material-import' },
|
|
62
|
+
description:
|
|
63
|
+
'Import complete normalized material records and embedded PNGs. Does not fetch URLs or execute source.',
|
|
64
|
+
},
|
|
34
65
|
};
|
|
35
66
|
const record = (description: string) =>
|
|
36
67
|
z.record(z.string(), z.unknown()).optional().describe(description);
|
|
@@ -74,9 +105,9 @@ const materialResult = (material: MaterialManifestV1) => ({
|
|
|
74
105
|
material,
|
|
75
106
|
portableSpec: materialLibraryPortableSpec(material),
|
|
76
107
|
});
|
|
77
|
-
const unknownMaterial = (materialId: string, revisionId
|
|
108
|
+
const unknownMaterial = (materialId: string, revisionId: string | undefined, listCall: string) =>
|
|
78
109
|
new Error(
|
|
79
|
-
`Unknown material ${materialId}${revisionId ? ` at ${revisionId}` : ''}.
|
|
110
|
+
`Unknown material ${materialId}${revisionId ? ` at ${revisionId}` : ''}. ${listCall} lists the materials in this workspace with their revision IDs.`,
|
|
80
111
|
);
|
|
81
112
|
/**
|
|
82
113
|
* The revision a `get` without `revisionId` means: the material's only one.
|
|
@@ -84,13 +115,17 @@ const unknownMaterial = (materialId: string, revisionId?: string) =>
|
|
|
84
115
|
* caller to choose (two live sessions of 1 October 2026 called get with the
|
|
85
116
|
* id alone and were refused).
|
|
86
117
|
*/
|
|
87
|
-
async function onlyRevision(
|
|
118
|
+
async function onlyRevision(
|
|
119
|
+
library: MaterialLibrary,
|
|
120
|
+
materialId: string,
|
|
121
|
+
listCall: string,
|
|
122
|
+
): Promise<string> {
|
|
88
123
|
const revisions = (await library.list())
|
|
89
124
|
.filter((material) => material.materialId === materialId)
|
|
90
125
|
.map((material) => material.revisionId)
|
|
91
126
|
.sort();
|
|
92
127
|
if (revisions.length === 1) return revisions[0]!;
|
|
93
|
-
if (revisions.length === 0) throw unknownMaterial(materialId);
|
|
128
|
+
if (revisions.length === 0) throw unknownMaterial(materialId, undefined, listCall);
|
|
94
129
|
throw new Error(
|
|
95
130
|
`Material ${materialId} has ${revisions.length} revisions: ${revisions.join(', ')}. Pass revisionId to get one.`,
|
|
96
131
|
);
|
|
@@ -111,12 +146,20 @@ function aliasMaterialId(raw: unknown): unknown {
|
|
|
111
146
|
);
|
|
112
147
|
return { ...rest, materialId: resourceId };
|
|
113
148
|
}
|
|
114
|
-
export function createKilnMaterialDef(
|
|
149
|
+
export function createKilnMaterialDef(
|
|
150
|
+
library: MaterialLibrary,
|
|
151
|
+
presentation: 'grouped' | 'operations' = 'grouped',
|
|
152
|
+
): KilnToolDef {
|
|
153
|
+
const listCall =
|
|
154
|
+
presentation === 'operations'
|
|
155
|
+
? 'kiln_material_search({ scope: "saved" })'
|
|
156
|
+
: "kiln_material { action: 'list' }";
|
|
115
157
|
return {
|
|
116
158
|
name: 'kiln_material',
|
|
117
159
|
description:
|
|
118
160
|
'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.',
|
|
119
161
|
inputSchema: materialToolInput,
|
|
162
|
+
actionContract: MATERIAL_CONTRACT,
|
|
120
163
|
annotations: {
|
|
121
164
|
readOnlyHint: false,
|
|
122
165
|
destructiveHint: false,
|
|
@@ -126,7 +169,7 @@ export function createKilnMaterialDef(library: MaterialLibrary): KilnToolDef {
|
|
|
126
169
|
async run(raw) {
|
|
127
170
|
assertMaterialJson(raw);
|
|
128
171
|
const input = materialToolInput.parse(aliasMaterialId(raw));
|
|
129
|
-
requireActionFields('kiln_material', input.action, input,
|
|
172
|
+
requireActionFields('kiln_material', input.action, input, MATERIAL_CONTRACT[input.action]);
|
|
130
173
|
switch (input.action) {
|
|
131
174
|
case 'presets':
|
|
132
175
|
return {
|
|
@@ -170,13 +213,14 @@ export function createKilnMaterialDef(library: MaterialLibrary): KilnToolDef {
|
|
|
170
213
|
};
|
|
171
214
|
}
|
|
172
215
|
case 'get': {
|
|
173
|
-
const revisionId =
|
|
216
|
+
const revisionId =
|
|
217
|
+
input.revisionId ?? (await onlyRevision(library, input.materialId!, listCall));
|
|
174
218
|
try {
|
|
175
219
|
return materialResult((await library.read(input.materialId!, revisionId)).manifest);
|
|
176
220
|
} catch (error) {
|
|
177
221
|
// Rule 9: an unknown id names the call that lists the known ones.
|
|
178
222
|
if ((error as NodeJS.ErrnoException).code === 'ENOENT')
|
|
179
|
-
throw unknownMaterial(input.materialId!, revisionId);
|
|
223
|
+
throw unknownMaterial(input.materialId!, revisionId, listCall);
|
|
180
224
|
throw error;
|
|
181
225
|
}
|
|
182
226
|
}
|