@genart-dev/mcp-server 0.1.2 → 0.3.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/dist/index.cjs +1057 -24
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1055 -16
- package/dist/index.js.map +1 -1
- package/dist/lib.cjs +1068 -35
- package/dist/lib.cjs.map +1 -1
- package/dist/lib.d.cts +26 -2
- package/dist/lib.d.ts +26 -2
- package/dist/lib.js +1060 -21
- package/dist/lib.js.map +1 -1
- package/package.json +14 -9
package/dist/lib.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
// src/server.ts
|
|
2
2
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
3
|
import { z as z2 } from "zod";
|
|
4
|
+
import { createPluginRegistry } from "@genart-dev/core";
|
|
5
|
+
import typographyPlugin from "@genart-dev/plugin-typography";
|
|
6
|
+
import filtersPlugin from "@genart-dev/plugin-filters";
|
|
7
|
+
import shapesPlugin from "@genart-dev/plugin-shapes";
|
|
8
|
+
import layoutGuidesPlugin from "@genart-dev/plugin-layout-guides";
|
|
4
9
|
|
|
5
10
|
// src/tools/workspace.ts
|
|
6
11
|
import { readFile, writeFile, stat } from "fs/promises";
|
|
@@ -345,6 +350,7 @@ import { writeFile as writeFile2, stat as stat2, unlink } from "fs/promises";
|
|
|
345
350
|
import { basename as basename2, dirname as dirname2, resolve } from "path";
|
|
346
351
|
import {
|
|
347
352
|
createDefaultRegistry,
|
|
353
|
+
resolveComponents,
|
|
348
354
|
resolvePreset,
|
|
349
355
|
serializeGenart,
|
|
350
356
|
serializeWorkspace as serializeWorkspace2
|
|
@@ -441,10 +447,40 @@ async function createSketch(state, input) {
|
|
|
441
447
|
const adapter = registry4.resolve(rendererType);
|
|
442
448
|
algorithm = adapter.getAlgorithmTemplate();
|
|
443
449
|
}
|
|
450
|
+
let resolvedComponents;
|
|
451
|
+
if (input.components && Object.keys(input.components).length > 0) {
|
|
452
|
+
const shorthand = {};
|
|
453
|
+
for (const [name, value] of Object.entries(input.components)) {
|
|
454
|
+
if (typeof value === "string") {
|
|
455
|
+
shorthand[name] = value;
|
|
456
|
+
} else if (value.version) {
|
|
457
|
+
shorthand[name] = value.version;
|
|
458
|
+
} else if (value.code) {
|
|
459
|
+
if (!resolvedComponents) resolvedComponents = {};
|
|
460
|
+
resolvedComponents[name] = {
|
|
461
|
+
...value.version ? { version: value.version } : {},
|
|
462
|
+
code: value.code,
|
|
463
|
+
...value.exports ? { exports: value.exports } : {}
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
if (Object.keys(shorthand).length > 0) {
|
|
468
|
+
const resolved = resolveComponents(shorthand, rendererType);
|
|
469
|
+
if (!resolvedComponents) resolvedComponents = {};
|
|
470
|
+
for (const rc of resolved) {
|
|
471
|
+
resolvedComponents[rc.name] = {
|
|
472
|
+
version: rc.version,
|
|
473
|
+
code: rc.code,
|
|
474
|
+
exports: [...rc.exports]
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
444
479
|
const seed = input.seed ?? Math.floor(Math.random() * 1e5);
|
|
445
480
|
const ts = now2();
|
|
481
|
+
const hasComponents = resolvedComponents && Object.keys(resolvedComponents).length > 0;
|
|
446
482
|
const sketch = {
|
|
447
|
-
genart: "1.1",
|
|
483
|
+
genart: hasComponents ? "1.2" : "1.1",
|
|
448
484
|
id: input.id,
|
|
449
485
|
title: input.title,
|
|
450
486
|
created: ts,
|
|
@@ -458,6 +494,7 @@ async function createSketch(state, input) {
|
|
|
458
494
|
...input.philosophy ? { philosophy: input.philosophy } : {},
|
|
459
495
|
...input.themes && input.themes.length > 0 ? { themes: input.themes } : {},
|
|
460
496
|
...input.skills && input.skills.length > 0 ? { skills: input.skills } : {},
|
|
497
|
+
...hasComponents ? { components: resolvedComponents } : {},
|
|
461
498
|
...input.agent ? { agent: input.agent } : {},
|
|
462
499
|
...input.model ? { model: input.model } : {}
|
|
463
500
|
};
|
|
@@ -641,10 +678,44 @@ async function updateAlgorithm(state, input) {
|
|
|
641
678
|
);
|
|
642
679
|
}
|
|
643
680
|
}
|
|
681
|
+
let resolvedComponents;
|
|
682
|
+
if (input.components && Object.keys(input.components).length > 0) {
|
|
683
|
+
const renderer = def.renderer.type;
|
|
684
|
+
const shorthand = {};
|
|
685
|
+
for (const [name, value] of Object.entries(input.components)) {
|
|
686
|
+
if (typeof value === "string") {
|
|
687
|
+
shorthand[name] = value;
|
|
688
|
+
} else if (value.version) {
|
|
689
|
+
shorthand[name] = value.version;
|
|
690
|
+
} else if (value.code) {
|
|
691
|
+
if (!resolvedComponents) resolvedComponents = {};
|
|
692
|
+
resolvedComponents[name] = {
|
|
693
|
+
...value.version ? { version: value.version } : {},
|
|
694
|
+
code: value.code,
|
|
695
|
+
...value.exports ? { exports: value.exports } : {}
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
if (Object.keys(shorthand).length > 0) {
|
|
700
|
+
const resolved = resolveComponents(shorthand, renderer);
|
|
701
|
+
if (!resolvedComponents) resolvedComponents = {};
|
|
702
|
+
for (const rc of resolved) {
|
|
703
|
+
resolvedComponents[rc.name] = {
|
|
704
|
+
version: rc.version,
|
|
705
|
+
code: rc.code,
|
|
706
|
+
exports: [...rc.exports]
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
const updated = ["algorithm"];
|
|
712
|
+
const hasNewComponents = resolvedComponents && Object.keys(resolvedComponents).length > 0;
|
|
713
|
+
if (hasNewComponents) updated.push("components");
|
|
644
714
|
const newDef = {
|
|
645
715
|
...def,
|
|
646
716
|
modified: now2(),
|
|
647
717
|
algorithm: input.algorithm,
|
|
718
|
+
...hasNewComponents ? { genart: "1.2", components: resolvedComponents } : {},
|
|
648
719
|
...input.agent ? { agent: input.agent } : {},
|
|
649
720
|
...input.model ? { model: input.model } : {}
|
|
650
721
|
};
|
|
@@ -658,7 +729,7 @@ async function updateAlgorithm(state, input) {
|
|
|
658
729
|
}
|
|
659
730
|
state.emitMutation("sketch:updated", {
|
|
660
731
|
id: input.sketchId,
|
|
661
|
-
updated
|
|
732
|
+
updated
|
|
662
733
|
});
|
|
663
734
|
return {
|
|
664
735
|
success: true,
|
|
@@ -666,6 +737,7 @@ async function updateAlgorithm(state, input) {
|
|
|
666
737
|
renderer: def.renderer.type,
|
|
667
738
|
algorithmLength: input.algorithm.length,
|
|
668
739
|
validationPassed,
|
|
740
|
+
...hasNewComponents ? { componentsUpdated: true } : {},
|
|
669
741
|
fileContent: json
|
|
670
742
|
};
|
|
671
743
|
}
|
|
@@ -1967,8 +2039,219 @@ ${guidelines}`,
|
|
|
1967
2039
|
};
|
|
1968
2040
|
}
|
|
1969
2041
|
|
|
1970
|
-
// src/tools/
|
|
2042
|
+
// src/tools/components.ts
|
|
1971
2043
|
import { writeFile as writeFile4 } from "fs/promises";
|
|
2044
|
+
import {
|
|
2045
|
+
COMPONENT_REGISTRY,
|
|
2046
|
+
resolveComponents as resolveComponents2,
|
|
2047
|
+
serializeGenart as serializeGenart3
|
|
2048
|
+
} from "@genart-dev/core";
|
|
2049
|
+
var VALID_RENDERERS2 = [
|
|
2050
|
+
"p5",
|
|
2051
|
+
"three",
|
|
2052
|
+
"glsl",
|
|
2053
|
+
"canvas2d",
|
|
2054
|
+
"svg"
|
|
2055
|
+
];
|
|
2056
|
+
var RENDERER_TARGET = {
|
|
2057
|
+
p5: "js",
|
|
2058
|
+
three: "js",
|
|
2059
|
+
canvas2d: "js",
|
|
2060
|
+
svg: "js",
|
|
2061
|
+
glsl: "glsl"
|
|
2062
|
+
};
|
|
2063
|
+
async function listComponents(_state, input) {
|
|
2064
|
+
let entries = Object.values(COMPONENT_REGISTRY);
|
|
2065
|
+
if (input.renderer) {
|
|
2066
|
+
const renderer = input.renderer;
|
|
2067
|
+
const target = RENDERER_TARGET[renderer];
|
|
2068
|
+
if (!target) {
|
|
2069
|
+
throw new Error(
|
|
2070
|
+
`Unknown renderer type: '${input.renderer}'. Valid types: ${VALID_RENDERERS2.join(", ")}`
|
|
2071
|
+
);
|
|
2072
|
+
}
|
|
2073
|
+
entries = entries.filter(
|
|
2074
|
+
(e) => e.target === target && (e.renderers.length === 0 || e.renderers.includes(renderer))
|
|
2075
|
+
);
|
|
2076
|
+
}
|
|
2077
|
+
if (input.category) {
|
|
2078
|
+
const cat = input.category;
|
|
2079
|
+
entries = entries.filter((e) => e.category === cat);
|
|
2080
|
+
}
|
|
2081
|
+
entries.sort((a, b) => {
|
|
2082
|
+
const catCmp = a.category.localeCompare(b.category);
|
|
2083
|
+
if (catCmp !== 0) return catCmp;
|
|
2084
|
+
return a.name.localeCompare(b.name);
|
|
2085
|
+
});
|
|
2086
|
+
const components = entries.map((e) => ({
|
|
2087
|
+
name: e.name,
|
|
2088
|
+
version: e.version,
|
|
2089
|
+
category: e.category,
|
|
2090
|
+
target: e.target,
|
|
2091
|
+
exports: [...e.exports],
|
|
2092
|
+
dependencies: [...e.dependencies],
|
|
2093
|
+
description: e.description
|
|
2094
|
+
}));
|
|
2095
|
+
return {
|
|
2096
|
+
count: components.length,
|
|
2097
|
+
components
|
|
2098
|
+
};
|
|
2099
|
+
}
|
|
2100
|
+
async function addComponent(state, input) {
|
|
2101
|
+
const loaded = state.requireSketch(input.sketchId);
|
|
2102
|
+
const def = loaded.definition;
|
|
2103
|
+
const renderer = def.renderer.type;
|
|
2104
|
+
const entry = COMPONENT_REGISTRY[input.component];
|
|
2105
|
+
if (!entry) {
|
|
2106
|
+
throw new Error(`Unknown component: "${input.component}"`);
|
|
2107
|
+
}
|
|
2108
|
+
const target = RENDERER_TARGET[renderer];
|
|
2109
|
+
if (entry.target !== target) {
|
|
2110
|
+
throw new Error(
|
|
2111
|
+
`Component "${input.component}" has target "${entry.target}" but renderer "${renderer}" requires target "${target}"`
|
|
2112
|
+
);
|
|
2113
|
+
}
|
|
2114
|
+
if (entry.renderers.length > 0 && !entry.renderers.includes(renderer)) {
|
|
2115
|
+
throw new Error(
|
|
2116
|
+
`Component "${input.component}" is not compatible with renderer "${renderer}". Compatible: ${entry.renderers.join(", ")}`
|
|
2117
|
+
);
|
|
2118
|
+
}
|
|
2119
|
+
const existingComponents = {};
|
|
2120
|
+
if (def.components) {
|
|
2121
|
+
for (const [name, value] of Object.entries(def.components)) {
|
|
2122
|
+
if (typeof value === "string") {
|
|
2123
|
+
existingComponents[name] = value;
|
|
2124
|
+
} else if (value.version) {
|
|
2125
|
+
existingComponents[name] = value.version;
|
|
2126
|
+
}
|
|
2127
|
+
}
|
|
2128
|
+
}
|
|
2129
|
+
if (existingComponents[input.component]) {
|
|
2130
|
+
throw new Error(
|
|
2131
|
+
`Component "${input.component}" is already present in sketch "${input.sketchId}"`
|
|
2132
|
+
);
|
|
2133
|
+
}
|
|
2134
|
+
existingComponents[input.component] = input.version ?? "^1.0.0";
|
|
2135
|
+
const resolved = resolveComponents2(existingComponents, renderer);
|
|
2136
|
+
const resolvedRecord = {};
|
|
2137
|
+
for (const rc of resolved) {
|
|
2138
|
+
resolvedRecord[rc.name] = {
|
|
2139
|
+
version: rc.version,
|
|
2140
|
+
code: rc.code,
|
|
2141
|
+
exports: [...rc.exports]
|
|
2142
|
+
};
|
|
2143
|
+
}
|
|
2144
|
+
const previousNames = new Set(
|
|
2145
|
+
def.components ? Object.keys(def.components) : []
|
|
2146
|
+
);
|
|
2147
|
+
const added = resolved.map((rc) => rc.name).filter((name) => !previousNames.has(name));
|
|
2148
|
+
const newDef = {
|
|
2149
|
+
...def,
|
|
2150
|
+
genart: "1.2",
|
|
2151
|
+
modified: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2152
|
+
components: resolvedRecord
|
|
2153
|
+
};
|
|
2154
|
+
state.sketches.set(input.sketchId, {
|
|
2155
|
+
definition: newDef,
|
|
2156
|
+
path: loaded.path
|
|
2157
|
+
});
|
|
2158
|
+
const json = serializeGenart3(newDef);
|
|
2159
|
+
if (!state.remoteMode) {
|
|
2160
|
+
await writeFile4(loaded.path, json, "utf-8");
|
|
2161
|
+
}
|
|
2162
|
+
state.emitMutation("sketch:updated", {
|
|
2163
|
+
id: input.sketchId,
|
|
2164
|
+
updated: ["components"]
|
|
2165
|
+
});
|
|
2166
|
+
return {
|
|
2167
|
+
success: true,
|
|
2168
|
+
sketchId: input.sketchId,
|
|
2169
|
+
components: resolvedRecord,
|
|
2170
|
+
added,
|
|
2171
|
+
fileContent: json
|
|
2172
|
+
};
|
|
2173
|
+
}
|
|
2174
|
+
async function removeComponent(state, input) {
|
|
2175
|
+
const loaded = state.requireSketch(input.sketchId);
|
|
2176
|
+
const def = loaded.definition;
|
|
2177
|
+
if (!def.components || !def.components[input.component]) {
|
|
2178
|
+
throw new Error(
|
|
2179
|
+
`Component "${input.component}" is not present in sketch "${input.sketchId}"`
|
|
2180
|
+
);
|
|
2181
|
+
}
|
|
2182
|
+
const remaining = { ...def.components };
|
|
2183
|
+
delete remaining[input.component];
|
|
2184
|
+
for (const [name, value] of Object.entries(remaining)) {
|
|
2185
|
+
const entry = COMPONENT_REGISTRY[name];
|
|
2186
|
+
if (entry && entry.dependencies.includes(input.component)) {
|
|
2187
|
+
throw new Error(
|
|
2188
|
+
`Cannot remove "${input.component}": component "${name}" depends on it`
|
|
2189
|
+
);
|
|
2190
|
+
}
|
|
2191
|
+
}
|
|
2192
|
+
let warning;
|
|
2193
|
+
const removedValue = def.components[input.component];
|
|
2194
|
+
const exports = typeof removedValue === "string" ? COMPONENT_REGISTRY[input.component]?.exports ?? [] : removedValue.exports ?? [];
|
|
2195
|
+
const usedExports = exports.filter((exp) => def.algorithm.includes(exp));
|
|
2196
|
+
if (usedExports.length > 0) {
|
|
2197
|
+
warning = `Algorithm may reference these exports from "${input.component}": ${usedExports.join(", ")}. Review your algorithm after removal.`;
|
|
2198
|
+
}
|
|
2199
|
+
const removed = [input.component];
|
|
2200
|
+
const neededDeps = /* @__PURE__ */ new Set();
|
|
2201
|
+
for (const name of Object.keys(remaining)) {
|
|
2202
|
+
const entry = COMPONENT_REGISTRY[name];
|
|
2203
|
+
if (entry) {
|
|
2204
|
+
collectTransitiveDeps(name, neededDeps);
|
|
2205
|
+
}
|
|
2206
|
+
}
|
|
2207
|
+
for (const name of Object.keys(remaining)) {
|
|
2208
|
+
if (!neededDeps.has(name) && !isDirectComponent(name, remaining)) {
|
|
2209
|
+
delete remaining[name];
|
|
2210
|
+
removed.push(name);
|
|
2211
|
+
}
|
|
2212
|
+
}
|
|
2213
|
+
const hasRemaining = Object.keys(remaining).length > 0;
|
|
2214
|
+
const newDef = {
|
|
2215
|
+
...def,
|
|
2216
|
+
modified: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2217
|
+
...hasRemaining ? { components: remaining } : { components: void 0 }
|
|
2218
|
+
};
|
|
2219
|
+
state.sketches.set(input.sketchId, {
|
|
2220
|
+
definition: newDef,
|
|
2221
|
+
path: loaded.path
|
|
2222
|
+
});
|
|
2223
|
+
const json = serializeGenart3(newDef);
|
|
2224
|
+
if (!state.remoteMode) {
|
|
2225
|
+
await writeFile4(loaded.path, json, "utf-8");
|
|
2226
|
+
}
|
|
2227
|
+
state.emitMutation("sketch:updated", {
|
|
2228
|
+
id: input.sketchId,
|
|
2229
|
+
updated: ["components"]
|
|
2230
|
+
});
|
|
2231
|
+
return {
|
|
2232
|
+
success: true,
|
|
2233
|
+
sketchId: input.sketchId,
|
|
2234
|
+
removed,
|
|
2235
|
+
...warning ? { warning } : {},
|
|
2236
|
+
fileContent: json
|
|
2237
|
+
};
|
|
2238
|
+
}
|
|
2239
|
+
function collectTransitiveDeps(name, deps) {
|
|
2240
|
+
const entry = COMPONENT_REGISTRY[name];
|
|
2241
|
+
if (!entry) return;
|
|
2242
|
+
deps.add(name);
|
|
2243
|
+
for (const dep of entry.dependencies) {
|
|
2244
|
+
if (!deps.has(dep)) {
|
|
2245
|
+
collectTransitiveDeps(dep, deps);
|
|
2246
|
+
}
|
|
2247
|
+
}
|
|
2248
|
+
}
|
|
2249
|
+
function isDirectComponent(name, components) {
|
|
2250
|
+
return name in components;
|
|
2251
|
+
}
|
|
2252
|
+
|
|
2253
|
+
// src/tools/capture.ts
|
|
2254
|
+
import { writeFile as writeFile5 } from "fs/promises";
|
|
1972
2255
|
import {
|
|
1973
2256
|
createDefaultRegistry as createDefaultRegistry2
|
|
1974
2257
|
} from "@genart-dev/core";
|
|
@@ -2146,7 +2429,7 @@ async function buildScreenshotMetadata(state, multi, info) {
|
|
|
2146
2429
|
previewPath: info.previewPath
|
|
2147
2430
|
};
|
|
2148
2431
|
if (!state.remoteMode) {
|
|
2149
|
-
await
|
|
2432
|
+
await writeFile5(info.previewPath, multi.previewPng);
|
|
2150
2433
|
metadata.savedPreviewTo = info.previewPath;
|
|
2151
2434
|
}
|
|
2152
2435
|
return metadata;
|
|
@@ -2207,12 +2490,12 @@ async function captureBatch(state, input) {
|
|
|
2207
2490
|
|
|
2208
2491
|
// src/tools/export.ts
|
|
2209
2492
|
import { createWriteStream } from "fs";
|
|
2210
|
-
import { stat as stat4, writeFile as
|
|
2493
|
+
import { stat as stat4, writeFile as writeFile6 } from "fs/promises";
|
|
2211
2494
|
import { dirname as dirname5 } from "path";
|
|
2212
2495
|
import archiver from "archiver";
|
|
2213
2496
|
import {
|
|
2214
2497
|
createDefaultRegistry as createDefaultRegistry3,
|
|
2215
|
-
serializeGenart as
|
|
2498
|
+
serializeGenart as serializeGenart4
|
|
2216
2499
|
} from "@genart-dev/core";
|
|
2217
2500
|
var registry3 = createDefaultRegistry3();
|
|
2218
2501
|
async function validateOutputPath(outputPath) {
|
|
@@ -2282,7 +2565,7 @@ async function exportHtml(sketch, outputPath) {
|
|
|
2282
2565
|
const adapter = registry3.resolve(sketch.renderer.type);
|
|
2283
2566
|
const html = adapter.generateStandaloneHTML(sketch);
|
|
2284
2567
|
const content = Buffer.from(html, "utf-8");
|
|
2285
|
-
await
|
|
2568
|
+
await writeFile6(outputPath, content);
|
|
2286
2569
|
return {
|
|
2287
2570
|
success: true,
|
|
2288
2571
|
sketchId: sketch.id,
|
|
@@ -2298,7 +2581,7 @@ async function exportPng(sketch, input) {
|
|
|
2298
2581
|
const width = input.width ?? sketch.canvas.width;
|
|
2299
2582
|
const height = input.height ?? sketch.canvas.height;
|
|
2300
2583
|
const result = await captureHtml({ html, width, height });
|
|
2301
|
-
await
|
|
2584
|
+
await writeFile6(input.outputPath, result.bytes);
|
|
2302
2585
|
return {
|
|
2303
2586
|
success: true,
|
|
2304
2587
|
sketchId: sketch.id,
|
|
@@ -2313,7 +2596,7 @@ async function exportSvg(sketch, input) {
|
|
|
2313
2596
|
const height = input.height ?? sketch.canvas.height;
|
|
2314
2597
|
if (sketch.renderer.type === "svg") {
|
|
2315
2598
|
const content2 = Buffer.from(sketch.algorithm, "utf-8");
|
|
2316
|
-
await
|
|
2599
|
+
await writeFile6(input.outputPath, content2);
|
|
2317
2600
|
return {
|
|
2318
2601
|
success: true,
|
|
2319
2602
|
sketchId: sketch.id,
|
|
@@ -2335,7 +2618,7 @@ async function exportSvg(sketch, input) {
|
|
|
2335
2618
|
href="data:image/png;base64,${b64}"/>
|
|
2336
2619
|
</svg>`;
|
|
2337
2620
|
const content = Buffer.from(svg, "utf-8");
|
|
2338
|
-
await
|
|
2621
|
+
await writeFile6(input.outputPath, content);
|
|
2339
2622
|
return {
|
|
2340
2623
|
success: true,
|
|
2341
2624
|
sketchId: sketch.id,
|
|
@@ -2348,7 +2631,7 @@ async function exportSvg(sketch, input) {
|
|
|
2348
2631
|
}
|
|
2349
2632
|
async function exportAlgorithm(sketch, outputPath) {
|
|
2350
2633
|
const content = Buffer.from(sketch.algorithm, "utf-8");
|
|
2351
|
-
await
|
|
2634
|
+
await writeFile6(outputPath, content);
|
|
2352
2635
|
return {
|
|
2353
2636
|
success: true,
|
|
2354
2637
|
sketchId: sketch.id,
|
|
@@ -2363,7 +2646,7 @@ async function exportZip(sketch, input) {
|
|
|
2363
2646
|
const width = input.width ?? sketch.canvas.width;
|
|
2364
2647
|
const height = input.height ?? sketch.canvas.height;
|
|
2365
2648
|
const html = adapter.generateStandaloneHTML(sketch);
|
|
2366
|
-
const genartJson =
|
|
2649
|
+
const genartJson = serializeGenart4(sketch);
|
|
2367
2650
|
const algorithm = sketch.algorithm;
|
|
2368
2651
|
const algExt = algorithmExtension(sketch.renderer.type);
|
|
2369
2652
|
const captureResult = await captureHtml({ html, width, height });
|
|
@@ -2397,6 +2680,330 @@ async function exportZip(sketch, input) {
|
|
|
2397
2680
|
};
|
|
2398
2681
|
}
|
|
2399
2682
|
|
|
2683
|
+
// src/tools/design.ts
|
|
2684
|
+
function requireSketchId(state, args) {
|
|
2685
|
+
return args.sketchId ?? state.requireSelectedSketchId();
|
|
2686
|
+
}
|
|
2687
|
+
var BLEND_MODES = [
|
|
2688
|
+
"normal",
|
|
2689
|
+
"multiply",
|
|
2690
|
+
"screen",
|
|
2691
|
+
"overlay",
|
|
2692
|
+
"darken",
|
|
2693
|
+
"lighten",
|
|
2694
|
+
"color-dodge",
|
|
2695
|
+
"color-burn",
|
|
2696
|
+
"hard-light",
|
|
2697
|
+
"soft-light",
|
|
2698
|
+
"difference",
|
|
2699
|
+
"exclusion",
|
|
2700
|
+
"hue",
|
|
2701
|
+
"saturation",
|
|
2702
|
+
"color",
|
|
2703
|
+
"luminosity"
|
|
2704
|
+
];
|
|
2705
|
+
function generateLayerId() {
|
|
2706
|
+
return `layer-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
2707
|
+
}
|
|
2708
|
+
async function designAddLayer(state, args) {
|
|
2709
|
+
const sketchId = requireSketchId(state, args);
|
|
2710
|
+
const loaded = state.requireSketch(sketchId);
|
|
2711
|
+
const stack = state.getLayerStack(sketchId);
|
|
2712
|
+
const registry4 = state.pluginRegistry;
|
|
2713
|
+
const layerTypeDef = registry4?.resolveLayerType(args.type);
|
|
2714
|
+
if (!layerTypeDef) {
|
|
2715
|
+
throw new Error(
|
|
2716
|
+
`Unknown layer type: '${args.type}'. Use design_list_layers types from registered plugins.`
|
|
2717
|
+
);
|
|
2718
|
+
}
|
|
2719
|
+
const defaults = layerTypeDef.createDefault();
|
|
2720
|
+
const id = generateLayerId();
|
|
2721
|
+
const { width, height } = loaded.definition.canvas;
|
|
2722
|
+
const layer = {
|
|
2723
|
+
id,
|
|
2724
|
+
type: args.type,
|
|
2725
|
+
name: args.name ?? layerTypeDef.displayName,
|
|
2726
|
+
visible: true,
|
|
2727
|
+
locked: false,
|
|
2728
|
+
opacity: args.opacity ?? 1,
|
|
2729
|
+
blendMode: args.blendMode ?? "normal",
|
|
2730
|
+
transform: {
|
|
2731
|
+
x: 0,
|
|
2732
|
+
y: 0,
|
|
2733
|
+
width,
|
|
2734
|
+
height,
|
|
2735
|
+
rotation: 0,
|
|
2736
|
+
scaleX: 1,
|
|
2737
|
+
scaleY: 1,
|
|
2738
|
+
anchorX: 0.5,
|
|
2739
|
+
anchorY: 0.5,
|
|
2740
|
+
...args.transform
|
|
2741
|
+
},
|
|
2742
|
+
properties: { ...defaults, ...args.properties }
|
|
2743
|
+
};
|
|
2744
|
+
stack.add(layer, args.index);
|
|
2745
|
+
await state.saveSketch(sketchId);
|
|
2746
|
+
return {
|
|
2747
|
+
layerId: id,
|
|
2748
|
+
type: args.type,
|
|
2749
|
+
name: layer.name,
|
|
2750
|
+
index: args.index ?? stack.count - 1,
|
|
2751
|
+
sketchId
|
|
2752
|
+
};
|
|
2753
|
+
}
|
|
2754
|
+
async function designRemoveLayer(state, args) {
|
|
2755
|
+
const sketchId = requireSketchId(state, args);
|
|
2756
|
+
const stack = state.getLayerStack(sketchId);
|
|
2757
|
+
const removed = stack.remove(args.layerId);
|
|
2758
|
+
if (!removed) {
|
|
2759
|
+
throw new Error(`Layer '${args.layerId}' not found in sketch '${sketchId}'.`);
|
|
2760
|
+
}
|
|
2761
|
+
await state.saveSketch(sketchId);
|
|
2762
|
+
return { removed: true, layerId: args.layerId, sketchId };
|
|
2763
|
+
}
|
|
2764
|
+
async function designListLayers(state, args) {
|
|
2765
|
+
const sketchId = requireSketchId(state, args);
|
|
2766
|
+
const stack = state.getLayerStack(sketchId);
|
|
2767
|
+
const layers = stack.getAll();
|
|
2768
|
+
return {
|
|
2769
|
+
sketchId,
|
|
2770
|
+
count: layers.length,
|
|
2771
|
+
layers: layers.map((l, i) => ({
|
|
2772
|
+
index: i,
|
|
2773
|
+
id: l.id,
|
|
2774
|
+
type: l.type,
|
|
2775
|
+
name: l.name,
|
|
2776
|
+
visible: l.visible,
|
|
2777
|
+
locked: l.locked,
|
|
2778
|
+
opacity: l.opacity,
|
|
2779
|
+
blendMode: l.blendMode
|
|
2780
|
+
}))
|
|
2781
|
+
};
|
|
2782
|
+
}
|
|
2783
|
+
async function designGetLayer(state, args) {
|
|
2784
|
+
const sketchId = requireSketchId(state, args);
|
|
2785
|
+
const stack = state.getLayerStack(sketchId);
|
|
2786
|
+
const layer = stack.get(args.layerId);
|
|
2787
|
+
if (!layer) {
|
|
2788
|
+
throw new Error(`Layer '${args.layerId}' not found in sketch '${sketchId}'.`);
|
|
2789
|
+
}
|
|
2790
|
+
return {
|
|
2791
|
+
sketchId,
|
|
2792
|
+
layer: {
|
|
2793
|
+
id: layer.id,
|
|
2794
|
+
type: layer.type,
|
|
2795
|
+
name: layer.name,
|
|
2796
|
+
visible: layer.visible,
|
|
2797
|
+
locked: layer.locked,
|
|
2798
|
+
opacity: layer.opacity,
|
|
2799
|
+
blendMode: layer.blendMode,
|
|
2800
|
+
transform: layer.transform,
|
|
2801
|
+
properties: layer.properties
|
|
2802
|
+
}
|
|
2803
|
+
};
|
|
2804
|
+
}
|
|
2805
|
+
async function designUpdateLayer(state, args) {
|
|
2806
|
+
const sketchId = requireSketchId(state, args);
|
|
2807
|
+
const stack = state.getLayerStack(sketchId);
|
|
2808
|
+
const layer = stack.get(args.layerId);
|
|
2809
|
+
if (!layer) {
|
|
2810
|
+
throw new Error(`Layer '${args.layerId}' not found in sketch '${sketchId}'.`);
|
|
2811
|
+
}
|
|
2812
|
+
const updates = {};
|
|
2813
|
+
if (args.properties) {
|
|
2814
|
+
Object.assign(updates, args.properties);
|
|
2815
|
+
}
|
|
2816
|
+
if (Object.keys(updates).length > 0) {
|
|
2817
|
+
stack.updateProperties(args.layerId, updates);
|
|
2818
|
+
}
|
|
2819
|
+
if (args.name !== void 0) {
|
|
2820
|
+
const current = stack.get(args.layerId);
|
|
2821
|
+
stack.updateProperties(args.layerId, { ...current.properties });
|
|
2822
|
+
const mutableLayer = stack.get(args.layerId);
|
|
2823
|
+
mutableLayer.name = args.name;
|
|
2824
|
+
}
|
|
2825
|
+
await state.saveSketch(sketchId);
|
|
2826
|
+
return { updated: true, layerId: args.layerId, sketchId };
|
|
2827
|
+
}
|
|
2828
|
+
async function designSetTransform(state, args) {
|
|
2829
|
+
const sketchId = requireSketchId(state, args);
|
|
2830
|
+
const stack = state.getLayerStack(sketchId);
|
|
2831
|
+
const layer = stack.get(args.layerId);
|
|
2832
|
+
if (!layer) {
|
|
2833
|
+
throw new Error(`Layer '${args.layerId}' not found in sketch '${sketchId}'.`);
|
|
2834
|
+
}
|
|
2835
|
+
const partial = {};
|
|
2836
|
+
if (args.x !== void 0) partial.x = args.x;
|
|
2837
|
+
if (args.y !== void 0) partial.y = args.y;
|
|
2838
|
+
if (args.width !== void 0) partial.width = args.width;
|
|
2839
|
+
if (args.height !== void 0) partial.height = args.height;
|
|
2840
|
+
if (args.rotation !== void 0) partial.rotation = args.rotation;
|
|
2841
|
+
if (args.scaleX !== void 0) partial.scaleX = args.scaleX;
|
|
2842
|
+
if (args.scaleY !== void 0) partial.scaleY = args.scaleY;
|
|
2843
|
+
if (args.anchorX !== void 0) partial.anchorX = args.anchorX;
|
|
2844
|
+
if (args.anchorY !== void 0) partial.anchorY = args.anchorY;
|
|
2845
|
+
stack.updateTransform(args.layerId, partial);
|
|
2846
|
+
await state.saveSketch(sketchId);
|
|
2847
|
+
return {
|
|
2848
|
+
updated: true,
|
|
2849
|
+
layerId: args.layerId,
|
|
2850
|
+
transform: stack.get(args.layerId).transform,
|
|
2851
|
+
sketchId
|
|
2852
|
+
};
|
|
2853
|
+
}
|
|
2854
|
+
async function designSetBlend(state, args) {
|
|
2855
|
+
const sketchId = requireSketchId(state, args);
|
|
2856
|
+
const stack = state.getLayerStack(sketchId);
|
|
2857
|
+
const layer = stack.get(args.layerId);
|
|
2858
|
+
if (!layer) {
|
|
2859
|
+
throw new Error(`Layer '${args.layerId}' not found in sketch '${sketchId}'.`);
|
|
2860
|
+
}
|
|
2861
|
+
if (args.blendMode && !BLEND_MODES.includes(args.blendMode)) {
|
|
2862
|
+
throw new Error(
|
|
2863
|
+
`Invalid blend mode '${args.blendMode}'. Must be one of: ${BLEND_MODES.join(", ")}`
|
|
2864
|
+
);
|
|
2865
|
+
}
|
|
2866
|
+
stack.updateBlend(
|
|
2867
|
+
args.layerId,
|
|
2868
|
+
args.blendMode,
|
|
2869
|
+
args.opacity
|
|
2870
|
+
);
|
|
2871
|
+
await state.saveSketch(sketchId);
|
|
2872
|
+
const updated = stack.get(args.layerId);
|
|
2873
|
+
return {
|
|
2874
|
+
updated: true,
|
|
2875
|
+
layerId: args.layerId,
|
|
2876
|
+
blendMode: updated.blendMode,
|
|
2877
|
+
opacity: updated.opacity,
|
|
2878
|
+
sketchId
|
|
2879
|
+
};
|
|
2880
|
+
}
|
|
2881
|
+
async function designReorderLayers(state, args) {
|
|
2882
|
+
const sketchId = requireSketchId(state, args);
|
|
2883
|
+
const stack = state.getLayerStack(sketchId);
|
|
2884
|
+
const layer = stack.get(args.layerId);
|
|
2885
|
+
if (!layer) {
|
|
2886
|
+
throw new Error(`Layer '${args.layerId}' not found in sketch '${sketchId}'.`);
|
|
2887
|
+
}
|
|
2888
|
+
stack.reorder(args.layerId, args.newIndex);
|
|
2889
|
+
await state.saveSketch(sketchId);
|
|
2890
|
+
return {
|
|
2891
|
+
reordered: true,
|
|
2892
|
+
layerId: args.layerId,
|
|
2893
|
+
newIndex: args.newIndex,
|
|
2894
|
+
sketchId
|
|
2895
|
+
};
|
|
2896
|
+
}
|
|
2897
|
+
async function designDuplicateLayer(state, args) {
|
|
2898
|
+
const sketchId = requireSketchId(state, args);
|
|
2899
|
+
const stack = state.getLayerStack(sketchId);
|
|
2900
|
+
const layer = stack.get(args.layerId);
|
|
2901
|
+
if (!layer) {
|
|
2902
|
+
throw new Error(`Layer '${args.layerId}' not found in sketch '${sketchId}'.`);
|
|
2903
|
+
}
|
|
2904
|
+
const newId = stack.duplicate(args.layerId);
|
|
2905
|
+
await state.saveSketch(sketchId);
|
|
2906
|
+
return {
|
|
2907
|
+
duplicated: true,
|
|
2908
|
+
sourceLayerId: args.layerId,
|
|
2909
|
+
newLayerId: newId,
|
|
2910
|
+
sketchId
|
|
2911
|
+
};
|
|
2912
|
+
}
|
|
2913
|
+
async function designToggleVisibility(state, args) {
|
|
2914
|
+
const sketchId = requireSketchId(state, args);
|
|
2915
|
+
const stack = state.getLayerStack(sketchId);
|
|
2916
|
+
const layer = stack.get(args.layerId);
|
|
2917
|
+
if (!layer) {
|
|
2918
|
+
throw new Error(`Layer '${args.layerId}' not found in sketch '${sketchId}'.`);
|
|
2919
|
+
}
|
|
2920
|
+
const newVisible = args.visible ?? !layer.visible;
|
|
2921
|
+
const mutableLayer = layer;
|
|
2922
|
+
mutableLayer.visible = newVisible;
|
|
2923
|
+
stack.updateProperties(args.layerId, { ...layer.properties });
|
|
2924
|
+
await state.saveSketch(sketchId);
|
|
2925
|
+
return {
|
|
2926
|
+
layerId: args.layerId,
|
|
2927
|
+
visible: newVisible,
|
|
2928
|
+
sketchId
|
|
2929
|
+
};
|
|
2930
|
+
}
|
|
2931
|
+
async function designLockLayer(state, args) {
|
|
2932
|
+
const sketchId = requireSketchId(state, args);
|
|
2933
|
+
const stack = state.getLayerStack(sketchId);
|
|
2934
|
+
const layer = stack.get(args.layerId);
|
|
2935
|
+
if (!layer) {
|
|
2936
|
+
throw new Error(`Layer '${args.layerId}' not found in sketch '${sketchId}'.`);
|
|
2937
|
+
}
|
|
2938
|
+
const newLocked = args.locked ?? !layer.locked;
|
|
2939
|
+
const mutableLayer = layer;
|
|
2940
|
+
mutableLayer.locked = newLocked;
|
|
2941
|
+
stack.updateProperties(args.layerId, { ...layer.properties });
|
|
2942
|
+
await state.saveSketch(sketchId);
|
|
2943
|
+
return {
|
|
2944
|
+
layerId: args.layerId,
|
|
2945
|
+
locked: newLocked,
|
|
2946
|
+
sketchId
|
|
2947
|
+
};
|
|
2948
|
+
}
|
|
2949
|
+
async function designCaptureComposite(state, args) {
|
|
2950
|
+
const sketchId = requireSketchId(state, args);
|
|
2951
|
+
const stack = state.getLayerStack(sketchId);
|
|
2952
|
+
const layers = stack.getAll();
|
|
2953
|
+
return {
|
|
2954
|
+
sketchId,
|
|
2955
|
+
layerCount: layers.length,
|
|
2956
|
+
visibleCount: layers.filter((l) => l.visible).length,
|
|
2957
|
+
message: "Composite capture requires a rendering surface. Use capture_screenshot to get a rasterized preview of the sketch, then use design_list_layers to see the design layer stack."
|
|
2958
|
+
};
|
|
2959
|
+
}
|
|
2960
|
+
|
|
2961
|
+
// src/tools/design-plugins.ts
|
|
2962
|
+
function registerPluginMcpTools(server, registry4, state) {
|
|
2963
|
+
for (const tool of registry4.getMcpTools()) {
|
|
2964
|
+
const inputSchema = tool.definition.inputSchema;
|
|
2965
|
+
server.tool(
|
|
2966
|
+
tool.name,
|
|
2967
|
+
tool.definition.description,
|
|
2968
|
+
// Pass raw JSON schema — MCP SDK accepts this alongside Zod
|
|
2969
|
+
inputSchema,
|
|
2970
|
+
async (args) => {
|
|
2971
|
+
try {
|
|
2972
|
+
const sketchId = args.sketchId ?? state.requireSelectedSketchId();
|
|
2973
|
+
const context = state.createMcpToolContext(sketchId);
|
|
2974
|
+
const result = await tool.definition.handler(args, context);
|
|
2975
|
+
await state.saveSketch(sketchId);
|
|
2976
|
+
return {
|
|
2977
|
+
content: result.content.map((c) => {
|
|
2978
|
+
if (c.type === "text") {
|
|
2979
|
+
return { type: "text", text: c.text };
|
|
2980
|
+
}
|
|
2981
|
+
return {
|
|
2982
|
+
type: "image",
|
|
2983
|
+
data: c.data,
|
|
2984
|
+
mimeType: c.mimeType
|
|
2985
|
+
};
|
|
2986
|
+
}),
|
|
2987
|
+
isError: result.isError
|
|
2988
|
+
};
|
|
2989
|
+
} catch (e) {
|
|
2990
|
+
return {
|
|
2991
|
+
content: [
|
|
2992
|
+
{
|
|
2993
|
+
type: "text",
|
|
2994
|
+
text: JSON.stringify({
|
|
2995
|
+
error: e instanceof Error ? e.message : String(e)
|
|
2996
|
+
})
|
|
2997
|
+
}
|
|
2998
|
+
],
|
|
2999
|
+
isError: true
|
|
3000
|
+
};
|
|
3001
|
+
}
|
|
3002
|
+
}
|
|
3003
|
+
);
|
|
3004
|
+
}
|
|
3005
|
+
}
|
|
3006
|
+
|
|
2400
3007
|
// src/resources/index.ts
|
|
2401
3008
|
import {
|
|
2402
3009
|
CANVAS_PRESETS,
|
|
@@ -2884,11 +3491,23 @@ function toolError(message) {
|
|
|
2884
3491
|
isError: true
|
|
2885
3492
|
};
|
|
2886
3493
|
}
|
|
3494
|
+
async function initializePluginRegistry() {
|
|
3495
|
+
const registry4 = createPluginRegistry({
|
|
3496
|
+
surface: "mcp",
|
|
3497
|
+
supportsInteractiveTools: false,
|
|
3498
|
+
supportsRendering: false
|
|
3499
|
+
});
|
|
3500
|
+
await registry4.register(typographyPlugin);
|
|
3501
|
+
await registry4.register(filtersPlugin);
|
|
3502
|
+
await registry4.register(shapesPlugin);
|
|
3503
|
+
await registry4.register(layoutGuidesPlugin);
|
|
3504
|
+
return registry4;
|
|
3505
|
+
}
|
|
2887
3506
|
function createServer(state) {
|
|
2888
3507
|
const server = new McpServer(
|
|
2889
3508
|
{
|
|
2890
3509
|
name: "@genart/mcp-server",
|
|
2891
|
-
version: "0.0
|
|
3510
|
+
version: "0.3.0"
|
|
2892
3511
|
},
|
|
2893
3512
|
{
|
|
2894
3513
|
capabilities: {
|
|
@@ -2898,8 +3517,14 @@ function createServer(state) {
|
|
|
2898
3517
|
}
|
|
2899
3518
|
}
|
|
2900
3519
|
);
|
|
3520
|
+
const registryReady = initializePluginRegistry().then((registry4) => {
|
|
3521
|
+
state.pluginRegistry = registry4;
|
|
3522
|
+
registerPluginMcpTools(server, registry4, state);
|
|
3523
|
+
});
|
|
3524
|
+
server._pluginsReady = registryReady;
|
|
2901
3525
|
registerWorkspaceTools(server, state);
|
|
2902
3526
|
registerSketchTools(server, state);
|
|
3527
|
+
registerComponentTools(server, state);
|
|
2903
3528
|
registerSelectionTools(server, state);
|
|
2904
3529
|
registerParameterTools(server, state);
|
|
2905
3530
|
registerArrangementTools(server, state);
|
|
@@ -2907,6 +3532,7 @@ function createServer(state) {
|
|
|
2907
3532
|
registerMergeTools(server, state);
|
|
2908
3533
|
registerSnapshotTools(server, state);
|
|
2909
3534
|
registerKnowledgeTools(server, state);
|
|
3535
|
+
registerDesignTools(server, state);
|
|
2910
3536
|
registerCaptureTools(server, state);
|
|
2911
3537
|
registerExportTools(server, state);
|
|
2912
3538
|
registerResources(server, state);
|
|
@@ -3003,7 +3629,7 @@ function registerWorkspaceTools(server, state) {
|
|
|
3003
3629
|
function registerSketchTools(server, state) {
|
|
3004
3630
|
server.tool(
|
|
3005
3631
|
"create_sketch",
|
|
3006
|
-
|
|
3632
|
+
'Create a new .genart sketch file from metadata, parameters, and algorithm. IMPORTANT: Do not embed common utilities (PRNG, noise, easing, color math, vector ops) inline in the algorithm. Instead, declare them as components: { "prng": "^1.0.0", "noise-2d": "^1.0.0" }. Then use the exported functions directly in your algorithm (e.g., mulberry32, fbm2D). Use list_components to see all available components for the current renderer.',
|
|
3007
3633
|
{
|
|
3008
3634
|
id: z2.string().describe("URL-safe kebab-case identifier"),
|
|
3009
3635
|
title: z2.string().describe("Human-readable title"),
|
|
@@ -3041,6 +3667,16 @@ function registerSketchTools(server, state) {
|
|
|
3041
3667
|
algorithm: z2.string().optional().describe("Algorithm source code (default: renderer template). For p5: must be `function sketch(p, state) { ... }` in instance mode. State provides: state.WIDTH, state.HEIGHT, state.SEED (number), state.PARAMS (keyed by param key), state.COLORS (keyed by color key, hex strings). Use p5 instance methods (p.createCanvas, p.background, etc)."),
|
|
3042
3668
|
seed: z2.number().optional().describe("Initial random seed (default: random)"),
|
|
3043
3669
|
skills: z2.array(z2.string()).optional().describe("Design skill references"),
|
|
3670
|
+
components: z2.record(
|
|
3671
|
+
z2.union([
|
|
3672
|
+
z2.string(),
|
|
3673
|
+
z2.object({
|
|
3674
|
+
version: z2.string().optional(),
|
|
3675
|
+
code: z2.string().optional(),
|
|
3676
|
+
exports: z2.array(z2.string()).optional()
|
|
3677
|
+
})
|
|
3678
|
+
])
|
|
3679
|
+
).optional().describe('Component dependencies. Use list_components to see available. Keys are component names, values are semver ranges (e.g. "^1.0.0") or objects with version/code/exports.'),
|
|
3044
3680
|
addToWorkspace: z2.string().optional().describe("Path to workspace to add sketch to after creation"),
|
|
3045
3681
|
agent: z2.string().optional().describe("Your CLI agent name (e.g. 'claude-code', 'codex-cli', 'gemini-cli', 'opencode', 'kiro')"),
|
|
3046
3682
|
model: z2.string().optional().describe("Your AI model identifier (e.g. 'claude-opus-4-6', 'gpt-4o', 'gemini-2.5-pro')")
|
|
@@ -3120,11 +3756,21 @@ function registerSketchTools(server, state) {
|
|
|
3120
3756
|
);
|
|
3121
3757
|
server.tool(
|
|
3122
3758
|
"update_algorithm",
|
|
3123
|
-
"Replace the algorithm source code of a sketch",
|
|
3759
|
+
"Replace the algorithm source code of a sketch. If adding/changing components, pass them in the components field alongside the algorithm.",
|
|
3124
3760
|
{
|
|
3125
3761
|
sketchId: z2.string().describe("ID of the sketch to update"),
|
|
3126
3762
|
algorithm: z2.string().describe("New algorithm source code. For p5: must be `function sketch(p, state) { ... }` in instance mode. State provides: state.WIDTH, state.HEIGHT, state.SEED, state.PARAMS (keyed by param key), state.COLORS (keyed by color key)."),
|
|
3127
3763
|
validate: z2.boolean().optional().describe("Run renderer-specific validation before saving (default: true)"),
|
|
3764
|
+
components: z2.record(
|
|
3765
|
+
z2.union([
|
|
3766
|
+
z2.string(),
|
|
3767
|
+
z2.object({
|
|
3768
|
+
version: z2.string().optional(),
|
|
3769
|
+
code: z2.string().optional(),
|
|
3770
|
+
exports: z2.array(z2.string()).optional()
|
|
3771
|
+
})
|
|
3772
|
+
])
|
|
3773
|
+
).optional().describe("Component dependencies to resolve alongside the algorithm update. Use list_components to see available."),
|
|
3128
3774
|
agent: z2.string().optional().describe("Your CLI agent name (e.g. 'claude-code', 'codex-cli', 'gemini-cli', 'opencode', 'kiro')"),
|
|
3129
3775
|
model: z2.string().optional().describe("Your AI model identifier (e.g. 'claude-opus-4-6', 'gpt-4o', 'gemini-2.5-pro')")
|
|
3130
3776
|
},
|
|
@@ -3220,6 +3866,76 @@ function registerSketchTools(server, state) {
|
|
|
3220
3866
|
}
|
|
3221
3867
|
);
|
|
3222
3868
|
}
|
|
3869
|
+
function registerComponentTools(server, state) {
|
|
3870
|
+
server.tool(
|
|
3871
|
+
"list_components",
|
|
3872
|
+
"List available reusable components from the registry, filtered by renderer and/or category. Components provide common utilities (PRNG, noise, easing, color math, etc.) that can be declared as dependencies instead of inlining code in the algorithm.",
|
|
3873
|
+
{
|
|
3874
|
+
renderer: z2.enum(["p5", "three", "glsl", "canvas2d", "svg"]).optional().describe("Filter by renderer compatibility"),
|
|
3875
|
+
category: z2.enum([
|
|
3876
|
+
"randomness",
|
|
3877
|
+
"noise",
|
|
3878
|
+
"math",
|
|
3879
|
+
"easing",
|
|
3880
|
+
"color",
|
|
3881
|
+
"vector",
|
|
3882
|
+
"geometry",
|
|
3883
|
+
"grid",
|
|
3884
|
+
"particle",
|
|
3885
|
+
"physics",
|
|
3886
|
+
"distribution",
|
|
3887
|
+
"pattern",
|
|
3888
|
+
"sdf",
|
|
3889
|
+
"transform",
|
|
3890
|
+
"animation",
|
|
3891
|
+
"string",
|
|
3892
|
+
"data-structure",
|
|
3893
|
+
"imaging"
|
|
3894
|
+
]).optional().describe("Filter by component category")
|
|
3895
|
+
},
|
|
3896
|
+
async (args) => {
|
|
3897
|
+
try {
|
|
3898
|
+
const result = await listComponents(state, args);
|
|
3899
|
+
return jsonResult(result);
|
|
3900
|
+
} catch (e) {
|
|
3901
|
+
return toolError(e instanceof Error ? e.message : String(e));
|
|
3902
|
+
}
|
|
3903
|
+
}
|
|
3904
|
+
);
|
|
3905
|
+
server.tool(
|
|
3906
|
+
"add_component",
|
|
3907
|
+
"Add a component dependency to an existing sketch. Resolves the component and any transitive dependencies from the registry, validates renderer compatibility, and writes the resolved form to the sketch file.",
|
|
3908
|
+
{
|
|
3909
|
+
sketchId: z2.string().describe("ID of the sketch to add the component to"),
|
|
3910
|
+
component: z2.string().describe("Component name (e.g. 'prng', 'noise-2d', 'glsl-noise')"),
|
|
3911
|
+
version: z2.string().optional().describe("Version range (default: '^1.0.0')")
|
|
3912
|
+
},
|
|
3913
|
+
async (args) => {
|
|
3914
|
+
try {
|
|
3915
|
+
const result = await addComponent(state, args);
|
|
3916
|
+
return jsonResult(result);
|
|
3917
|
+
} catch (e) {
|
|
3918
|
+
return toolError(e instanceof Error ? e.message : String(e));
|
|
3919
|
+
}
|
|
3920
|
+
}
|
|
3921
|
+
);
|
|
3922
|
+
server.tool(
|
|
3923
|
+
"remove_component",
|
|
3924
|
+
"Remove a component dependency from a sketch. Checks for dependent components and warns if the algorithm references the component's exports.",
|
|
3925
|
+
{
|
|
3926
|
+
sketchId: z2.string().describe("ID of the sketch to remove the component from"),
|
|
3927
|
+
component: z2.string().describe("Component name to remove")
|
|
3928
|
+
},
|
|
3929
|
+
async (args) => {
|
|
3930
|
+
try {
|
|
3931
|
+
const result = await removeComponent(state, args);
|
|
3932
|
+
return jsonResult(result);
|
|
3933
|
+
} catch (e) {
|
|
3934
|
+
return toolError(e instanceof Error ? e.message : String(e));
|
|
3935
|
+
}
|
|
3936
|
+
}
|
|
3937
|
+
);
|
|
3938
|
+
}
|
|
3223
3939
|
function registerSelectionTools(server, state) {
|
|
3224
3940
|
server.tool(
|
|
3225
3941
|
"get_selection",
|
|
@@ -3605,6 +4321,247 @@ function registerExportTools(server, state) {
|
|
|
3605
4321
|
}
|
|
3606
4322
|
);
|
|
3607
4323
|
}
|
|
4324
|
+
function registerDesignTools(server, state) {
|
|
4325
|
+
server.tool(
|
|
4326
|
+
"design_add_layer",
|
|
4327
|
+
"Add a new design layer of a given type to the active sketch. Layer types come from registered plugins (e.g. 'typography:text', 'filter:grain', 'shapes:rect', 'guides:thirds').",
|
|
4328
|
+
{
|
|
4329
|
+
sketchId: z2.string().optional().describe("Target sketch ID (default: selected sketch)"),
|
|
4330
|
+
type: z2.string().describe("Layer type ID (e.g. 'typography:text', 'filter:grain', 'shapes:rect')"),
|
|
4331
|
+
name: z2.string().optional().describe("Layer display name (default: type's display name)"),
|
|
4332
|
+
properties: z2.record(z2.unknown()).optional().describe("Initial layer properties (merged with type defaults)"),
|
|
4333
|
+
transform: z2.object({
|
|
4334
|
+
x: z2.number().optional(),
|
|
4335
|
+
y: z2.number().optional(),
|
|
4336
|
+
width: z2.number().optional(),
|
|
4337
|
+
height: z2.number().optional(),
|
|
4338
|
+
rotation: z2.number().optional(),
|
|
4339
|
+
scaleX: z2.number().optional(),
|
|
4340
|
+
scaleY: z2.number().optional(),
|
|
4341
|
+
anchorX: z2.number().optional(),
|
|
4342
|
+
anchorY: z2.number().optional()
|
|
4343
|
+
}).optional().describe("Layer transform (default: full canvas)"),
|
|
4344
|
+
opacity: z2.number().optional().describe("Layer opacity 0\u20131 (default: 1)"),
|
|
4345
|
+
blendMode: z2.string().optional().describe("Blend mode (default: 'normal')"),
|
|
4346
|
+
index: z2.number().optional().describe("Insert position in layer stack (default: top)")
|
|
4347
|
+
},
|
|
4348
|
+
async (args) => {
|
|
4349
|
+
try {
|
|
4350
|
+
const result = await designAddLayer(state, args);
|
|
4351
|
+
return jsonResult(result);
|
|
4352
|
+
} catch (e) {
|
|
4353
|
+
return toolError(e instanceof Error ? e.message : String(e));
|
|
4354
|
+
}
|
|
4355
|
+
}
|
|
4356
|
+
);
|
|
4357
|
+
server.tool(
|
|
4358
|
+
"design_remove_layer",
|
|
4359
|
+
"Remove a design layer from the active sketch",
|
|
4360
|
+
{
|
|
4361
|
+
sketchId: z2.string().optional().describe("Target sketch ID (default: selected sketch)"),
|
|
4362
|
+
layerId: z2.string().describe("ID of the layer to remove")
|
|
4363
|
+
},
|
|
4364
|
+
async (args) => {
|
|
4365
|
+
try {
|
|
4366
|
+
const result = await designRemoveLayer(state, args);
|
|
4367
|
+
return jsonResult(result);
|
|
4368
|
+
} catch (e) {
|
|
4369
|
+
return toolError(e instanceof Error ? e.message : String(e));
|
|
4370
|
+
}
|
|
4371
|
+
}
|
|
4372
|
+
);
|
|
4373
|
+
server.tool(
|
|
4374
|
+
"design_list_layers",
|
|
4375
|
+
"List all design layers in the active sketch with their types, visibility, and key properties",
|
|
4376
|
+
{
|
|
4377
|
+
sketchId: z2.string().optional().describe("Target sketch ID (default: selected sketch)")
|
|
4378
|
+
},
|
|
4379
|
+
async (args) => {
|
|
4380
|
+
try {
|
|
4381
|
+
const result = await designListLayers(state, args);
|
|
4382
|
+
return jsonResult(result);
|
|
4383
|
+
} catch (e) {
|
|
4384
|
+
return toolError(e instanceof Error ? e.message : String(e));
|
|
4385
|
+
}
|
|
4386
|
+
}
|
|
4387
|
+
);
|
|
4388
|
+
server.tool(
|
|
4389
|
+
"design_get_layer",
|
|
4390
|
+
"Get full details of a single design layer including all properties and transform",
|
|
4391
|
+
{
|
|
4392
|
+
sketchId: z2.string().optional().describe("Target sketch ID (default: selected sketch)"),
|
|
4393
|
+
layerId: z2.string().describe("ID of the layer to inspect")
|
|
4394
|
+
},
|
|
4395
|
+
async (args) => {
|
|
4396
|
+
try {
|
|
4397
|
+
const result = await designGetLayer(state, args);
|
|
4398
|
+
return jsonResult(result);
|
|
4399
|
+
} catch (e) {
|
|
4400
|
+
return toolError(e instanceof Error ? e.message : String(e));
|
|
4401
|
+
}
|
|
4402
|
+
}
|
|
4403
|
+
);
|
|
4404
|
+
server.tool(
|
|
4405
|
+
"design_update_layer",
|
|
4406
|
+
"Update properties on a design layer (e.g. text content, filter intensity, shape fill color)",
|
|
4407
|
+
{
|
|
4408
|
+
sketchId: z2.string().optional().describe("Target sketch ID (default: selected sketch)"),
|
|
4409
|
+
layerId: z2.string().describe("ID of the layer to update"),
|
|
4410
|
+
name: z2.string().optional().describe("New display name"),
|
|
4411
|
+
properties: z2.record(z2.unknown()).optional().describe("Property key-value pairs to set")
|
|
4412
|
+
},
|
|
4413
|
+
async (args) => {
|
|
4414
|
+
try {
|
|
4415
|
+
const result = await designUpdateLayer(state, args);
|
|
4416
|
+
return jsonResult(result);
|
|
4417
|
+
} catch (e) {
|
|
4418
|
+
return toolError(e instanceof Error ? e.message : String(e));
|
|
4419
|
+
}
|
|
4420
|
+
}
|
|
4421
|
+
);
|
|
4422
|
+
server.tool(
|
|
4423
|
+
"design_set_transform",
|
|
4424
|
+
"Set the position, size, rotation, and scale of a design layer",
|
|
4425
|
+
{
|
|
4426
|
+
sketchId: z2.string().optional().describe("Target sketch ID (default: selected sketch)"),
|
|
4427
|
+
layerId: z2.string().describe("ID of the layer to transform"),
|
|
4428
|
+
x: z2.number().optional().describe("X position"),
|
|
4429
|
+
y: z2.number().optional().describe("Y position"),
|
|
4430
|
+
width: z2.number().optional().describe("Width"),
|
|
4431
|
+
height: z2.number().optional().describe("Height"),
|
|
4432
|
+
rotation: z2.number().optional().describe("Rotation in degrees"),
|
|
4433
|
+
scaleX: z2.number().optional().describe("Horizontal scale"),
|
|
4434
|
+
scaleY: z2.number().optional().describe("Vertical scale"),
|
|
4435
|
+
anchorX: z2.number().optional().describe("Anchor X (0\u20131)"),
|
|
4436
|
+
anchorY: z2.number().optional().describe("Anchor Y (0\u20131)")
|
|
4437
|
+
},
|
|
4438
|
+
async (args) => {
|
|
4439
|
+
try {
|
|
4440
|
+
const result = await designSetTransform(state, args);
|
|
4441
|
+
return jsonResult(result);
|
|
4442
|
+
} catch (e) {
|
|
4443
|
+
return toolError(e instanceof Error ? e.message : String(e));
|
|
4444
|
+
}
|
|
4445
|
+
}
|
|
4446
|
+
);
|
|
4447
|
+
server.tool(
|
|
4448
|
+
"design_set_blend",
|
|
4449
|
+
"Set blend mode and/or opacity on a design layer",
|
|
4450
|
+
{
|
|
4451
|
+
sketchId: z2.string().optional().describe("Target sketch ID (default: selected sketch)"),
|
|
4452
|
+
layerId: z2.string().describe("ID of the layer"),
|
|
4453
|
+
blendMode: z2.enum([
|
|
4454
|
+
"normal",
|
|
4455
|
+
"multiply",
|
|
4456
|
+
"screen",
|
|
4457
|
+
"overlay",
|
|
4458
|
+
"darken",
|
|
4459
|
+
"lighten",
|
|
4460
|
+
"color-dodge",
|
|
4461
|
+
"color-burn",
|
|
4462
|
+
"hard-light",
|
|
4463
|
+
"soft-light",
|
|
4464
|
+
"difference",
|
|
4465
|
+
"exclusion",
|
|
4466
|
+
"hue",
|
|
4467
|
+
"saturation",
|
|
4468
|
+
"color",
|
|
4469
|
+
"luminosity"
|
|
4470
|
+
]).optional().describe("CSS blend mode"),
|
|
4471
|
+
opacity: z2.number().optional().describe("Layer opacity 0\u20131")
|
|
4472
|
+
},
|
|
4473
|
+
async (args) => {
|
|
4474
|
+
try {
|
|
4475
|
+
const result = await designSetBlend(state, args);
|
|
4476
|
+
return jsonResult(result);
|
|
4477
|
+
} catch (e) {
|
|
4478
|
+
return toolError(e instanceof Error ? e.message : String(e));
|
|
4479
|
+
}
|
|
4480
|
+
}
|
|
4481
|
+
);
|
|
4482
|
+
server.tool(
|
|
4483
|
+
"design_reorder_layers",
|
|
4484
|
+
"Move a design layer to a new position in the z-order stack",
|
|
4485
|
+
{
|
|
4486
|
+
sketchId: z2.string().optional().describe("Target sketch ID (default: selected sketch)"),
|
|
4487
|
+
layerId: z2.string().describe("ID of the layer to move"),
|
|
4488
|
+
newIndex: z2.number().describe("New position (0 = bottom, n-1 = top)")
|
|
4489
|
+
},
|
|
4490
|
+
async (args) => {
|
|
4491
|
+
try {
|
|
4492
|
+
const result = await designReorderLayers(state, args);
|
|
4493
|
+
return jsonResult(result);
|
|
4494
|
+
} catch (e) {
|
|
4495
|
+
return toolError(e instanceof Error ? e.message : String(e));
|
|
4496
|
+
}
|
|
4497
|
+
}
|
|
4498
|
+
);
|
|
4499
|
+
server.tool(
|
|
4500
|
+
"design_duplicate_layer",
|
|
4501
|
+
"Clone a design layer with a new ID, inserted directly above the source",
|
|
4502
|
+
{
|
|
4503
|
+
sketchId: z2.string().optional().describe("Target sketch ID (default: selected sketch)"),
|
|
4504
|
+
layerId: z2.string().describe("ID of the layer to duplicate")
|
|
4505
|
+
},
|
|
4506
|
+
async (args) => {
|
|
4507
|
+
try {
|
|
4508
|
+
const result = await designDuplicateLayer(state, args);
|
|
4509
|
+
return jsonResult(result);
|
|
4510
|
+
} catch (e) {
|
|
4511
|
+
return toolError(e instanceof Error ? e.message : String(e));
|
|
4512
|
+
}
|
|
4513
|
+
}
|
|
4514
|
+
);
|
|
4515
|
+
server.tool(
|
|
4516
|
+
"design_toggle_visibility",
|
|
4517
|
+
"Show or hide a design layer",
|
|
4518
|
+
{
|
|
4519
|
+
sketchId: z2.string().optional().describe("Target sketch ID (default: selected sketch)"),
|
|
4520
|
+
layerId: z2.string().describe("ID of the layer"),
|
|
4521
|
+
visible: z2.boolean().optional().describe("Set visibility (default: toggle)")
|
|
4522
|
+
},
|
|
4523
|
+
async (args) => {
|
|
4524
|
+
try {
|
|
4525
|
+
const result = await designToggleVisibility(state, args);
|
|
4526
|
+
return jsonResult(result);
|
|
4527
|
+
} catch (e) {
|
|
4528
|
+
return toolError(e instanceof Error ? e.message : String(e));
|
|
4529
|
+
}
|
|
4530
|
+
}
|
|
4531
|
+
);
|
|
4532
|
+
server.tool(
|
|
4533
|
+
"design_lock_layer",
|
|
4534
|
+
"Lock or unlock a design layer to prevent accidental edits",
|
|
4535
|
+
{
|
|
4536
|
+
sketchId: z2.string().optional().describe("Target sketch ID (default: selected sketch)"),
|
|
4537
|
+
layerId: z2.string().describe("ID of the layer"),
|
|
4538
|
+
locked: z2.boolean().optional().describe("Set lock state (default: toggle)")
|
|
4539
|
+
},
|
|
4540
|
+
async (args) => {
|
|
4541
|
+
try {
|
|
4542
|
+
const result = await designLockLayer(state, args);
|
|
4543
|
+
return jsonResult(result);
|
|
4544
|
+
} catch (e) {
|
|
4545
|
+
return toolError(e instanceof Error ? e.message : String(e));
|
|
4546
|
+
}
|
|
4547
|
+
}
|
|
4548
|
+
);
|
|
4549
|
+
server.tool(
|
|
4550
|
+
"design_capture_composite",
|
|
4551
|
+
"Get info about the design layer composite for a sketch. For full visual capture use capture_screenshot.",
|
|
4552
|
+
{
|
|
4553
|
+
sketchId: z2.string().optional().describe("Target sketch ID (default: selected sketch)")
|
|
4554
|
+
},
|
|
4555
|
+
async (args) => {
|
|
4556
|
+
try {
|
|
4557
|
+
const result = await designCaptureComposite(state, args);
|
|
4558
|
+
return jsonResult(result);
|
|
4559
|
+
} catch (e) {
|
|
4560
|
+
return toolError(e instanceof Error ? e.message : String(e));
|
|
4561
|
+
}
|
|
4562
|
+
}
|
|
4563
|
+
);
|
|
4564
|
+
}
|
|
3608
4565
|
function registerKnowledgeTools(server, _state) {
|
|
3609
4566
|
server.tool(
|
|
3610
4567
|
"list_skills",
|
|
@@ -3676,10 +4633,11 @@ function notifyMutation(type, payload) {
|
|
|
3676
4633
|
import {
|
|
3677
4634
|
parseGenart as parseGenart4,
|
|
3678
4635
|
parseWorkspace as parseWorkspace2,
|
|
3679
|
-
serializeGenart as
|
|
3680
|
-
serializeWorkspace as serializeWorkspace3
|
|
4636
|
+
serializeGenart as serializeGenart5,
|
|
4637
|
+
serializeWorkspace as serializeWorkspace3,
|
|
4638
|
+
createLayerStack
|
|
3681
4639
|
} from "@genart-dev/core";
|
|
3682
|
-
import { writeFile as
|
|
4640
|
+
import { writeFile as writeFile7 } from "fs/promises";
|
|
3683
4641
|
var EditorState = class extends EventEmitter {
|
|
3684
4642
|
/** Absolute path to the active .genart-workspace file, or null. */
|
|
3685
4643
|
workspacePath = null;
|
|
@@ -3701,6 +4659,10 @@ var EditorState = class extends EventEmitter {
|
|
|
3701
4659
|
* instead of writing to disk. Set by mcp-host for HTTP-based sessions.
|
|
3702
4660
|
*/
|
|
3703
4661
|
remoteMode = false;
|
|
4662
|
+
/** Plugin registry for design mode. Set during server initialization. */
|
|
4663
|
+
pluginRegistry = null;
|
|
4664
|
+
/** Layer stacks keyed by sketch ID. Created lazily when design tools are used. */
|
|
4665
|
+
layerStacks = /* @__PURE__ */ new Map();
|
|
3704
4666
|
constructor(options) {
|
|
3705
4667
|
super();
|
|
3706
4668
|
if (options?.basePath) {
|
|
@@ -3765,6 +4727,7 @@ var EditorState = class extends EventEmitter {
|
|
|
3765
4727
|
this.workspace = ws;
|
|
3766
4728
|
this.sketches.clear();
|
|
3767
4729
|
this.selection.clear();
|
|
4730
|
+
this.layerStacks.clear();
|
|
3768
4731
|
for (const ref of ws.sketches) {
|
|
3769
4732
|
const sketchPath = this.resolveSketchPath(ref.file);
|
|
3770
4733
|
await this.loadSketch(sketchPath);
|
|
@@ -3806,6 +4769,7 @@ var EditorState = class extends EventEmitter {
|
|
|
3806
4769
|
removeSketch(id) {
|
|
3807
4770
|
this.sketches.delete(id);
|
|
3808
4771
|
this.selection.delete(id);
|
|
4772
|
+
this.layerStacks.delete(id);
|
|
3809
4773
|
this.emitMutation("sketch:removed", { id });
|
|
3810
4774
|
}
|
|
3811
4775
|
/** Save the active workspace to disk. */
|
|
@@ -3815,16 +4779,16 @@ var EditorState = class extends EventEmitter {
|
|
|
3815
4779
|
}
|
|
3816
4780
|
const json = serializeWorkspace3(this.workspace);
|
|
3817
4781
|
if (!this.remoteMode) {
|
|
3818
|
-
await
|
|
4782
|
+
await writeFile7(this.workspacePath, json, "utf-8");
|
|
3819
4783
|
}
|
|
3820
4784
|
this.emitMutation("workspace:saved", { path: this.workspacePath });
|
|
3821
4785
|
}
|
|
3822
4786
|
/** Save a sketch to disk. */
|
|
3823
4787
|
async saveSketch(id) {
|
|
3824
4788
|
const loaded = this.requireSketch(id);
|
|
3825
|
-
const json =
|
|
4789
|
+
const json = serializeGenart5(loaded.definition);
|
|
3826
4790
|
if (!this.remoteMode) {
|
|
3827
|
-
await
|
|
4791
|
+
await writeFile7(loaded.path, json, "utf-8");
|
|
3828
4792
|
}
|
|
3829
4793
|
this.emitMutation("sketch:saved", { id, path: loaded.path });
|
|
3830
4794
|
}
|
|
@@ -3849,6 +4813,81 @@ var EditorState = class extends EventEmitter {
|
|
|
3849
4813
|
selection: Array.from(this.selection)
|
|
3850
4814
|
};
|
|
3851
4815
|
}
|
|
4816
|
+
/**
|
|
4817
|
+
* Get or create a LayerStackAccessor for a sketch.
|
|
4818
|
+
* Initializes from the sketch's persisted design layers.
|
|
4819
|
+
*/
|
|
4820
|
+
getLayerStack(sketchId) {
|
|
4821
|
+
let stack = this.layerStacks.get(sketchId);
|
|
4822
|
+
if (stack) return stack;
|
|
4823
|
+
const loaded = this.requireSketch(sketchId);
|
|
4824
|
+
const initialLayers = loaded.definition.layers ?? [];
|
|
4825
|
+
stack = createLayerStack(initialLayers, (changeType) => {
|
|
4826
|
+
this.syncLayersToDefinition(sketchId);
|
|
4827
|
+
const mutationType = `design:${changeType}`;
|
|
4828
|
+
this.emitMutation(mutationType, { sketchId, changeType });
|
|
4829
|
+
});
|
|
4830
|
+
this.layerStacks.set(sketchId, stack);
|
|
4831
|
+
return stack;
|
|
4832
|
+
}
|
|
4833
|
+
/**
|
|
4834
|
+
* Sync the layer stack's current state back to the sketch definition.
|
|
4835
|
+
* Called automatically on every layer mutation.
|
|
4836
|
+
*/
|
|
4837
|
+
syncLayersToDefinition(sketchId) {
|
|
4838
|
+
const loaded = this.sketches.get(sketchId);
|
|
4839
|
+
const stack = this.layerStacks.get(sketchId);
|
|
4840
|
+
if (!loaded || !stack) return;
|
|
4841
|
+
const layers = stack.getAll();
|
|
4842
|
+
loaded.definition = {
|
|
4843
|
+
...loaded.definition,
|
|
4844
|
+
layers: layers.length > 0 ? layers : void 0
|
|
4845
|
+
};
|
|
4846
|
+
}
|
|
4847
|
+
/**
|
|
4848
|
+
* Create an McpToolContext for a plugin's MCP tool handler.
|
|
4849
|
+
* Provides access to the layer stack, sketch state, and change notifications.
|
|
4850
|
+
*/
|
|
4851
|
+
createMcpToolContext(sketchId) {
|
|
4852
|
+
const loaded = this.requireSketch(sketchId);
|
|
4853
|
+
const layerStack = this.getLayerStack(sketchId);
|
|
4854
|
+
const def = loaded.definition;
|
|
4855
|
+
const sketchState = {
|
|
4856
|
+
seed: def.state.seed,
|
|
4857
|
+
params: def.state.params,
|
|
4858
|
+
colorPalette: def.state.colorPalette,
|
|
4859
|
+
canvasWidth: def.canvas.width,
|
|
4860
|
+
canvasHeight: def.canvas.height,
|
|
4861
|
+
rendererId: def.renderer.type
|
|
4862
|
+
};
|
|
4863
|
+
return {
|
|
4864
|
+
layers: layerStack,
|
|
4865
|
+
sketchState,
|
|
4866
|
+
canvasWidth: def.canvas.width,
|
|
4867
|
+
canvasHeight: def.canvas.height,
|
|
4868
|
+
async resolveAsset(_assetId) {
|
|
4869
|
+
return null;
|
|
4870
|
+
},
|
|
4871
|
+
async captureComposite(_format) {
|
|
4872
|
+
throw new Error("captureComposite is not available in headless MCP mode");
|
|
4873
|
+
},
|
|
4874
|
+
emitChange(_changeType) {
|
|
4875
|
+
}
|
|
4876
|
+
};
|
|
4877
|
+
}
|
|
4878
|
+
/**
|
|
4879
|
+
* Get the currently selected sketch ID for design operations.
|
|
4880
|
+
* Returns the single selected sketch, or throws if none/multiple selected.
|
|
4881
|
+
*/
|
|
4882
|
+
requireSelectedSketchId() {
|
|
4883
|
+
if (this.selection.size === 0) {
|
|
4884
|
+
throw new Error("No sketch is selected. Use select_sketch or open_sketch first.");
|
|
4885
|
+
}
|
|
4886
|
+
if (this.selection.size > 1) {
|
|
4887
|
+
throw new Error("Multiple sketches are selected. Design operations require a single sketch.");
|
|
4888
|
+
}
|
|
4889
|
+
return this.selection.values().next().value;
|
|
4890
|
+
}
|
|
3852
4891
|
/** Emit a mutation event for external listeners (WebSocket broadcast, sidecar IPC). */
|
|
3853
4892
|
emitMutation(type, payload) {
|
|
3854
4893
|
this.emit("mutation", { type, payload });
|