@call-me-sensei/toonlab 0.4.19 → 0.4.21
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/NPM-LIBRARY.md +230 -0
- package/README.md +78 -36
- package/agents/PROMPTS.md +91 -0
- package/agents/README.md +9 -14
- package/agents/claude/CLAUDE.md +8 -0
- package/agents/codex/AGENTS.md +161 -0
- package/agents/cursor/toonlab.mdc +37 -0
- package/agents/references/mcp-asset-discovery.md +12 -9
- package/agents/references/runtime-entry-points.md +5 -4
- package/agents/skills/claude/asset-sourcing/SKILL.md +9 -6
- package/agents/skills/claude/rock-ground-shaders/SKILL.md +16 -0
- package/agents/skills/claude/vegetation-sky/SKILL.md +7 -4
- package/agents/skills/codex/asset-sourcing/SKILL.md +9 -6
- package/agents/skills/codex/rock-ground-shaders/SKILL.md +16 -0
- package/agents/skills/codex/vegetation-sky/SKILL.md +7 -4
- package/cli/toonlab.mjs +3 -2
- package/database/fal-patina.mjs +131 -0
- package/database/generation-service.mjs +108 -7
- package/database/migrations/0007_creation_revisions.sql +80 -0
- package/database/migrations/0008_creation_revision_hardening.sql +98 -0
- package/database/migrations/0009_creation_revision_delete_integrity.sql +11 -0
- package/database/migrations/0010_external_catalog_assets.sql +28 -0
- package/database/providers.mjs +195 -16
- package/database/repository.mjs +518 -70
- package/database/seeds/catalog/0003_2026-08-open-assets.sql +159086 -0
- package/database/seeds/catalog/0004_2026-08-c7-rocks.sql +39405 -0
- package/mcp/public-catalog.mjs +4 -52
- package/mcp/server.mjs +175 -12
- package/mcp/vite-plugin.mjs +78 -2
- package/package.json +32 -12
- package/scripts/generate-catalog-seed.mjs +34 -7
- package/src/asset-policy/catalogLicenses.js +64 -6
- package/src/assetlib/loadImported.js +15 -1
- package/src/catalog/officialCatalogAssetRuntime.js +13 -2
- package/src/catalog/officialCatalogLod.js +34 -1
- package/src/catalog/officialCatalogPlacement.js +29 -14
- package/src/character/animationRetarget.js +2 -0
- package/src/character/characterRig.js +6 -1
- package/src/character/characterRuntime.js +175 -7
- package/src/environment/environmentMaterialAdapter.js +30 -0
- package/src/environment/environmentPresets.js +90 -0
- package/src/environment/environmentSunShadowPass.js +42 -0
- package/src/environment/toonLabSurfaceLighting.js +108 -10
- package/src/environment/urbanPropMaterial.js +104 -2
- package/src/lighting/lightingSystem.js +65 -10
- package/src/renderer/index.js +7 -0
- package/src/renderer/styleComparison.js +985 -0
- package/src/rock-shader/index.js +5 -0
- package/src/rock-shader/rockGeometryDetail.js +701 -0
- package/src/rock-shader/rockMaterial.js +782 -39
- package/src/rock-shader/rockRegionRuntime.js +185 -0
- package/src/rock-shader/rockSemanticMaterialRuntime.js +337 -0
- package/src/rock-shader/rockShaderRuntime.js +354 -67
- package/src/rock-shader/rockShaderSettings.js +246 -16
- package/src/rock-shader/rockTangentIntegrity.js +136 -0
- package/src/rock-shader/rockTextureIntegrity.js +115 -0
- package/src/rockgen/index.js +1 -0
- package/src/rockgen/rockDocument.js +27 -3
- package/src/rockgen/surface/c7GeologySurface.js +372 -0
- package/src/shaders-tsl/water.js +8 -0
- package/src/sky/cloudShadow.js +25 -1
- package/src/sky/skySystem.js +9 -0
- package/src/styles/index.js +23 -0
- package/src/styles/neutralStylePresets.js +712 -0
- package/src/styles/sceneStyleRuntime.js +51 -4
- package/src/styles/styleAdapters.js +13 -2
- package/src/styles/styleBundle.js +1 -1
- package/src/texgen/evaluateTexture.js +30 -3
- package/src/texgen/textureGenerators.js +63 -31
- package/src/texgen/textureSettings.js +25 -8
- package/src/toon/toonSettings.js +96 -2
- package/src/vegetation/branchTree.js +327 -42
- package/src/vegetation/index.js +24 -2
- package/src/vegetation/scatter.js +372 -1
- package/src/vegetation/stylizedTree.js +312 -126
- package/src/vegetation/stylizedTreeFoliage.js +229 -7
- package/src/vegetation/treeSurfaceTextures.js +328 -0
- package/src/version.js +1 -1
- package/src/water/waterSettings.js +15 -7
- package/types/asset-policy/catalogLicenses.d.ts +2 -0
- package/types/asset-policy/index.d.ts +2 -0
- package/types/assetlib/loadImported.d.ts +3 -1
- package/types/catalog/officialCatalogLod.d.ts +63 -5
- package/types/character/characterRig.d.ts +59 -0
- package/types/character/characterRuntime.d.ts +1 -0
- package/types/environment/environmentMaterialAdapter.d.ts +3 -1
- package/types/environment/toonLabSurfaceLighting.d.ts +20 -3
- package/types/index.d.ts +89 -7
- package/types/lighting/lightingSystem.d.ts +7 -0
- package/types/react/index.d.ts +20 -0
- package/types/renderer/index.d.ts +1 -0
- package/types/renderer/styleComparison.d.ts +409 -0
- package/types/rock-shader/index.d.ts +5 -0
- package/types/rock-shader/rockGeometryDetail.d.ts +300 -0
- package/types/rock-shader/rockMaterial.d.ts +3 -0
- package/types/rock-shader/rockRegionRuntime.d.ts +86 -0
- package/types/rock-shader/rockSemanticMaterialRuntime.d.ts +29 -0
- package/types/rock-shader/rockShaderRuntime.d.ts +2 -0
- package/types/rock-shader/rockShaderSettings.d.ts +19 -5
- package/types/rock-shader/rockTangentIntegrity.d.ts +51 -0
- package/types/rock-shader/rockTextureIntegrity.d.ts +61 -0
- package/types/rockgen/index.d.ts +1 -0
- package/types/rockgen/rockDocument.d.ts +29 -1
- package/types/rockgen/surface/c7GeologySurface.d.ts +8 -0
- package/types/sky/cloudShadow.d.ts +1 -0
- package/types/styles/index.d.ts +1 -0
- package/types/styles/neutralStylePresets.d.ts +944 -0
- package/types/styles/sceneStyleRuntime.d.ts +20 -0
- package/types/vegetation/branchTree.d.ts +4 -0
- package/types/vegetation/index.d.ts +9 -7
- package/types/vegetation/scatter.d.ts +1 -0
- package/types/vegetation/stylizedTree.d.ts +1 -2
- package/types/vegetation/stylizedTreeFoliage.d.ts +1 -0
- package/types/version.d.ts +1 -1
- package/agents/references/geology-playbook.md +0 -118
- package/agents/skills/claude/karst-cliff-construction/SKILL.md +0 -258
- package/agents/skills/claude/outdoor-world/SKILL.md +0 -492
- package/agents/skills/claude/outdoor-world/references/coastal-landform-formation.md +0 -100
- package/agents/skills/codex/karst-cliff-construction/SKILL.md +0 -258
- package/agents/skills/codex/outdoor-world/SKILL.md +0 -492
- package/agents/skills/codex/outdoor-world/references/coastal-landform-formation.md +0 -100
- package/src/vegetation/legacyTreePresets.js +0 -120
- package/types/vegetation/legacyTreePresets.d.ts +0 -22
package/mcp/public-catalog.mjs
CHANGED
|
@@ -25,62 +25,14 @@ function entry(input) {
|
|
|
25
25
|
});
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
const CURATED_PLANT_RECIPES = Object.freeze([
|
|
29
|
-
entry({
|
|
30
|
-
cluster: 'vegetation',
|
|
31
|
-
description: 'Mid-size broadleaf starting recipe for anime environments.',
|
|
32
|
-
id: 'tree/broadleaf/sensei',
|
|
33
|
-
kind: 'recipe',
|
|
34
|
-
label: 'Sensei broadleaf',
|
|
35
|
-
recipe: {
|
|
36
|
-
options: { seed: 3, size: 3.2 },
|
|
37
|
-
schema: 'treeRecipe',
|
|
38
|
-
type: 'tree',
|
|
39
|
-
version: 1,
|
|
40
|
-
},
|
|
41
|
-
spawn: 'createPlantFromRecipe(entry.recipe)',
|
|
42
|
-
tags: ['tree', 'vegetation', 'anime'],
|
|
43
|
-
thumbnail: thumbnailFor('tree/broadleaf/sensei'),
|
|
44
|
-
}),
|
|
45
|
-
entry({
|
|
46
|
-
cluster: 'vegetation',
|
|
47
|
-
description: 'Ground-hugging foliage mass for yards and path edges.',
|
|
48
|
-
id: 'tree/bush/hedge',
|
|
49
|
-
kind: 'recipe',
|
|
50
|
-
label: 'Hedge bush',
|
|
51
|
-
recipe: {
|
|
52
|
-
options: { seed: 5, size: 1.2 },
|
|
53
|
-
schema: 'treeRecipe',
|
|
54
|
-
type: 'bush',
|
|
55
|
-
version: 1,
|
|
56
|
-
},
|
|
57
|
-
spawn: 'createPlantFromRecipe(entry.recipe)',
|
|
58
|
-
tags: ['bush', 'vegetation', 'anime'],
|
|
59
|
-
thumbnail: thumbnailFor('tree/bush/hedge'),
|
|
60
|
-
}),
|
|
61
|
-
entry({
|
|
62
|
-
cluster: 'vegetation',
|
|
63
|
-
description: 'Flowering plant with daisy heads.',
|
|
64
|
-
id: 'tree/flower/daisy',
|
|
65
|
-
kind: 'recipe',
|
|
66
|
-
label: 'Daisy flower',
|
|
67
|
-
recipe: {
|
|
68
|
-
options: { seed: 9, size: 0.5, species: 'daisy' },
|
|
69
|
-
schema: 'treeRecipe',
|
|
70
|
-
type: 'flower',
|
|
71
|
-
version: 1,
|
|
72
|
-
},
|
|
73
|
-
spawn: 'createPlantFromRecipe(entry.recipe)',
|
|
74
|
-
tags: ['flower', 'vegetation', 'anime'],
|
|
75
|
-
thumbnail: thumbnailFor('tree/flower/daisy'),
|
|
76
|
-
}),
|
|
77
|
-
]);
|
|
78
|
-
|
|
79
28
|
let cached = null;
|
|
80
29
|
|
|
81
30
|
export function publicMcpCatalogEntries() {
|
|
82
31
|
if (cached) return cached;
|
|
83
|
-
|
|
32
|
+
// Authored vegetation recipes are Gallery assets, not package built-ins.
|
|
33
|
+
// The packaged MCP discovers them from the public Gallery so provenance,
|
|
34
|
+
// license, review state, and immutable documents stay attached.
|
|
35
|
+
const entries = [];
|
|
84
36
|
|
|
85
37
|
for (const option of getRockgenPresetOptions()) {
|
|
86
38
|
const id = `rock/${slug(option.value)}`;
|
package/mcp/server.mjs
CHANGED
|
@@ -82,6 +82,8 @@ const SERVER_NAME = 'toonlab-oss';
|
|
|
82
82
|
const SERVER_VERSION = JSON.parse(
|
|
83
83
|
readFileSync(new URL('../package.json', import.meta.url), 'utf8'),
|
|
84
84
|
).version;
|
|
85
|
+
const PUBLIC_GALLERY_ENDPOINT = process.env.TOONLAB_PUBLIC_GALLERY_URL
|
|
86
|
+
?? 'https://toonlab.io/api/v1/creations';
|
|
85
87
|
const LATEST_PROTOCOL_VERSION = '2025-11-25';
|
|
86
88
|
const SUPPORTED_PROTOCOL_VERSIONS = new Set([
|
|
87
89
|
LATEST_PROTOCOL_VERSION,
|
|
@@ -103,7 +105,9 @@ function parseArguments(argv) {
|
|
|
103
105
|
}
|
|
104
106
|
|
|
105
107
|
const { workspace } = parseArguments(process.argv.slice(2));
|
|
106
|
-
const
|
|
108
|
+
const legacyWorkspaceMode = process.env.TOONLAB_LEGACY_WORKSPACE;
|
|
109
|
+
const legacyWorkspace = legacyWorkspaceMode === '1'
|
|
110
|
+
|| (legacyWorkspaceMode !== '0' && !process.env.DATABASE_URL);
|
|
107
111
|
|
|
108
112
|
async function getWorkspaceInfo() {
|
|
109
113
|
if (legacyWorkspace) return getLegacyWorkspaceInfo(workspace);
|
|
@@ -287,6 +291,112 @@ function normalizeDiscovery(entry, summary, { domain = '', policy = null } = {})
|
|
|
287
291
|
};
|
|
288
292
|
}
|
|
289
293
|
|
|
294
|
+
function publicGalleryUrl(path = '') {
|
|
295
|
+
let endpoint;
|
|
296
|
+
try {
|
|
297
|
+
endpoint = new URL(PUBLIC_GALLERY_ENDPOINT);
|
|
298
|
+
} catch {
|
|
299
|
+
throw new Error('TOONLAB_PUBLIC_GALLERY_URL must be an absolute HTTP(S) URL.');
|
|
300
|
+
}
|
|
301
|
+
if (!['http:', 'https:'].includes(endpoint.protocol)) {
|
|
302
|
+
throw new Error('TOONLAB_PUBLIC_GALLERY_URL must use HTTP(S).');
|
|
303
|
+
}
|
|
304
|
+
endpoint.search = '';
|
|
305
|
+
endpoint.hash = '';
|
|
306
|
+
if (!path) return endpoint;
|
|
307
|
+
endpoint.pathname = `${endpoint.pathname.replace(/\/$/u, '')}/${encodeURIComponent(path)}`;
|
|
308
|
+
return endpoint;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function absoluteGalleryReference(reference) {
|
|
312
|
+
if (typeof reference !== 'string' || !reference.trim()) return null;
|
|
313
|
+
return new URL(reference, publicGalleryUrl()).href;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
async function fetchPublicGalleryJson(url) {
|
|
317
|
+
let response;
|
|
318
|
+
try {
|
|
319
|
+
response = await fetch(url, {
|
|
320
|
+
headers: { accept: 'application/json' },
|
|
321
|
+
signal: AbortSignal.timeout(15_000),
|
|
322
|
+
});
|
|
323
|
+
} catch (cause) {
|
|
324
|
+
throw new Error(`ToonLab public Gallery request failed: ${cause?.message ?? String(cause)}`);
|
|
325
|
+
}
|
|
326
|
+
if (!response.ok) {
|
|
327
|
+
throw new Error(`ToonLab public Gallery returned HTTP ${response.status}.`);
|
|
328
|
+
}
|
|
329
|
+
try {
|
|
330
|
+
return await response.json();
|
|
331
|
+
} catch {
|
|
332
|
+
throw new Error('ToonLab public Gallery returned invalid JSON.');
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function normalizePublicGalleryItem(item, args = {}) {
|
|
337
|
+
const id = String(item?.id ?? '').trim();
|
|
338
|
+
if (!id) throw new Error('ToonLab public Gallery returned an item without an id.');
|
|
339
|
+
const type = String(item.type ?? 'creation');
|
|
340
|
+
const entry = {
|
|
341
|
+
attribution: item.attribution ?? null,
|
|
342
|
+
author: item.author ?? null,
|
|
343
|
+
cluster: type,
|
|
344
|
+
description: item.description ?? null,
|
|
345
|
+
documentUrl: absoluteGalleryReference(item.download ?? item.documentUrl),
|
|
346
|
+
id,
|
|
347
|
+
kind: type,
|
|
348
|
+
label: String(item.label ?? id),
|
|
349
|
+
license: item.license ?? null,
|
|
350
|
+
source: 'gallery',
|
|
351
|
+
sourceClass: 'toonlab-gallery',
|
|
352
|
+
tags: Array.isArray(item.tags) ? item.tags.map(String) : [],
|
|
353
|
+
thumbnail: absoluteGalleryReference(item.thumbnail),
|
|
354
|
+
updatedAt: item.updated_at ?? null,
|
|
355
|
+
url: absoluteGalleryReference(item.url ?? `/c/${item.slug ?? id}`),
|
|
356
|
+
};
|
|
357
|
+
return normalizeDiscovery(entry, entry, args);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
async function searchPublicGallery(args = {}) {
|
|
361
|
+
const limit = limitValue(args.limit, 20, 60);
|
|
362
|
+
const offset = Math.max(0, Math.floor(Number(args.offset) || 0));
|
|
363
|
+
const endpoint = publicGalleryUrl();
|
|
364
|
+
endpoint.searchParams.set('limit', String(limit));
|
|
365
|
+
endpoint.searchParams.set('offset', String(offset));
|
|
366
|
+
if (String(args.query ?? '').trim()) endpoint.searchParams.set('q', String(args.query).trim());
|
|
367
|
+
if (String(args.type ?? '').trim()) endpoint.searchParams.set('type', String(args.type).trim());
|
|
368
|
+
const response = await fetchPublicGalleryJson(endpoint);
|
|
369
|
+
if (!Array.isArray(response?.items)) {
|
|
370
|
+
throw new Error('ToonLab public Gallery response is missing items.');
|
|
371
|
+
}
|
|
372
|
+
const items = response.items.map((item) => normalizePublicGalleryItem(item, args));
|
|
373
|
+
return {
|
|
374
|
+
count: items.length,
|
|
375
|
+
items,
|
|
376
|
+
limit,
|
|
377
|
+
nextOffset: items.length === limit ? offset + items.length : null,
|
|
378
|
+
offset,
|
|
379
|
+
source: 'toonlab-public-gallery',
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
async function getPublicGalleryAsset(args = {}) {
|
|
384
|
+
const id = String(args.id ?? '').trim();
|
|
385
|
+
if (!id) throw new Error('A public Gallery asset id or slug is required.');
|
|
386
|
+
const item = await fetchPublicGalleryJson(publicGalleryUrl(id));
|
|
387
|
+
const normalized = normalizePublicGalleryItem({
|
|
388
|
+
...item,
|
|
389
|
+
download: item.documentUrl ?? null,
|
|
390
|
+
thumbnail: item.thumbnail ?? null,
|
|
391
|
+
url: item.url ?? `/c/${item.slug ?? id}`,
|
|
392
|
+
}, args);
|
|
393
|
+
return {
|
|
394
|
+
...normalized,
|
|
395
|
+
document: item.document ?? null,
|
|
396
|
+
files: Array.isArray(item.files) ? item.files : [],
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
|
|
290
400
|
function summarizeCatalogEntry(entry) {
|
|
291
401
|
const metadata = entry.metadata && typeof entry.metadata === 'object' && !Array.isArray(entry.metadata)
|
|
292
402
|
? entry.metadata
|
|
@@ -987,7 +1097,7 @@ const TOOLS = [
|
|
|
987
1097
|
},
|
|
988
1098
|
{
|
|
989
1099
|
annotations: { readOnlyHint: true },
|
|
990
|
-
description: 'Search built-in
|
|
1100
|
+
description: 'Search approved built-in generator/settings entries, the local released official catalog, saved library entries, lab presets, and files on disk. Official rock summaries include compact dimensions and taxonomy metadata; follow nextOffset until null.',
|
|
991
1101
|
inputSchema: {
|
|
992
1102
|
additionalProperties: false,
|
|
993
1103
|
properties: {
|
|
@@ -1006,6 +1116,40 @@ const TOOLS = [
|
|
|
1006
1116
|
name: 'search_assets',
|
|
1007
1117
|
title: 'Search ToonLab assets',
|
|
1008
1118
|
},
|
|
1119
|
+
{
|
|
1120
|
+
annotations: { openWorldHint: true, readOnlyHint: true },
|
|
1121
|
+
description: 'Search public ToonLab Gallery creations with their license, attribution, tags, thumbnail, and immutable portable-document URL. Authored tree recipes live here rather than in npm; use type="tree-recipe" and follow nextOffset until null.',
|
|
1122
|
+
inputSchema: {
|
|
1123
|
+
additionalProperties: false,
|
|
1124
|
+
properties: {
|
|
1125
|
+
domain: { type: 'string' },
|
|
1126
|
+
limit: { maximum: 60, minimum: 1, type: 'integer' },
|
|
1127
|
+
offset: { minimum: 0, type: 'integer' },
|
|
1128
|
+
policy: { type: 'object' },
|
|
1129
|
+
query: { type: 'string' },
|
|
1130
|
+
type: { type: 'string' },
|
|
1131
|
+
},
|
|
1132
|
+
type: 'object',
|
|
1133
|
+
},
|
|
1134
|
+
name: 'search_public_gallery',
|
|
1135
|
+
title: 'Search the ToonLab public Gallery',
|
|
1136
|
+
},
|
|
1137
|
+
{
|
|
1138
|
+
annotations: { openWorldHint: true, readOnlyHint: true },
|
|
1139
|
+
description: 'Get one public ToonLab Gallery creation by publication id or slug, including its complete portable document and immutable public files.',
|
|
1140
|
+
inputSchema: {
|
|
1141
|
+
additionalProperties: false,
|
|
1142
|
+
properties: {
|
|
1143
|
+
domain: { type: 'string' },
|
|
1144
|
+
id: { type: 'string' },
|
|
1145
|
+
policy: { type: 'object' },
|
|
1146
|
+
},
|
|
1147
|
+
required: ['id'],
|
|
1148
|
+
type: 'object',
|
|
1149
|
+
},
|
|
1150
|
+
name: 'get_public_gallery_asset',
|
|
1151
|
+
title: 'Get a ToonLab public Gallery asset',
|
|
1152
|
+
},
|
|
1009
1153
|
{
|
|
1010
1154
|
annotations: { readOnlyHint: true },
|
|
1011
1155
|
description: 'Get a complete asset, recipe, preset, library entry, or workspace file descriptor by id.',
|
|
@@ -1202,7 +1346,7 @@ const TOOLS = [
|
|
|
1202
1346
|
},
|
|
1203
1347
|
{
|
|
1204
1348
|
annotations: { destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
1205
|
-
description: 'Start local managed image
|
|
1349
|
+
description: 'Start local managed image, Fal Patina PBR material, or 3D generation with user-configured provider keys. Material jobs preserve the seamless preview and typed PBR maps as one durable local bundle.',
|
|
1206
1350
|
inputSchema: {
|
|
1207
1351
|
additionalProperties: false,
|
|
1208
1352
|
properties: {
|
|
@@ -1210,12 +1354,27 @@ const TOOLS = [
|
|
|
1210
1354
|
domain: { maxLength: 120, minLength: 1, type: 'string' },
|
|
1211
1355
|
gap_id: { description: 'ID returned by record_asset_gap.', minLength: 1, type: 'string' },
|
|
1212
1356
|
image_model: { enum: ['nano-banana-2-lite', 'nano-banana-2', 'nano-banana-pro'], type: 'string' },
|
|
1213
|
-
kind: { enum: ['image', 'texture_image', 'concept_image', 'text_to_model', 'image_to_model', 'multiview_to_model', 'model_segment'], type: 'string' },
|
|
1357
|
+
kind: { enum: ['image', 'texture_image', 'concept_image', 'material', 'text_to_model', 'image_to_model', 'multiview_to_model', 'model_segment'], type: 'string' },
|
|
1214
1358
|
model_provider: { default: 'meshy', enum: ['meshy', 'tripo'], type: 'string' },
|
|
1215
1359
|
policy: { description: 'Developer-approved asset-sourcing policy.', type: 'object' },
|
|
1216
1360
|
prompt: { maxLength: 600, type: 'string' },
|
|
1217
1361
|
reference_paths: { items: { type: 'string' }, maxItems: 6, type: 'array' },
|
|
1218
|
-
resolution: { default: '1k', enum: ['1k', '2k', '4k'], type: 'string' },
|
|
1362
|
+
resolution: { default: '1k', enum: ['1k', '2k', '4k', '8k'], type: 'string' },
|
|
1363
|
+
maps: { description: 'material only; empty skips PBR estimation.', items: { enum: ['basecolor', 'normal', 'roughness', 'metalness', 'height'], type: 'string' }, maxItems: 5, type: 'array' },
|
|
1364
|
+
image_size: { description: 'material only; a Patina preset or {width,height}.', oneOf: [{ enum: ['square_hd', 'square', 'portrait_4_3', 'portrait_16_9', 'landscape_4_3', 'landscape_16_9'], type: 'string' }, { additionalProperties: false, properties: { width: { maximum: 2048, minimum: 256, type: 'integer' }, height: { maximum: 2048, minimum: 256, type: 'integer' } }, required: ['width', 'height'], type: 'object' }] },
|
|
1365
|
+
upscale_factor: { enum: [0, 2, 4], type: 'integer' },
|
|
1366
|
+
tiling_mode: { enum: ['both', 'horizontal', 'vertical'], type: 'string' },
|
|
1367
|
+
output_format: { enum: ['png', 'jpeg', 'webp'], type: 'string' },
|
|
1368
|
+
num_images: { maximum: 4, minimum: 1, type: 'integer' },
|
|
1369
|
+
num_inference_steps: { maximum: 8, minimum: 1, type: 'integer' },
|
|
1370
|
+
enable_prompt_expansion: { type: 'boolean' },
|
|
1371
|
+
enable_safety_checker: { type: 'boolean' },
|
|
1372
|
+
tile_size: { maximum: 256, minimum: 32, type: 'integer' },
|
|
1373
|
+
tile_stride: { maximum: 128, minimum: 16, type: 'integer' },
|
|
1374
|
+
seed: { type: 'integer' },
|
|
1375
|
+
image_url: { description: 'material only; public HTTPS source texture.', format: 'uri', type: 'string' },
|
|
1376
|
+
mask_url: { description: 'material only; public HTTPS inpainting mask. Requires image_url.', format: 'uri', type: 'string' },
|
|
1377
|
+
strength: { maximum: 1, minimum: 0, type: 'number' },
|
|
1219
1378
|
source_image_path: { description: 'Workspace object path for image_to_model.', type: 'string' },
|
|
1220
1379
|
source_job_id: { description: 'Succeeded image job for image_to_model, or Tripo model job for model_segment.', type: 'string' },
|
|
1221
1380
|
style: { default: 'stylized', enum: ['stylized', 'cartoon', 'raw'], type: 'string' },
|
|
@@ -1225,7 +1384,7 @@ const TOOLS = [
|
|
|
1225
1384
|
type: 'object',
|
|
1226
1385
|
},
|
|
1227
1386
|
name: 'generate_ai_asset',
|
|
1228
|
-
title: 'Generate an image or 3D asset with local provider keys',
|
|
1387
|
+
title: 'Generate an image, PBR material, or 3D asset with local provider keys',
|
|
1229
1388
|
},
|
|
1230
1389
|
{
|
|
1231
1390
|
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
@@ -1257,7 +1416,7 @@ const TOOLS = [
|
|
|
1257
1416
|
},
|
|
1258
1417
|
{
|
|
1259
1418
|
annotations: { destructiveHint: false, idempotentHint: true },
|
|
1260
|
-
description: 'Save a succeeded local 3D
|
|
1419
|
+
description: 'Save a succeeded local 3D model or PBR material job into the editable ToonLab library with provider provenance and durable local files.',
|
|
1261
1420
|
inputSchema: {
|
|
1262
1421
|
additionalProperties: false,
|
|
1263
1422
|
properties: { job_id: { type: 'string' }, label: { maxLength: 120, type: 'string' } },
|
|
@@ -1265,7 +1424,7 @@ const TOOLS = [
|
|
|
1265
1424
|
type: 'object',
|
|
1266
1425
|
},
|
|
1267
1426
|
name: 'save_generated_asset',
|
|
1268
|
-
title: 'Save a generated
|
|
1427
|
+
title: 'Save a generated model or material',
|
|
1269
1428
|
},
|
|
1270
1429
|
{
|
|
1271
1430
|
annotations: { openWorldHint: true, readOnlyHint: true },
|
|
@@ -1398,6 +1557,8 @@ async function callTool(name, args = {}) {
|
|
|
1398
1557
|
case 'get_lab_features': return getLabFeatures(args.lab);
|
|
1399
1558
|
case 'create_lab_document': return createLabDocumentTool(args);
|
|
1400
1559
|
case 'search_assets': return searchAssets(args);
|
|
1560
|
+
case 'search_public_gallery': return searchPublicGallery(args);
|
|
1561
|
+
case 'get_public_gallery_asset': return getPublicGalleryAsset(args);
|
|
1401
1562
|
case 'get_asset': return getAsset(args);
|
|
1402
1563
|
case 'list_my_creations': return listMyCreations(args);
|
|
1403
1564
|
case 'get_my_creation': return getMyCreation(args);
|
|
@@ -1423,8 +1584,8 @@ async function callTool(name, args = {}) {
|
|
|
1423
1584
|
policyDecision,
|
|
1424
1585
|
pollAfterMs: 5_000,
|
|
1425
1586
|
next: job.status === 'running'
|
|
1426
|
-
? 'Poll get_generation_job until succeeded, failed, or cancelled; then
|
|
1427
|
-
: 'The provider completed inline. Save a succeeded
|
|
1587
|
+
? 'Poll get_generation_job until succeeded, failed, or cancelled; then save a succeeded model or material job if it belongs in the Library.'
|
|
1588
|
+
: 'The provider completed inline. Save a succeeded model or material job if it belongs in the Library.',
|
|
1428
1589
|
};
|
|
1429
1590
|
}
|
|
1430
1591
|
case 'get_generation_job': {
|
|
@@ -1461,13 +1622,15 @@ async function callTool(name, args = {}) {
|
|
|
1461
1622
|
case 'get_generation_capabilities': return {
|
|
1462
1623
|
managedLocal: {
|
|
1463
1624
|
...providerConfiguration(),
|
|
1464
|
-
kinds: ['image', 'texture_image', 'concept_image', 'text_to_model', 'image_to_model', 'multiview_to_model', 'model_segment'],
|
|
1625
|
+
kinds: ['image', 'texture_image', 'concept_image', 'material', 'text_to_model', 'image_to_model', 'multiview_to_model', 'model_segment'],
|
|
1626
|
+
patinaMaterialFlow: 'prompt or source image -> seamless preview + typed PBR map bundle (1k-8k)',
|
|
1465
1627
|
meshyTextFlow: 'selected image_model concept_image -> meshy-7 image_to_model',
|
|
1466
1628
|
tools: ['generate_ai_asset', 'get_generation_job', 'get_generation_jobs', 'save_generated_asset'],
|
|
1467
1629
|
},
|
|
1468
1630
|
available: [
|
|
1469
1631
|
'Generate deterministic recipes from the stable public ToonLab catalog entries.',
|
|
1470
1632
|
'Save and retrieve editable lab documents through the shared .toonlab workspace.',
|
|
1633
|
+
'Search and retrieve authored public Gallery recipes with license and provenance.',
|
|
1471
1634
|
'Search and import CC0 assets from Poly Haven and ambientCG.',
|
|
1472
1635
|
'Use each recipe\'s spawn snippet to integrate it into a Three.js project.',
|
|
1473
1636
|
],
|
|
@@ -1518,7 +1681,7 @@ async function handleRequest(message) {
|
|
|
1518
1681
|
jsonrpc: '2.0',
|
|
1519
1682
|
result: {
|
|
1520
1683
|
capabilities: { resources: {}, tools: {} },
|
|
1521
|
-
instructions: 'ToonLab builds anime-style games, characters, and environments for Three.js. Call list_live_labs and get_lab_features when authoring a Lab artifact, use create_lab_document for a valid starter, and prefer mutate_lab_creation over hand-editing structural arrays. Official Rock details include a source-GLB starter. This OSS server may edit any non-secret creation or lab-state entry in its local workspace; deletions require explicit confirmation. Load the selected style bundle first. If no asset-sourcing policy exists, ask the developer, then continue with library-first advisory discovery while recording the unresolved decision. Search the project and
|
|
1684
|
+
instructions: 'ToonLab builds anime-style games, characters, and environments for Three.js. Call list_live_labs and get_lab_features when authoring a Lab artifact, use create_lab_document for a valid starter, and prefer mutate_lab_creation over hand-editing structural arrays. Official Rock details include a source-GLB starter. This OSS server may edit any non-secret creation or lab-state entry in its local workspace; deletions require explicit confirmation. Load the selected style bundle first. If no asset-sourcing policy exists, ask the developer, then continue with library-first advisory discovery while recording the unresolved decision. Search the project and local library first, then search_public_gallery for reviewed authored assets such as tree recipes, before external sources. Generate or hand-author only when policy permits. Validate every candidate, preserve provenance, evaluate anime-fit in scene, and record a gap before adding a custom shader, texture, model, or adapter. When local provider keys are configured, generate_ai_asset exposes image generation, Meshy 7 image/multiview-to-3D, and Text -> selected image model -> Meshy 7 through MCP.',
|
|
1522
1685
|
protocolVersion: SUPPORTED_PROTOCOL_VERSIONS.has(requested) ? requested : LATEST_PROTOCOL_VERSION,
|
|
1523
1686
|
serverInfo: { name: SERVER_NAME, version: SERVER_VERSION },
|
|
1524
1687
|
},
|
package/mcp/vite-plugin.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import { join, resolve } from 'node:path';
|
|
|
3
3
|
import { readWorkspaceFile, resolveWorkspacePath } from './workspace.mjs';
|
|
4
4
|
import {
|
|
5
5
|
clearLabState,
|
|
6
|
+
annotateCreationRevision,
|
|
6
7
|
cancelGenerationJob,
|
|
7
8
|
createGenerationJob,
|
|
8
9
|
databaseInfo,
|
|
@@ -11,14 +12,19 @@ import {
|
|
|
11
12
|
hasLegacyImport,
|
|
12
13
|
finishGenerationJob,
|
|
13
14
|
getCatalogAsset,
|
|
15
|
+
getCreationRevision,
|
|
14
16
|
getGenerationJob,
|
|
15
17
|
listCatalogAssets,
|
|
18
|
+
listCatalogFacets,
|
|
16
19
|
listGenerationJobs,
|
|
20
|
+
listCreationRevisions,
|
|
17
21
|
listLibraryEntries,
|
|
18
22
|
migrateLegacy,
|
|
19
23
|
providerConfiguration,
|
|
20
24
|
readLabState,
|
|
21
25
|
readObject,
|
|
26
|
+
resolveStyleBundleEntry,
|
|
27
|
+
restoreCreationRevision,
|
|
22
28
|
saveLibraryEntry,
|
|
23
29
|
saveObject,
|
|
24
30
|
setLabState,
|
|
@@ -30,6 +36,7 @@ import {
|
|
|
30
36
|
pollTripoTask,
|
|
31
37
|
runProvider,
|
|
32
38
|
} from '../database/providers.mjs';
|
|
39
|
+
import { storeManagedMaterial } from '../database/generation-service.mjs';
|
|
33
40
|
|
|
34
41
|
const JSON_LIMIT = 8 * 1024 * 1024;
|
|
35
42
|
const FILE_LIMIT = 128 * 1024 * 1024;
|
|
@@ -97,6 +104,17 @@ function clientJob(job) {
|
|
|
97
104
|
...job.result,
|
|
98
105
|
...(job.result.file ? { file: clientFile(job.result.file) } : {}),
|
|
99
106
|
...(job.result.previewFile ? { previewFile: clientFile(job.result.previewFile) } : {}),
|
|
107
|
+
...(job.result.materialBundle ? {
|
|
108
|
+
materialBundle: {
|
|
109
|
+
...job.result.materialBundle,
|
|
110
|
+
manifestFile: clientFile(job.result.materialBundle.manifestFile),
|
|
111
|
+
variants: (job.result.materialBundle.variants ?? []).map((variant) => ({
|
|
112
|
+
...variant,
|
|
113
|
+
preview: clientFile(variant.preview),
|
|
114
|
+
maps: Object.fromEntries(Object.entries(variant.maps ?? {}).map(([key, file]) => [key, clientFile(file)])),
|
|
115
|
+
})),
|
|
116
|
+
},
|
|
117
|
+
} : {}),
|
|
100
118
|
}
|
|
101
119
|
: job.result;
|
|
102
120
|
return {
|
|
@@ -125,17 +143,18 @@ export function toonlabWorkspacePlugin({ rootDirectory = process.cwd(), workspac
|
|
|
125
143
|
async function runGeneration(body) {
|
|
126
144
|
const provider = String(body.provider ?? '');
|
|
127
145
|
const kind = String(body.kind ?? '');
|
|
128
|
-
if (!['ark', 'gemini', 'meshy', 'openai', 'tripo'].includes(provider)) {
|
|
146
|
+
if (!['ark', 'fal', 'gemini', 'meshy', 'openai', 'tripo'].includes(provider)) {
|
|
129
147
|
throw Object.assign(new Error('Unsupported provider.'), { statusCode: 400 });
|
|
130
148
|
}
|
|
131
149
|
const imageKinds = new Set(['image', 'texture_image', 'concept_image']);
|
|
150
|
+
const materialKinds = new Set(['material']);
|
|
132
151
|
const modelKinds = new Set([
|
|
133
152
|
'text_to_model',
|
|
134
153
|
'image_to_model',
|
|
135
154
|
'multiview_to_model',
|
|
136
155
|
'model_segment',
|
|
137
156
|
]);
|
|
138
|
-
if (!imageKinds.has(kind) && !modelKinds.has(kind)) {
|
|
157
|
+
if (!imageKinds.has(kind) && !modelKinds.has(kind) && !materialKinds.has(kind)) {
|
|
139
158
|
throw Object.assign(new Error('Unsupported generation kind.'), { statusCode: 400 });
|
|
140
159
|
}
|
|
141
160
|
const providerKinds = {
|
|
@@ -148,6 +167,9 @@ export function toonlabWorkspacePlugin({ rootDirectory = process.cwd(), workspac
|
|
|
148
167
|
if (imageKinds.has(kind) && provider !== 'gemini') {
|
|
149
168
|
throw Object.assign(new Error('OSS image generation currently supports Gemini only.'), { statusCode: 400 });
|
|
150
169
|
}
|
|
170
|
+
if (materialKinds.has(kind) && provider !== 'fal') {
|
|
171
|
+
throw Object.assign(new Error('PBR material generation requires Fal Patina.'), { statusCode: 400 });
|
|
172
|
+
}
|
|
151
173
|
const requestBody = body.request && typeof body.request === 'object'
|
|
152
174
|
? body.request
|
|
153
175
|
: {};
|
|
@@ -213,6 +235,14 @@ export function toonlabWorkspacePlugin({ rootDirectory = process.cwd(), workspac
|
|
|
213
235
|
status: 200,
|
|
214
236
|
};
|
|
215
237
|
}
|
|
238
|
+
if (result.material) {
|
|
239
|
+
return {
|
|
240
|
+
job: clientJob(await finishGenerationJob(job.id, {
|
|
241
|
+
result: await storeManagedMaterial(workspacePath, job.id, result),
|
|
242
|
+
})),
|
|
243
|
+
status: 200,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
216
246
|
return {
|
|
217
247
|
job: clientJob(await finishGenerationJob(job.id, { result })),
|
|
218
248
|
status: 200,
|
|
@@ -411,13 +441,20 @@ export function toonlabWorkspacePlugin({ rootDirectory = process.cwd(), workspac
|
|
|
411
441
|
if (request.method !== 'GET') return methodNotAllowed(response, ['GET']);
|
|
412
442
|
return sendJson(response, 200, await listCatalogAssets({
|
|
413
443
|
kind: url.searchParams.get('kind') ?? '',
|
|
444
|
+
license: url.searchParams.get('license') ?? '',
|
|
414
445
|
limit: url.searchParams.get('limit') ?? 60,
|
|
415
446
|
offset: url.searchParams.get('offset') ?? 0,
|
|
416
447
|
q: url.searchParams.get('q') ?? '',
|
|
448
|
+
size: url.searchParams.get('size') ?? '',
|
|
417
449
|
source: url.searchParams.get('source') ?? '',
|
|
418
450
|
}));
|
|
419
451
|
}
|
|
420
452
|
|
|
453
|
+
if (url.pathname === '/api/toonlab/catalog-facets') {
|
|
454
|
+
if (request.method !== 'GET') return methodNotAllowed(response, ['GET']);
|
|
455
|
+
return sendJson(response, 200, await listCatalogFacets());
|
|
456
|
+
}
|
|
457
|
+
|
|
421
458
|
if (url.pathname.startsWith('/api/toonlab/catalog/')) {
|
|
422
459
|
if (request.method !== 'GET') return methodNotAllowed(response, ['GET']);
|
|
423
460
|
const id = decodeURIComponent(url.pathname.slice('/api/toonlab/catalog/'.length));
|
|
@@ -513,6 +550,45 @@ export function toonlabWorkspacePlugin({ rootDirectory = process.cwd(), workspac
|
|
|
513
550
|
}
|
|
514
551
|
|
|
515
552
|
if (url.pathname.startsWith('/api/toonlab/library/')) {
|
|
553
|
+
const resolvedMatch = url.pathname.match(/^\/api\/toonlab\/library\/([^/]+)\/resolved$/);
|
|
554
|
+
if (resolvedMatch) {
|
|
555
|
+
if (request.method !== 'GET') return methodNotAllowed(response, ['GET']);
|
|
556
|
+
return sendJson(response, 200, {
|
|
557
|
+
bundle: await resolveStyleBundleEntry(decodeURIComponent(resolvedMatch[1])),
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
const revisionMatch = url.pathname.match(/^\/api\/toonlab\/library\/([^/]+)\/revisions(?:\/([^/]+))?(?:\/(restore))?$/);
|
|
561
|
+
if (revisionMatch) {
|
|
562
|
+
const creationId = decodeURIComponent(revisionMatch[1]);
|
|
563
|
+
const revisionId = revisionMatch[2] ? decodeURIComponent(revisionMatch[2]) : null;
|
|
564
|
+
if (!revisionId && request.method === 'GET') {
|
|
565
|
+
const history = await listCreationRevisions(creationId, {
|
|
566
|
+
limit: url.searchParams.get('limit'),
|
|
567
|
+
offset: url.searchParams.get('offset'),
|
|
568
|
+
});
|
|
569
|
+
return sendJson(response, 200, history);
|
|
570
|
+
}
|
|
571
|
+
if (revisionId && !revisionMatch[3] && request.method === 'GET') {
|
|
572
|
+
return sendJson(response, 200, {
|
|
573
|
+
revision: await getCreationRevision(creationId, revisionId),
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
if (revisionId && revisionMatch[3] === 'restore' && request.method === 'POST') {
|
|
577
|
+
return sendJson(response, 200, {
|
|
578
|
+
entry: await restoreCreationRevision(creationId, revisionId),
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
if (revisionId && !revisionMatch[3] && request.method === 'PATCH') {
|
|
582
|
+
return sendJson(response, 200, {
|
|
583
|
+
revision: await annotateCreationRevision(
|
|
584
|
+
creationId,
|
|
585
|
+
revisionId,
|
|
586
|
+
await readJsonBody(request),
|
|
587
|
+
),
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
return methodNotAllowed(response, revisionId ? ['GET', 'PATCH', 'POST'] : ['GET']);
|
|
591
|
+
}
|
|
516
592
|
const id = decodeURIComponent(url.pathname.slice('/api/toonlab/library/'.length));
|
|
517
593
|
if (request.method === 'PUT') {
|
|
518
594
|
const entry = await readJsonBody(request);
|