@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/index.js
CHANGED
|
@@ -25,7 +25,8 @@ import {
|
|
|
25
25
|
parseGenart,
|
|
26
26
|
parseWorkspace,
|
|
27
27
|
serializeGenart,
|
|
28
|
-
serializeWorkspace
|
|
28
|
+
serializeWorkspace,
|
|
29
|
+
createLayerStack
|
|
29
30
|
} from "@genart-dev/core";
|
|
30
31
|
import { writeFile } from "fs/promises";
|
|
31
32
|
var EditorState = class extends EventEmitter {
|
|
@@ -49,6 +50,10 @@ var EditorState = class extends EventEmitter {
|
|
|
49
50
|
* instead of writing to disk. Set by mcp-host for HTTP-based sessions.
|
|
50
51
|
*/
|
|
51
52
|
remoteMode = false;
|
|
53
|
+
/** Plugin registry for design mode. Set during server initialization. */
|
|
54
|
+
pluginRegistry = null;
|
|
55
|
+
/** Layer stacks keyed by sketch ID. Created lazily when design tools are used. */
|
|
56
|
+
layerStacks = /* @__PURE__ */ new Map();
|
|
52
57
|
constructor(options) {
|
|
53
58
|
super();
|
|
54
59
|
if (options?.basePath) {
|
|
@@ -113,6 +118,7 @@ var EditorState = class extends EventEmitter {
|
|
|
113
118
|
this.workspace = ws;
|
|
114
119
|
this.sketches.clear();
|
|
115
120
|
this.selection.clear();
|
|
121
|
+
this.layerStacks.clear();
|
|
116
122
|
for (const ref of ws.sketches) {
|
|
117
123
|
const sketchPath = this.resolveSketchPath(ref.file);
|
|
118
124
|
await this.loadSketch(sketchPath);
|
|
@@ -154,6 +160,7 @@ var EditorState = class extends EventEmitter {
|
|
|
154
160
|
removeSketch(id) {
|
|
155
161
|
this.sketches.delete(id);
|
|
156
162
|
this.selection.delete(id);
|
|
163
|
+
this.layerStacks.delete(id);
|
|
157
164
|
this.emitMutation("sketch:removed", { id });
|
|
158
165
|
}
|
|
159
166
|
/** Save the active workspace to disk. */
|
|
@@ -197,6 +204,81 @@ var EditorState = class extends EventEmitter {
|
|
|
197
204
|
selection: Array.from(this.selection)
|
|
198
205
|
};
|
|
199
206
|
}
|
|
207
|
+
/**
|
|
208
|
+
* Get or create a LayerStackAccessor for a sketch.
|
|
209
|
+
* Initializes from the sketch's persisted design layers.
|
|
210
|
+
*/
|
|
211
|
+
getLayerStack(sketchId) {
|
|
212
|
+
let stack = this.layerStacks.get(sketchId);
|
|
213
|
+
if (stack) return stack;
|
|
214
|
+
const loaded = this.requireSketch(sketchId);
|
|
215
|
+
const initialLayers = loaded.definition.layers ?? [];
|
|
216
|
+
stack = createLayerStack(initialLayers, (changeType) => {
|
|
217
|
+
this.syncLayersToDefinition(sketchId);
|
|
218
|
+
const mutationType = `design:${changeType}`;
|
|
219
|
+
this.emitMutation(mutationType, { sketchId, changeType });
|
|
220
|
+
});
|
|
221
|
+
this.layerStacks.set(sketchId, stack);
|
|
222
|
+
return stack;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Sync the layer stack's current state back to the sketch definition.
|
|
226
|
+
* Called automatically on every layer mutation.
|
|
227
|
+
*/
|
|
228
|
+
syncLayersToDefinition(sketchId) {
|
|
229
|
+
const loaded = this.sketches.get(sketchId);
|
|
230
|
+
const stack = this.layerStacks.get(sketchId);
|
|
231
|
+
if (!loaded || !stack) return;
|
|
232
|
+
const layers = stack.getAll();
|
|
233
|
+
loaded.definition = {
|
|
234
|
+
...loaded.definition,
|
|
235
|
+
layers: layers.length > 0 ? layers : void 0
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Create an McpToolContext for a plugin's MCP tool handler.
|
|
240
|
+
* Provides access to the layer stack, sketch state, and change notifications.
|
|
241
|
+
*/
|
|
242
|
+
createMcpToolContext(sketchId) {
|
|
243
|
+
const loaded = this.requireSketch(sketchId);
|
|
244
|
+
const layerStack = this.getLayerStack(sketchId);
|
|
245
|
+
const def = loaded.definition;
|
|
246
|
+
const sketchState = {
|
|
247
|
+
seed: def.state.seed,
|
|
248
|
+
params: def.state.params,
|
|
249
|
+
colorPalette: def.state.colorPalette,
|
|
250
|
+
canvasWidth: def.canvas.width,
|
|
251
|
+
canvasHeight: def.canvas.height,
|
|
252
|
+
rendererId: def.renderer.type
|
|
253
|
+
};
|
|
254
|
+
return {
|
|
255
|
+
layers: layerStack,
|
|
256
|
+
sketchState,
|
|
257
|
+
canvasWidth: def.canvas.width,
|
|
258
|
+
canvasHeight: def.canvas.height,
|
|
259
|
+
async resolveAsset(_assetId) {
|
|
260
|
+
return null;
|
|
261
|
+
},
|
|
262
|
+
async captureComposite(_format) {
|
|
263
|
+
throw new Error("captureComposite is not available in headless MCP mode");
|
|
264
|
+
},
|
|
265
|
+
emitChange(_changeType) {
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Get the currently selected sketch ID for design operations.
|
|
271
|
+
* Returns the single selected sketch, or throws if none/multiple selected.
|
|
272
|
+
*/
|
|
273
|
+
requireSelectedSketchId() {
|
|
274
|
+
if (this.selection.size === 0) {
|
|
275
|
+
throw new Error("No sketch is selected. Use select_sketch or open_sketch first.");
|
|
276
|
+
}
|
|
277
|
+
if (this.selection.size > 1) {
|
|
278
|
+
throw new Error("Multiple sketches are selected. Design operations require a single sketch.");
|
|
279
|
+
}
|
|
280
|
+
return this.selection.values().next().value;
|
|
281
|
+
}
|
|
200
282
|
/** Emit a mutation event for external listeners (WebSocket broadcast, sidecar IPC). */
|
|
201
283
|
emitMutation(type, payload) {
|
|
202
284
|
this.emit("mutation", { type, payload });
|
|
@@ -207,6 +289,11 @@ var EditorState = class extends EventEmitter {
|
|
|
207
289
|
// src/server.ts
|
|
208
290
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
209
291
|
import { z as z2 } from "zod";
|
|
292
|
+
import { createPluginRegistry } from "@genart-dev/core";
|
|
293
|
+
import typographyPlugin from "@genart-dev/plugin-typography";
|
|
294
|
+
import filtersPlugin from "@genart-dev/plugin-filters";
|
|
295
|
+
import shapesPlugin from "@genart-dev/plugin-shapes";
|
|
296
|
+
import layoutGuidesPlugin from "@genart-dev/plugin-layout-guides";
|
|
210
297
|
|
|
211
298
|
// src/tools/workspace.ts
|
|
212
299
|
import { readFile as readFile2, writeFile as writeFile2, stat } from "fs/promises";
|
|
@@ -551,6 +638,7 @@ import { writeFile as writeFile3, stat as stat2, unlink } from "fs/promises";
|
|
|
551
638
|
import { basename as basename2, dirname as dirname3, resolve as resolve2 } from "path";
|
|
552
639
|
import {
|
|
553
640
|
createDefaultRegistry,
|
|
641
|
+
resolveComponents,
|
|
554
642
|
resolvePreset,
|
|
555
643
|
serializeGenart as serializeGenart2,
|
|
556
644
|
serializeWorkspace as serializeWorkspace3
|
|
@@ -647,10 +735,40 @@ async function createSketch(state, input) {
|
|
|
647
735
|
const adapter = registry4.resolve(rendererType);
|
|
648
736
|
algorithm = adapter.getAlgorithmTemplate();
|
|
649
737
|
}
|
|
738
|
+
let resolvedComponents;
|
|
739
|
+
if (input.components && Object.keys(input.components).length > 0) {
|
|
740
|
+
const shorthand = {};
|
|
741
|
+
for (const [name, value] of Object.entries(input.components)) {
|
|
742
|
+
if (typeof value === "string") {
|
|
743
|
+
shorthand[name] = value;
|
|
744
|
+
} else if (value.version) {
|
|
745
|
+
shorthand[name] = value.version;
|
|
746
|
+
} else if (value.code) {
|
|
747
|
+
if (!resolvedComponents) resolvedComponents = {};
|
|
748
|
+
resolvedComponents[name] = {
|
|
749
|
+
...value.version ? { version: value.version } : {},
|
|
750
|
+
code: value.code,
|
|
751
|
+
...value.exports ? { exports: value.exports } : {}
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
if (Object.keys(shorthand).length > 0) {
|
|
756
|
+
const resolved = resolveComponents(shorthand, rendererType);
|
|
757
|
+
if (!resolvedComponents) resolvedComponents = {};
|
|
758
|
+
for (const rc of resolved) {
|
|
759
|
+
resolvedComponents[rc.name] = {
|
|
760
|
+
version: rc.version,
|
|
761
|
+
code: rc.code,
|
|
762
|
+
exports: [...rc.exports]
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
}
|
|
650
767
|
const seed = input.seed ?? Math.floor(Math.random() * 1e5);
|
|
651
768
|
const ts = now2();
|
|
769
|
+
const hasComponents = resolvedComponents && Object.keys(resolvedComponents).length > 0;
|
|
652
770
|
const sketch = {
|
|
653
|
-
genart: "1.1",
|
|
771
|
+
genart: hasComponents ? "1.2" : "1.1",
|
|
654
772
|
id: input.id,
|
|
655
773
|
title: input.title,
|
|
656
774
|
created: ts,
|
|
@@ -664,6 +782,7 @@ async function createSketch(state, input) {
|
|
|
664
782
|
...input.philosophy ? { philosophy: input.philosophy } : {},
|
|
665
783
|
...input.themes && input.themes.length > 0 ? { themes: input.themes } : {},
|
|
666
784
|
...input.skills && input.skills.length > 0 ? { skills: input.skills } : {},
|
|
785
|
+
...hasComponents ? { components: resolvedComponents } : {},
|
|
667
786
|
...input.agent ? { agent: input.agent } : {},
|
|
668
787
|
...input.model ? { model: input.model } : {}
|
|
669
788
|
};
|
|
@@ -847,10 +966,44 @@ async function updateAlgorithm(state, input) {
|
|
|
847
966
|
);
|
|
848
967
|
}
|
|
849
968
|
}
|
|
969
|
+
let resolvedComponents;
|
|
970
|
+
if (input.components && Object.keys(input.components).length > 0) {
|
|
971
|
+
const renderer = def.renderer.type;
|
|
972
|
+
const shorthand = {};
|
|
973
|
+
for (const [name, value] of Object.entries(input.components)) {
|
|
974
|
+
if (typeof value === "string") {
|
|
975
|
+
shorthand[name] = value;
|
|
976
|
+
} else if (value.version) {
|
|
977
|
+
shorthand[name] = value.version;
|
|
978
|
+
} else if (value.code) {
|
|
979
|
+
if (!resolvedComponents) resolvedComponents = {};
|
|
980
|
+
resolvedComponents[name] = {
|
|
981
|
+
...value.version ? { version: value.version } : {},
|
|
982
|
+
code: value.code,
|
|
983
|
+
...value.exports ? { exports: value.exports } : {}
|
|
984
|
+
};
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
if (Object.keys(shorthand).length > 0) {
|
|
988
|
+
const resolved = resolveComponents(shorthand, renderer);
|
|
989
|
+
if (!resolvedComponents) resolvedComponents = {};
|
|
990
|
+
for (const rc of resolved) {
|
|
991
|
+
resolvedComponents[rc.name] = {
|
|
992
|
+
version: rc.version,
|
|
993
|
+
code: rc.code,
|
|
994
|
+
exports: [...rc.exports]
|
|
995
|
+
};
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
const updated = ["algorithm"];
|
|
1000
|
+
const hasNewComponents = resolvedComponents && Object.keys(resolvedComponents).length > 0;
|
|
1001
|
+
if (hasNewComponents) updated.push("components");
|
|
850
1002
|
const newDef = {
|
|
851
1003
|
...def,
|
|
852
1004
|
modified: now2(),
|
|
853
1005
|
algorithm: input.algorithm,
|
|
1006
|
+
...hasNewComponents ? { genart: "1.2", components: resolvedComponents } : {},
|
|
854
1007
|
...input.agent ? { agent: input.agent } : {},
|
|
855
1008
|
...input.model ? { model: input.model } : {}
|
|
856
1009
|
};
|
|
@@ -864,7 +1017,7 @@ async function updateAlgorithm(state, input) {
|
|
|
864
1017
|
}
|
|
865
1018
|
state.emitMutation("sketch:updated", {
|
|
866
1019
|
id: input.sketchId,
|
|
867
|
-
updated
|
|
1020
|
+
updated
|
|
868
1021
|
});
|
|
869
1022
|
return {
|
|
870
1023
|
success: true,
|
|
@@ -872,6 +1025,7 @@ async function updateAlgorithm(state, input) {
|
|
|
872
1025
|
renderer: def.renderer.type,
|
|
873
1026
|
algorithmLength: input.algorithm.length,
|
|
874
1027
|
validationPassed,
|
|
1028
|
+
...hasNewComponents ? { componentsUpdated: true } : {},
|
|
875
1029
|
fileContent: json
|
|
876
1030
|
};
|
|
877
1031
|
}
|
|
@@ -2173,8 +2327,219 @@ ${guidelines}`,
|
|
|
2173
2327
|
};
|
|
2174
2328
|
}
|
|
2175
2329
|
|
|
2176
|
-
// src/tools/
|
|
2330
|
+
// src/tools/components.ts
|
|
2177
2331
|
import { writeFile as writeFile5 } from "fs/promises";
|
|
2332
|
+
import {
|
|
2333
|
+
COMPONENT_REGISTRY,
|
|
2334
|
+
resolveComponents as resolveComponents2,
|
|
2335
|
+
serializeGenart as serializeGenart4
|
|
2336
|
+
} from "@genart-dev/core";
|
|
2337
|
+
var VALID_RENDERERS2 = [
|
|
2338
|
+
"p5",
|
|
2339
|
+
"three",
|
|
2340
|
+
"glsl",
|
|
2341
|
+
"canvas2d",
|
|
2342
|
+
"svg"
|
|
2343
|
+
];
|
|
2344
|
+
var RENDERER_TARGET = {
|
|
2345
|
+
p5: "js",
|
|
2346
|
+
three: "js",
|
|
2347
|
+
canvas2d: "js",
|
|
2348
|
+
svg: "js",
|
|
2349
|
+
glsl: "glsl"
|
|
2350
|
+
};
|
|
2351
|
+
async function listComponents(_state, input) {
|
|
2352
|
+
let entries = Object.values(COMPONENT_REGISTRY);
|
|
2353
|
+
if (input.renderer) {
|
|
2354
|
+
const renderer = input.renderer;
|
|
2355
|
+
const target = RENDERER_TARGET[renderer];
|
|
2356
|
+
if (!target) {
|
|
2357
|
+
throw new Error(
|
|
2358
|
+
`Unknown renderer type: '${input.renderer}'. Valid types: ${VALID_RENDERERS2.join(", ")}`
|
|
2359
|
+
);
|
|
2360
|
+
}
|
|
2361
|
+
entries = entries.filter(
|
|
2362
|
+
(e) => e.target === target && (e.renderers.length === 0 || e.renderers.includes(renderer))
|
|
2363
|
+
);
|
|
2364
|
+
}
|
|
2365
|
+
if (input.category) {
|
|
2366
|
+
const cat = input.category;
|
|
2367
|
+
entries = entries.filter((e) => e.category === cat);
|
|
2368
|
+
}
|
|
2369
|
+
entries.sort((a, b) => {
|
|
2370
|
+
const catCmp = a.category.localeCompare(b.category);
|
|
2371
|
+
if (catCmp !== 0) return catCmp;
|
|
2372
|
+
return a.name.localeCompare(b.name);
|
|
2373
|
+
});
|
|
2374
|
+
const components = entries.map((e) => ({
|
|
2375
|
+
name: e.name,
|
|
2376
|
+
version: e.version,
|
|
2377
|
+
category: e.category,
|
|
2378
|
+
target: e.target,
|
|
2379
|
+
exports: [...e.exports],
|
|
2380
|
+
dependencies: [...e.dependencies],
|
|
2381
|
+
description: e.description
|
|
2382
|
+
}));
|
|
2383
|
+
return {
|
|
2384
|
+
count: components.length,
|
|
2385
|
+
components
|
|
2386
|
+
};
|
|
2387
|
+
}
|
|
2388
|
+
async function addComponent(state, input) {
|
|
2389
|
+
const loaded = state.requireSketch(input.sketchId);
|
|
2390
|
+
const def = loaded.definition;
|
|
2391
|
+
const renderer = def.renderer.type;
|
|
2392
|
+
const entry = COMPONENT_REGISTRY[input.component];
|
|
2393
|
+
if (!entry) {
|
|
2394
|
+
throw new Error(`Unknown component: "${input.component}"`);
|
|
2395
|
+
}
|
|
2396
|
+
const target = RENDERER_TARGET[renderer];
|
|
2397
|
+
if (entry.target !== target) {
|
|
2398
|
+
throw new Error(
|
|
2399
|
+
`Component "${input.component}" has target "${entry.target}" but renderer "${renderer}" requires target "${target}"`
|
|
2400
|
+
);
|
|
2401
|
+
}
|
|
2402
|
+
if (entry.renderers.length > 0 && !entry.renderers.includes(renderer)) {
|
|
2403
|
+
throw new Error(
|
|
2404
|
+
`Component "${input.component}" is not compatible with renderer "${renderer}". Compatible: ${entry.renderers.join(", ")}`
|
|
2405
|
+
);
|
|
2406
|
+
}
|
|
2407
|
+
const existingComponents = {};
|
|
2408
|
+
if (def.components) {
|
|
2409
|
+
for (const [name, value] of Object.entries(def.components)) {
|
|
2410
|
+
if (typeof value === "string") {
|
|
2411
|
+
existingComponents[name] = value;
|
|
2412
|
+
} else if (value.version) {
|
|
2413
|
+
existingComponents[name] = value.version;
|
|
2414
|
+
}
|
|
2415
|
+
}
|
|
2416
|
+
}
|
|
2417
|
+
if (existingComponents[input.component]) {
|
|
2418
|
+
throw new Error(
|
|
2419
|
+
`Component "${input.component}" is already present in sketch "${input.sketchId}"`
|
|
2420
|
+
);
|
|
2421
|
+
}
|
|
2422
|
+
existingComponents[input.component] = input.version ?? "^1.0.0";
|
|
2423
|
+
const resolved = resolveComponents2(existingComponents, renderer);
|
|
2424
|
+
const resolvedRecord = {};
|
|
2425
|
+
for (const rc of resolved) {
|
|
2426
|
+
resolvedRecord[rc.name] = {
|
|
2427
|
+
version: rc.version,
|
|
2428
|
+
code: rc.code,
|
|
2429
|
+
exports: [...rc.exports]
|
|
2430
|
+
};
|
|
2431
|
+
}
|
|
2432
|
+
const previousNames = new Set(
|
|
2433
|
+
def.components ? Object.keys(def.components) : []
|
|
2434
|
+
);
|
|
2435
|
+
const added = resolved.map((rc) => rc.name).filter((name) => !previousNames.has(name));
|
|
2436
|
+
const newDef = {
|
|
2437
|
+
...def,
|
|
2438
|
+
genart: "1.2",
|
|
2439
|
+
modified: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2440
|
+
components: resolvedRecord
|
|
2441
|
+
};
|
|
2442
|
+
state.sketches.set(input.sketchId, {
|
|
2443
|
+
definition: newDef,
|
|
2444
|
+
path: loaded.path
|
|
2445
|
+
});
|
|
2446
|
+
const json = serializeGenart4(newDef);
|
|
2447
|
+
if (!state.remoteMode) {
|
|
2448
|
+
await writeFile5(loaded.path, json, "utf-8");
|
|
2449
|
+
}
|
|
2450
|
+
state.emitMutation("sketch:updated", {
|
|
2451
|
+
id: input.sketchId,
|
|
2452
|
+
updated: ["components"]
|
|
2453
|
+
});
|
|
2454
|
+
return {
|
|
2455
|
+
success: true,
|
|
2456
|
+
sketchId: input.sketchId,
|
|
2457
|
+
components: resolvedRecord,
|
|
2458
|
+
added,
|
|
2459
|
+
fileContent: json
|
|
2460
|
+
};
|
|
2461
|
+
}
|
|
2462
|
+
async function removeComponent(state, input) {
|
|
2463
|
+
const loaded = state.requireSketch(input.sketchId);
|
|
2464
|
+
const def = loaded.definition;
|
|
2465
|
+
if (!def.components || !def.components[input.component]) {
|
|
2466
|
+
throw new Error(
|
|
2467
|
+
`Component "${input.component}" is not present in sketch "${input.sketchId}"`
|
|
2468
|
+
);
|
|
2469
|
+
}
|
|
2470
|
+
const remaining = { ...def.components };
|
|
2471
|
+
delete remaining[input.component];
|
|
2472
|
+
for (const [name, value] of Object.entries(remaining)) {
|
|
2473
|
+
const entry = COMPONENT_REGISTRY[name];
|
|
2474
|
+
if (entry && entry.dependencies.includes(input.component)) {
|
|
2475
|
+
throw new Error(
|
|
2476
|
+
`Cannot remove "${input.component}": component "${name}" depends on it`
|
|
2477
|
+
);
|
|
2478
|
+
}
|
|
2479
|
+
}
|
|
2480
|
+
let warning;
|
|
2481
|
+
const removedValue = def.components[input.component];
|
|
2482
|
+
const exports = typeof removedValue === "string" ? COMPONENT_REGISTRY[input.component]?.exports ?? [] : removedValue.exports ?? [];
|
|
2483
|
+
const usedExports = exports.filter((exp) => def.algorithm.includes(exp));
|
|
2484
|
+
if (usedExports.length > 0) {
|
|
2485
|
+
warning = `Algorithm may reference these exports from "${input.component}": ${usedExports.join(", ")}. Review your algorithm after removal.`;
|
|
2486
|
+
}
|
|
2487
|
+
const removed = [input.component];
|
|
2488
|
+
const neededDeps = /* @__PURE__ */ new Set();
|
|
2489
|
+
for (const name of Object.keys(remaining)) {
|
|
2490
|
+
const entry = COMPONENT_REGISTRY[name];
|
|
2491
|
+
if (entry) {
|
|
2492
|
+
collectTransitiveDeps(name, neededDeps);
|
|
2493
|
+
}
|
|
2494
|
+
}
|
|
2495
|
+
for (const name of Object.keys(remaining)) {
|
|
2496
|
+
if (!neededDeps.has(name) && !isDirectComponent(name, remaining)) {
|
|
2497
|
+
delete remaining[name];
|
|
2498
|
+
removed.push(name);
|
|
2499
|
+
}
|
|
2500
|
+
}
|
|
2501
|
+
const hasRemaining = Object.keys(remaining).length > 0;
|
|
2502
|
+
const newDef = {
|
|
2503
|
+
...def,
|
|
2504
|
+
modified: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2505
|
+
...hasRemaining ? { components: remaining } : { components: void 0 }
|
|
2506
|
+
};
|
|
2507
|
+
state.sketches.set(input.sketchId, {
|
|
2508
|
+
definition: newDef,
|
|
2509
|
+
path: loaded.path
|
|
2510
|
+
});
|
|
2511
|
+
const json = serializeGenart4(newDef);
|
|
2512
|
+
if (!state.remoteMode) {
|
|
2513
|
+
await writeFile5(loaded.path, json, "utf-8");
|
|
2514
|
+
}
|
|
2515
|
+
state.emitMutation("sketch:updated", {
|
|
2516
|
+
id: input.sketchId,
|
|
2517
|
+
updated: ["components"]
|
|
2518
|
+
});
|
|
2519
|
+
return {
|
|
2520
|
+
success: true,
|
|
2521
|
+
sketchId: input.sketchId,
|
|
2522
|
+
removed,
|
|
2523
|
+
...warning ? { warning } : {},
|
|
2524
|
+
fileContent: json
|
|
2525
|
+
};
|
|
2526
|
+
}
|
|
2527
|
+
function collectTransitiveDeps(name, deps) {
|
|
2528
|
+
const entry = COMPONENT_REGISTRY[name];
|
|
2529
|
+
if (!entry) return;
|
|
2530
|
+
deps.add(name);
|
|
2531
|
+
for (const dep of entry.dependencies) {
|
|
2532
|
+
if (!deps.has(dep)) {
|
|
2533
|
+
collectTransitiveDeps(dep, deps);
|
|
2534
|
+
}
|
|
2535
|
+
}
|
|
2536
|
+
}
|
|
2537
|
+
function isDirectComponent(name, components) {
|
|
2538
|
+
return name in components;
|
|
2539
|
+
}
|
|
2540
|
+
|
|
2541
|
+
// src/tools/capture.ts
|
|
2542
|
+
import { writeFile as writeFile6 } from "fs/promises";
|
|
2178
2543
|
import {
|
|
2179
2544
|
createDefaultRegistry as createDefaultRegistry2
|
|
2180
2545
|
} from "@genart-dev/core";
|
|
@@ -2352,7 +2717,7 @@ async function buildScreenshotMetadata(state, multi, info) {
|
|
|
2352
2717
|
previewPath: info.previewPath
|
|
2353
2718
|
};
|
|
2354
2719
|
if (!state.remoteMode) {
|
|
2355
|
-
await
|
|
2720
|
+
await writeFile6(info.previewPath, multi.previewPng);
|
|
2356
2721
|
metadata.savedPreviewTo = info.previewPath;
|
|
2357
2722
|
}
|
|
2358
2723
|
return metadata;
|
|
@@ -2413,12 +2778,12 @@ async function captureBatch(state, input) {
|
|
|
2413
2778
|
|
|
2414
2779
|
// src/tools/export.ts
|
|
2415
2780
|
import { createWriteStream } from "fs";
|
|
2416
|
-
import { stat as stat4, writeFile as
|
|
2781
|
+
import { stat as stat4, writeFile as writeFile7 } from "fs/promises";
|
|
2417
2782
|
import { dirname as dirname6 } from "path";
|
|
2418
2783
|
import archiver from "archiver";
|
|
2419
2784
|
import {
|
|
2420
2785
|
createDefaultRegistry as createDefaultRegistry3,
|
|
2421
|
-
serializeGenart as
|
|
2786
|
+
serializeGenart as serializeGenart5
|
|
2422
2787
|
} from "@genart-dev/core";
|
|
2423
2788
|
var registry3 = createDefaultRegistry3();
|
|
2424
2789
|
async function validateOutputPath(outputPath) {
|
|
@@ -2488,7 +2853,7 @@ async function exportHtml(sketch, outputPath) {
|
|
|
2488
2853
|
const adapter = registry3.resolve(sketch.renderer.type);
|
|
2489
2854
|
const html = adapter.generateStandaloneHTML(sketch);
|
|
2490
2855
|
const content = Buffer.from(html, "utf-8");
|
|
2491
|
-
await
|
|
2856
|
+
await writeFile7(outputPath, content);
|
|
2492
2857
|
return {
|
|
2493
2858
|
success: true,
|
|
2494
2859
|
sketchId: sketch.id,
|
|
@@ -2504,7 +2869,7 @@ async function exportPng(sketch, input) {
|
|
|
2504
2869
|
const width = input.width ?? sketch.canvas.width;
|
|
2505
2870
|
const height = input.height ?? sketch.canvas.height;
|
|
2506
2871
|
const result = await captureHtml({ html, width, height });
|
|
2507
|
-
await
|
|
2872
|
+
await writeFile7(input.outputPath, result.bytes);
|
|
2508
2873
|
return {
|
|
2509
2874
|
success: true,
|
|
2510
2875
|
sketchId: sketch.id,
|
|
@@ -2519,7 +2884,7 @@ async function exportSvg(sketch, input) {
|
|
|
2519
2884
|
const height = input.height ?? sketch.canvas.height;
|
|
2520
2885
|
if (sketch.renderer.type === "svg") {
|
|
2521
2886
|
const content2 = Buffer.from(sketch.algorithm, "utf-8");
|
|
2522
|
-
await
|
|
2887
|
+
await writeFile7(input.outputPath, content2);
|
|
2523
2888
|
return {
|
|
2524
2889
|
success: true,
|
|
2525
2890
|
sketchId: sketch.id,
|
|
@@ -2541,7 +2906,7 @@ async function exportSvg(sketch, input) {
|
|
|
2541
2906
|
href="data:image/png;base64,${b64}"/>
|
|
2542
2907
|
</svg>`;
|
|
2543
2908
|
const content = Buffer.from(svg, "utf-8");
|
|
2544
|
-
await
|
|
2909
|
+
await writeFile7(input.outputPath, content);
|
|
2545
2910
|
return {
|
|
2546
2911
|
success: true,
|
|
2547
2912
|
sketchId: sketch.id,
|
|
@@ -2554,7 +2919,7 @@ async function exportSvg(sketch, input) {
|
|
|
2554
2919
|
}
|
|
2555
2920
|
async function exportAlgorithm(sketch, outputPath) {
|
|
2556
2921
|
const content = Buffer.from(sketch.algorithm, "utf-8");
|
|
2557
|
-
await
|
|
2922
|
+
await writeFile7(outputPath, content);
|
|
2558
2923
|
return {
|
|
2559
2924
|
success: true,
|
|
2560
2925
|
sketchId: sketch.id,
|
|
@@ -2569,7 +2934,7 @@ async function exportZip(sketch, input) {
|
|
|
2569
2934
|
const width = input.width ?? sketch.canvas.width;
|
|
2570
2935
|
const height = input.height ?? sketch.canvas.height;
|
|
2571
2936
|
const html = adapter.generateStandaloneHTML(sketch);
|
|
2572
|
-
const genartJson =
|
|
2937
|
+
const genartJson = serializeGenart5(sketch);
|
|
2573
2938
|
const algorithm = sketch.algorithm;
|
|
2574
2939
|
const algExt = algorithmExtension(sketch.renderer.type);
|
|
2575
2940
|
const captureResult = await captureHtml({ html, width, height });
|
|
@@ -2603,6 +2968,330 @@ async function exportZip(sketch, input) {
|
|
|
2603
2968
|
};
|
|
2604
2969
|
}
|
|
2605
2970
|
|
|
2971
|
+
// src/tools/design.ts
|
|
2972
|
+
function requireSketchId(state, args) {
|
|
2973
|
+
return args.sketchId ?? state.requireSelectedSketchId();
|
|
2974
|
+
}
|
|
2975
|
+
var BLEND_MODES = [
|
|
2976
|
+
"normal",
|
|
2977
|
+
"multiply",
|
|
2978
|
+
"screen",
|
|
2979
|
+
"overlay",
|
|
2980
|
+
"darken",
|
|
2981
|
+
"lighten",
|
|
2982
|
+
"color-dodge",
|
|
2983
|
+
"color-burn",
|
|
2984
|
+
"hard-light",
|
|
2985
|
+
"soft-light",
|
|
2986
|
+
"difference",
|
|
2987
|
+
"exclusion",
|
|
2988
|
+
"hue",
|
|
2989
|
+
"saturation",
|
|
2990
|
+
"color",
|
|
2991
|
+
"luminosity"
|
|
2992
|
+
];
|
|
2993
|
+
function generateLayerId() {
|
|
2994
|
+
return `layer-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
2995
|
+
}
|
|
2996
|
+
async function designAddLayer(state, args) {
|
|
2997
|
+
const sketchId = requireSketchId(state, args);
|
|
2998
|
+
const loaded = state.requireSketch(sketchId);
|
|
2999
|
+
const stack = state.getLayerStack(sketchId);
|
|
3000
|
+
const registry4 = state.pluginRegistry;
|
|
3001
|
+
const layerTypeDef = registry4?.resolveLayerType(args.type);
|
|
3002
|
+
if (!layerTypeDef) {
|
|
3003
|
+
throw new Error(
|
|
3004
|
+
`Unknown layer type: '${args.type}'. Use design_list_layers types from registered plugins.`
|
|
3005
|
+
);
|
|
3006
|
+
}
|
|
3007
|
+
const defaults = layerTypeDef.createDefault();
|
|
3008
|
+
const id = generateLayerId();
|
|
3009
|
+
const { width, height } = loaded.definition.canvas;
|
|
3010
|
+
const layer = {
|
|
3011
|
+
id,
|
|
3012
|
+
type: args.type,
|
|
3013
|
+
name: args.name ?? layerTypeDef.displayName,
|
|
3014
|
+
visible: true,
|
|
3015
|
+
locked: false,
|
|
3016
|
+
opacity: args.opacity ?? 1,
|
|
3017
|
+
blendMode: args.blendMode ?? "normal",
|
|
3018
|
+
transform: {
|
|
3019
|
+
x: 0,
|
|
3020
|
+
y: 0,
|
|
3021
|
+
width,
|
|
3022
|
+
height,
|
|
3023
|
+
rotation: 0,
|
|
3024
|
+
scaleX: 1,
|
|
3025
|
+
scaleY: 1,
|
|
3026
|
+
anchorX: 0.5,
|
|
3027
|
+
anchorY: 0.5,
|
|
3028
|
+
...args.transform
|
|
3029
|
+
},
|
|
3030
|
+
properties: { ...defaults, ...args.properties }
|
|
3031
|
+
};
|
|
3032
|
+
stack.add(layer, args.index);
|
|
3033
|
+
await state.saveSketch(sketchId);
|
|
3034
|
+
return {
|
|
3035
|
+
layerId: id,
|
|
3036
|
+
type: args.type,
|
|
3037
|
+
name: layer.name,
|
|
3038
|
+
index: args.index ?? stack.count - 1,
|
|
3039
|
+
sketchId
|
|
3040
|
+
};
|
|
3041
|
+
}
|
|
3042
|
+
async function designRemoveLayer(state, args) {
|
|
3043
|
+
const sketchId = requireSketchId(state, args);
|
|
3044
|
+
const stack = state.getLayerStack(sketchId);
|
|
3045
|
+
const removed = stack.remove(args.layerId);
|
|
3046
|
+
if (!removed) {
|
|
3047
|
+
throw new Error(`Layer '${args.layerId}' not found in sketch '${sketchId}'.`);
|
|
3048
|
+
}
|
|
3049
|
+
await state.saveSketch(sketchId);
|
|
3050
|
+
return { removed: true, layerId: args.layerId, sketchId };
|
|
3051
|
+
}
|
|
3052
|
+
async function designListLayers(state, args) {
|
|
3053
|
+
const sketchId = requireSketchId(state, args);
|
|
3054
|
+
const stack = state.getLayerStack(sketchId);
|
|
3055
|
+
const layers = stack.getAll();
|
|
3056
|
+
return {
|
|
3057
|
+
sketchId,
|
|
3058
|
+
count: layers.length,
|
|
3059
|
+
layers: layers.map((l, i) => ({
|
|
3060
|
+
index: i,
|
|
3061
|
+
id: l.id,
|
|
3062
|
+
type: l.type,
|
|
3063
|
+
name: l.name,
|
|
3064
|
+
visible: l.visible,
|
|
3065
|
+
locked: l.locked,
|
|
3066
|
+
opacity: l.opacity,
|
|
3067
|
+
blendMode: l.blendMode
|
|
3068
|
+
}))
|
|
3069
|
+
};
|
|
3070
|
+
}
|
|
3071
|
+
async function designGetLayer(state, args) {
|
|
3072
|
+
const sketchId = requireSketchId(state, args);
|
|
3073
|
+
const stack = state.getLayerStack(sketchId);
|
|
3074
|
+
const layer = stack.get(args.layerId);
|
|
3075
|
+
if (!layer) {
|
|
3076
|
+
throw new Error(`Layer '${args.layerId}' not found in sketch '${sketchId}'.`);
|
|
3077
|
+
}
|
|
3078
|
+
return {
|
|
3079
|
+
sketchId,
|
|
3080
|
+
layer: {
|
|
3081
|
+
id: layer.id,
|
|
3082
|
+
type: layer.type,
|
|
3083
|
+
name: layer.name,
|
|
3084
|
+
visible: layer.visible,
|
|
3085
|
+
locked: layer.locked,
|
|
3086
|
+
opacity: layer.opacity,
|
|
3087
|
+
blendMode: layer.blendMode,
|
|
3088
|
+
transform: layer.transform,
|
|
3089
|
+
properties: layer.properties
|
|
3090
|
+
}
|
|
3091
|
+
};
|
|
3092
|
+
}
|
|
3093
|
+
async function designUpdateLayer(state, args) {
|
|
3094
|
+
const sketchId = requireSketchId(state, args);
|
|
3095
|
+
const stack = state.getLayerStack(sketchId);
|
|
3096
|
+
const layer = stack.get(args.layerId);
|
|
3097
|
+
if (!layer) {
|
|
3098
|
+
throw new Error(`Layer '${args.layerId}' not found in sketch '${sketchId}'.`);
|
|
3099
|
+
}
|
|
3100
|
+
const updates = {};
|
|
3101
|
+
if (args.properties) {
|
|
3102
|
+
Object.assign(updates, args.properties);
|
|
3103
|
+
}
|
|
3104
|
+
if (Object.keys(updates).length > 0) {
|
|
3105
|
+
stack.updateProperties(args.layerId, updates);
|
|
3106
|
+
}
|
|
3107
|
+
if (args.name !== void 0) {
|
|
3108
|
+
const current = stack.get(args.layerId);
|
|
3109
|
+
stack.updateProperties(args.layerId, { ...current.properties });
|
|
3110
|
+
const mutableLayer = stack.get(args.layerId);
|
|
3111
|
+
mutableLayer.name = args.name;
|
|
3112
|
+
}
|
|
3113
|
+
await state.saveSketch(sketchId);
|
|
3114
|
+
return { updated: true, layerId: args.layerId, sketchId };
|
|
3115
|
+
}
|
|
3116
|
+
async function designSetTransform(state, args) {
|
|
3117
|
+
const sketchId = requireSketchId(state, args);
|
|
3118
|
+
const stack = state.getLayerStack(sketchId);
|
|
3119
|
+
const layer = stack.get(args.layerId);
|
|
3120
|
+
if (!layer) {
|
|
3121
|
+
throw new Error(`Layer '${args.layerId}' not found in sketch '${sketchId}'.`);
|
|
3122
|
+
}
|
|
3123
|
+
const partial = {};
|
|
3124
|
+
if (args.x !== void 0) partial.x = args.x;
|
|
3125
|
+
if (args.y !== void 0) partial.y = args.y;
|
|
3126
|
+
if (args.width !== void 0) partial.width = args.width;
|
|
3127
|
+
if (args.height !== void 0) partial.height = args.height;
|
|
3128
|
+
if (args.rotation !== void 0) partial.rotation = args.rotation;
|
|
3129
|
+
if (args.scaleX !== void 0) partial.scaleX = args.scaleX;
|
|
3130
|
+
if (args.scaleY !== void 0) partial.scaleY = args.scaleY;
|
|
3131
|
+
if (args.anchorX !== void 0) partial.anchorX = args.anchorX;
|
|
3132
|
+
if (args.anchorY !== void 0) partial.anchorY = args.anchorY;
|
|
3133
|
+
stack.updateTransform(args.layerId, partial);
|
|
3134
|
+
await state.saveSketch(sketchId);
|
|
3135
|
+
return {
|
|
3136
|
+
updated: true,
|
|
3137
|
+
layerId: args.layerId,
|
|
3138
|
+
transform: stack.get(args.layerId).transform,
|
|
3139
|
+
sketchId
|
|
3140
|
+
};
|
|
3141
|
+
}
|
|
3142
|
+
async function designSetBlend(state, args) {
|
|
3143
|
+
const sketchId = requireSketchId(state, args);
|
|
3144
|
+
const stack = state.getLayerStack(sketchId);
|
|
3145
|
+
const layer = stack.get(args.layerId);
|
|
3146
|
+
if (!layer) {
|
|
3147
|
+
throw new Error(`Layer '${args.layerId}' not found in sketch '${sketchId}'.`);
|
|
3148
|
+
}
|
|
3149
|
+
if (args.blendMode && !BLEND_MODES.includes(args.blendMode)) {
|
|
3150
|
+
throw new Error(
|
|
3151
|
+
`Invalid blend mode '${args.blendMode}'. Must be one of: ${BLEND_MODES.join(", ")}`
|
|
3152
|
+
);
|
|
3153
|
+
}
|
|
3154
|
+
stack.updateBlend(
|
|
3155
|
+
args.layerId,
|
|
3156
|
+
args.blendMode,
|
|
3157
|
+
args.opacity
|
|
3158
|
+
);
|
|
3159
|
+
await state.saveSketch(sketchId);
|
|
3160
|
+
const updated = stack.get(args.layerId);
|
|
3161
|
+
return {
|
|
3162
|
+
updated: true,
|
|
3163
|
+
layerId: args.layerId,
|
|
3164
|
+
blendMode: updated.blendMode,
|
|
3165
|
+
opacity: updated.opacity,
|
|
3166
|
+
sketchId
|
|
3167
|
+
};
|
|
3168
|
+
}
|
|
3169
|
+
async function designReorderLayers(state, args) {
|
|
3170
|
+
const sketchId = requireSketchId(state, args);
|
|
3171
|
+
const stack = state.getLayerStack(sketchId);
|
|
3172
|
+
const layer = stack.get(args.layerId);
|
|
3173
|
+
if (!layer) {
|
|
3174
|
+
throw new Error(`Layer '${args.layerId}' not found in sketch '${sketchId}'.`);
|
|
3175
|
+
}
|
|
3176
|
+
stack.reorder(args.layerId, args.newIndex);
|
|
3177
|
+
await state.saveSketch(sketchId);
|
|
3178
|
+
return {
|
|
3179
|
+
reordered: true,
|
|
3180
|
+
layerId: args.layerId,
|
|
3181
|
+
newIndex: args.newIndex,
|
|
3182
|
+
sketchId
|
|
3183
|
+
};
|
|
3184
|
+
}
|
|
3185
|
+
async function designDuplicateLayer(state, args) {
|
|
3186
|
+
const sketchId = requireSketchId(state, args);
|
|
3187
|
+
const stack = state.getLayerStack(sketchId);
|
|
3188
|
+
const layer = stack.get(args.layerId);
|
|
3189
|
+
if (!layer) {
|
|
3190
|
+
throw new Error(`Layer '${args.layerId}' not found in sketch '${sketchId}'.`);
|
|
3191
|
+
}
|
|
3192
|
+
const newId = stack.duplicate(args.layerId);
|
|
3193
|
+
await state.saveSketch(sketchId);
|
|
3194
|
+
return {
|
|
3195
|
+
duplicated: true,
|
|
3196
|
+
sourceLayerId: args.layerId,
|
|
3197
|
+
newLayerId: newId,
|
|
3198
|
+
sketchId
|
|
3199
|
+
};
|
|
3200
|
+
}
|
|
3201
|
+
async function designToggleVisibility(state, args) {
|
|
3202
|
+
const sketchId = requireSketchId(state, args);
|
|
3203
|
+
const stack = state.getLayerStack(sketchId);
|
|
3204
|
+
const layer = stack.get(args.layerId);
|
|
3205
|
+
if (!layer) {
|
|
3206
|
+
throw new Error(`Layer '${args.layerId}' not found in sketch '${sketchId}'.`);
|
|
3207
|
+
}
|
|
3208
|
+
const newVisible = args.visible ?? !layer.visible;
|
|
3209
|
+
const mutableLayer = layer;
|
|
3210
|
+
mutableLayer.visible = newVisible;
|
|
3211
|
+
stack.updateProperties(args.layerId, { ...layer.properties });
|
|
3212
|
+
await state.saveSketch(sketchId);
|
|
3213
|
+
return {
|
|
3214
|
+
layerId: args.layerId,
|
|
3215
|
+
visible: newVisible,
|
|
3216
|
+
sketchId
|
|
3217
|
+
};
|
|
3218
|
+
}
|
|
3219
|
+
async function designLockLayer(state, args) {
|
|
3220
|
+
const sketchId = requireSketchId(state, args);
|
|
3221
|
+
const stack = state.getLayerStack(sketchId);
|
|
3222
|
+
const layer = stack.get(args.layerId);
|
|
3223
|
+
if (!layer) {
|
|
3224
|
+
throw new Error(`Layer '${args.layerId}' not found in sketch '${sketchId}'.`);
|
|
3225
|
+
}
|
|
3226
|
+
const newLocked = args.locked ?? !layer.locked;
|
|
3227
|
+
const mutableLayer = layer;
|
|
3228
|
+
mutableLayer.locked = newLocked;
|
|
3229
|
+
stack.updateProperties(args.layerId, { ...layer.properties });
|
|
3230
|
+
await state.saveSketch(sketchId);
|
|
3231
|
+
return {
|
|
3232
|
+
layerId: args.layerId,
|
|
3233
|
+
locked: newLocked,
|
|
3234
|
+
sketchId
|
|
3235
|
+
};
|
|
3236
|
+
}
|
|
3237
|
+
async function designCaptureComposite(state, args) {
|
|
3238
|
+
const sketchId = requireSketchId(state, args);
|
|
3239
|
+
const stack = state.getLayerStack(sketchId);
|
|
3240
|
+
const layers = stack.getAll();
|
|
3241
|
+
return {
|
|
3242
|
+
sketchId,
|
|
3243
|
+
layerCount: layers.length,
|
|
3244
|
+
visibleCount: layers.filter((l) => l.visible).length,
|
|
3245
|
+
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."
|
|
3246
|
+
};
|
|
3247
|
+
}
|
|
3248
|
+
|
|
3249
|
+
// src/tools/design-plugins.ts
|
|
3250
|
+
function registerPluginMcpTools(server, registry4, state) {
|
|
3251
|
+
for (const tool of registry4.getMcpTools()) {
|
|
3252
|
+
const inputSchema = tool.definition.inputSchema;
|
|
3253
|
+
server.tool(
|
|
3254
|
+
tool.name,
|
|
3255
|
+
tool.definition.description,
|
|
3256
|
+
// Pass raw JSON schema — MCP SDK accepts this alongside Zod
|
|
3257
|
+
inputSchema,
|
|
3258
|
+
async (args) => {
|
|
3259
|
+
try {
|
|
3260
|
+
const sketchId = args.sketchId ?? state.requireSelectedSketchId();
|
|
3261
|
+
const context = state.createMcpToolContext(sketchId);
|
|
3262
|
+
const result = await tool.definition.handler(args, context);
|
|
3263
|
+
await state.saveSketch(sketchId);
|
|
3264
|
+
return {
|
|
3265
|
+
content: result.content.map((c) => {
|
|
3266
|
+
if (c.type === "text") {
|
|
3267
|
+
return { type: "text", text: c.text };
|
|
3268
|
+
}
|
|
3269
|
+
return {
|
|
3270
|
+
type: "image",
|
|
3271
|
+
data: c.data,
|
|
3272
|
+
mimeType: c.mimeType
|
|
3273
|
+
};
|
|
3274
|
+
}),
|
|
3275
|
+
isError: result.isError
|
|
3276
|
+
};
|
|
3277
|
+
} catch (e) {
|
|
3278
|
+
return {
|
|
3279
|
+
content: [
|
|
3280
|
+
{
|
|
3281
|
+
type: "text",
|
|
3282
|
+
text: JSON.stringify({
|
|
3283
|
+
error: e instanceof Error ? e.message : String(e)
|
|
3284
|
+
})
|
|
3285
|
+
}
|
|
3286
|
+
],
|
|
3287
|
+
isError: true
|
|
3288
|
+
};
|
|
3289
|
+
}
|
|
3290
|
+
}
|
|
3291
|
+
);
|
|
3292
|
+
}
|
|
3293
|
+
}
|
|
3294
|
+
|
|
2606
3295
|
// src/resources/index.ts
|
|
2607
3296
|
import {
|
|
2608
3297
|
CANVAS_PRESETS,
|
|
@@ -3090,11 +3779,23 @@ function toolError(message) {
|
|
|
3090
3779
|
isError: true
|
|
3091
3780
|
};
|
|
3092
3781
|
}
|
|
3782
|
+
async function initializePluginRegistry() {
|
|
3783
|
+
const registry4 = createPluginRegistry({
|
|
3784
|
+
surface: "mcp",
|
|
3785
|
+
supportsInteractiveTools: false,
|
|
3786
|
+
supportsRendering: false
|
|
3787
|
+
});
|
|
3788
|
+
await registry4.register(typographyPlugin);
|
|
3789
|
+
await registry4.register(filtersPlugin);
|
|
3790
|
+
await registry4.register(shapesPlugin);
|
|
3791
|
+
await registry4.register(layoutGuidesPlugin);
|
|
3792
|
+
return registry4;
|
|
3793
|
+
}
|
|
3093
3794
|
function createServer(state) {
|
|
3094
3795
|
const server = new McpServer(
|
|
3095
3796
|
{
|
|
3096
3797
|
name: "@genart/mcp-server",
|
|
3097
|
-
version: "0.0
|
|
3798
|
+
version: "0.3.0"
|
|
3098
3799
|
},
|
|
3099
3800
|
{
|
|
3100
3801
|
capabilities: {
|
|
@@ -3104,8 +3805,14 @@ function createServer(state) {
|
|
|
3104
3805
|
}
|
|
3105
3806
|
}
|
|
3106
3807
|
);
|
|
3808
|
+
const registryReady = initializePluginRegistry().then((registry4) => {
|
|
3809
|
+
state.pluginRegistry = registry4;
|
|
3810
|
+
registerPluginMcpTools(server, registry4, state);
|
|
3811
|
+
});
|
|
3812
|
+
server._pluginsReady = registryReady;
|
|
3107
3813
|
registerWorkspaceTools(server, state);
|
|
3108
3814
|
registerSketchTools(server, state);
|
|
3815
|
+
registerComponentTools(server, state);
|
|
3109
3816
|
registerSelectionTools(server, state);
|
|
3110
3817
|
registerParameterTools(server, state);
|
|
3111
3818
|
registerArrangementTools(server, state);
|
|
@@ -3113,6 +3820,7 @@ function createServer(state) {
|
|
|
3113
3820
|
registerMergeTools(server, state);
|
|
3114
3821
|
registerSnapshotTools(server, state);
|
|
3115
3822
|
registerKnowledgeTools(server, state);
|
|
3823
|
+
registerDesignTools(server, state);
|
|
3116
3824
|
registerCaptureTools(server, state);
|
|
3117
3825
|
registerExportTools(server, state);
|
|
3118
3826
|
registerResources(server, state);
|
|
@@ -3209,7 +3917,7 @@ function registerWorkspaceTools(server, state) {
|
|
|
3209
3917
|
function registerSketchTools(server, state) {
|
|
3210
3918
|
server.tool(
|
|
3211
3919
|
"create_sketch",
|
|
3212
|
-
|
|
3920
|
+
'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.',
|
|
3213
3921
|
{
|
|
3214
3922
|
id: z2.string().describe("URL-safe kebab-case identifier"),
|
|
3215
3923
|
title: z2.string().describe("Human-readable title"),
|
|
@@ -3247,6 +3955,16 @@ function registerSketchTools(server, state) {
|
|
|
3247
3955
|
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)."),
|
|
3248
3956
|
seed: z2.number().optional().describe("Initial random seed (default: random)"),
|
|
3249
3957
|
skills: z2.array(z2.string()).optional().describe("Design skill references"),
|
|
3958
|
+
components: z2.record(
|
|
3959
|
+
z2.union([
|
|
3960
|
+
z2.string(),
|
|
3961
|
+
z2.object({
|
|
3962
|
+
version: z2.string().optional(),
|
|
3963
|
+
code: z2.string().optional(),
|
|
3964
|
+
exports: z2.array(z2.string()).optional()
|
|
3965
|
+
})
|
|
3966
|
+
])
|
|
3967
|
+
).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.'),
|
|
3250
3968
|
addToWorkspace: z2.string().optional().describe("Path to workspace to add sketch to after creation"),
|
|
3251
3969
|
agent: z2.string().optional().describe("Your CLI agent name (e.g. 'claude-code', 'codex-cli', 'gemini-cli', 'opencode', 'kiro')"),
|
|
3252
3970
|
model: z2.string().optional().describe("Your AI model identifier (e.g. 'claude-opus-4-6', 'gpt-4o', 'gemini-2.5-pro')")
|
|
@@ -3326,11 +4044,21 @@ function registerSketchTools(server, state) {
|
|
|
3326
4044
|
);
|
|
3327
4045
|
server.tool(
|
|
3328
4046
|
"update_algorithm",
|
|
3329
|
-
"Replace the algorithm source code of a sketch",
|
|
4047
|
+
"Replace the algorithm source code of a sketch. If adding/changing components, pass them in the components field alongside the algorithm.",
|
|
3330
4048
|
{
|
|
3331
4049
|
sketchId: z2.string().describe("ID of the sketch to update"),
|
|
3332
4050
|
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)."),
|
|
3333
4051
|
validate: z2.boolean().optional().describe("Run renderer-specific validation before saving (default: true)"),
|
|
4052
|
+
components: z2.record(
|
|
4053
|
+
z2.union([
|
|
4054
|
+
z2.string(),
|
|
4055
|
+
z2.object({
|
|
4056
|
+
version: z2.string().optional(),
|
|
4057
|
+
code: z2.string().optional(),
|
|
4058
|
+
exports: z2.array(z2.string()).optional()
|
|
4059
|
+
})
|
|
4060
|
+
])
|
|
4061
|
+
).optional().describe("Component dependencies to resolve alongside the algorithm update. Use list_components to see available."),
|
|
3334
4062
|
agent: z2.string().optional().describe("Your CLI agent name (e.g. 'claude-code', 'codex-cli', 'gemini-cli', 'opencode', 'kiro')"),
|
|
3335
4063
|
model: z2.string().optional().describe("Your AI model identifier (e.g. 'claude-opus-4-6', 'gpt-4o', 'gemini-2.5-pro')")
|
|
3336
4064
|
},
|
|
@@ -3426,6 +4154,76 @@ function registerSketchTools(server, state) {
|
|
|
3426
4154
|
}
|
|
3427
4155
|
);
|
|
3428
4156
|
}
|
|
4157
|
+
function registerComponentTools(server, state) {
|
|
4158
|
+
server.tool(
|
|
4159
|
+
"list_components",
|
|
4160
|
+
"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.",
|
|
4161
|
+
{
|
|
4162
|
+
renderer: z2.enum(["p5", "three", "glsl", "canvas2d", "svg"]).optional().describe("Filter by renderer compatibility"),
|
|
4163
|
+
category: z2.enum([
|
|
4164
|
+
"randomness",
|
|
4165
|
+
"noise",
|
|
4166
|
+
"math",
|
|
4167
|
+
"easing",
|
|
4168
|
+
"color",
|
|
4169
|
+
"vector",
|
|
4170
|
+
"geometry",
|
|
4171
|
+
"grid",
|
|
4172
|
+
"particle",
|
|
4173
|
+
"physics",
|
|
4174
|
+
"distribution",
|
|
4175
|
+
"pattern",
|
|
4176
|
+
"sdf",
|
|
4177
|
+
"transform",
|
|
4178
|
+
"animation",
|
|
4179
|
+
"string",
|
|
4180
|
+
"data-structure",
|
|
4181
|
+
"imaging"
|
|
4182
|
+
]).optional().describe("Filter by component category")
|
|
4183
|
+
},
|
|
4184
|
+
async (args) => {
|
|
4185
|
+
try {
|
|
4186
|
+
const result = await listComponents(state, args);
|
|
4187
|
+
return jsonResult(result);
|
|
4188
|
+
} catch (e) {
|
|
4189
|
+
return toolError(e instanceof Error ? e.message : String(e));
|
|
4190
|
+
}
|
|
4191
|
+
}
|
|
4192
|
+
);
|
|
4193
|
+
server.tool(
|
|
4194
|
+
"add_component",
|
|
4195
|
+
"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.",
|
|
4196
|
+
{
|
|
4197
|
+
sketchId: z2.string().describe("ID of the sketch to add the component to"),
|
|
4198
|
+
component: z2.string().describe("Component name (e.g. 'prng', 'noise-2d', 'glsl-noise')"),
|
|
4199
|
+
version: z2.string().optional().describe("Version range (default: '^1.0.0')")
|
|
4200
|
+
},
|
|
4201
|
+
async (args) => {
|
|
4202
|
+
try {
|
|
4203
|
+
const result = await addComponent(state, args);
|
|
4204
|
+
return jsonResult(result);
|
|
4205
|
+
} catch (e) {
|
|
4206
|
+
return toolError(e instanceof Error ? e.message : String(e));
|
|
4207
|
+
}
|
|
4208
|
+
}
|
|
4209
|
+
);
|
|
4210
|
+
server.tool(
|
|
4211
|
+
"remove_component",
|
|
4212
|
+
"Remove a component dependency from a sketch. Checks for dependent components and warns if the algorithm references the component's exports.",
|
|
4213
|
+
{
|
|
4214
|
+
sketchId: z2.string().describe("ID of the sketch to remove the component from"),
|
|
4215
|
+
component: z2.string().describe("Component name to remove")
|
|
4216
|
+
},
|
|
4217
|
+
async (args) => {
|
|
4218
|
+
try {
|
|
4219
|
+
const result = await removeComponent(state, args);
|
|
4220
|
+
return jsonResult(result);
|
|
4221
|
+
} catch (e) {
|
|
4222
|
+
return toolError(e instanceof Error ? e.message : String(e));
|
|
4223
|
+
}
|
|
4224
|
+
}
|
|
4225
|
+
);
|
|
4226
|
+
}
|
|
3429
4227
|
function registerSelectionTools(server, state) {
|
|
3430
4228
|
server.tool(
|
|
3431
4229
|
"get_selection",
|
|
@@ -3811,6 +4609,247 @@ function registerExportTools(server, state) {
|
|
|
3811
4609
|
}
|
|
3812
4610
|
);
|
|
3813
4611
|
}
|
|
4612
|
+
function registerDesignTools(server, state) {
|
|
4613
|
+
server.tool(
|
|
4614
|
+
"design_add_layer",
|
|
4615
|
+
"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').",
|
|
4616
|
+
{
|
|
4617
|
+
sketchId: z2.string().optional().describe("Target sketch ID (default: selected sketch)"),
|
|
4618
|
+
type: z2.string().describe("Layer type ID (e.g. 'typography:text', 'filter:grain', 'shapes:rect')"),
|
|
4619
|
+
name: z2.string().optional().describe("Layer display name (default: type's display name)"),
|
|
4620
|
+
properties: z2.record(z2.unknown()).optional().describe("Initial layer properties (merged with type defaults)"),
|
|
4621
|
+
transform: z2.object({
|
|
4622
|
+
x: z2.number().optional(),
|
|
4623
|
+
y: z2.number().optional(),
|
|
4624
|
+
width: z2.number().optional(),
|
|
4625
|
+
height: z2.number().optional(),
|
|
4626
|
+
rotation: z2.number().optional(),
|
|
4627
|
+
scaleX: z2.number().optional(),
|
|
4628
|
+
scaleY: z2.number().optional(),
|
|
4629
|
+
anchorX: z2.number().optional(),
|
|
4630
|
+
anchorY: z2.number().optional()
|
|
4631
|
+
}).optional().describe("Layer transform (default: full canvas)"),
|
|
4632
|
+
opacity: z2.number().optional().describe("Layer opacity 0\u20131 (default: 1)"),
|
|
4633
|
+
blendMode: z2.string().optional().describe("Blend mode (default: 'normal')"),
|
|
4634
|
+
index: z2.number().optional().describe("Insert position in layer stack (default: top)")
|
|
4635
|
+
},
|
|
4636
|
+
async (args) => {
|
|
4637
|
+
try {
|
|
4638
|
+
const result = await designAddLayer(state, args);
|
|
4639
|
+
return jsonResult(result);
|
|
4640
|
+
} catch (e) {
|
|
4641
|
+
return toolError(e instanceof Error ? e.message : String(e));
|
|
4642
|
+
}
|
|
4643
|
+
}
|
|
4644
|
+
);
|
|
4645
|
+
server.tool(
|
|
4646
|
+
"design_remove_layer",
|
|
4647
|
+
"Remove a design layer from the active sketch",
|
|
4648
|
+
{
|
|
4649
|
+
sketchId: z2.string().optional().describe("Target sketch ID (default: selected sketch)"),
|
|
4650
|
+
layerId: z2.string().describe("ID of the layer to remove")
|
|
4651
|
+
},
|
|
4652
|
+
async (args) => {
|
|
4653
|
+
try {
|
|
4654
|
+
const result = await designRemoveLayer(state, args);
|
|
4655
|
+
return jsonResult(result);
|
|
4656
|
+
} catch (e) {
|
|
4657
|
+
return toolError(e instanceof Error ? e.message : String(e));
|
|
4658
|
+
}
|
|
4659
|
+
}
|
|
4660
|
+
);
|
|
4661
|
+
server.tool(
|
|
4662
|
+
"design_list_layers",
|
|
4663
|
+
"List all design layers in the active sketch with their types, visibility, and key properties",
|
|
4664
|
+
{
|
|
4665
|
+
sketchId: z2.string().optional().describe("Target sketch ID (default: selected sketch)")
|
|
4666
|
+
},
|
|
4667
|
+
async (args) => {
|
|
4668
|
+
try {
|
|
4669
|
+
const result = await designListLayers(state, args);
|
|
4670
|
+
return jsonResult(result);
|
|
4671
|
+
} catch (e) {
|
|
4672
|
+
return toolError(e instanceof Error ? e.message : String(e));
|
|
4673
|
+
}
|
|
4674
|
+
}
|
|
4675
|
+
);
|
|
4676
|
+
server.tool(
|
|
4677
|
+
"design_get_layer",
|
|
4678
|
+
"Get full details of a single design layer including all properties and transform",
|
|
4679
|
+
{
|
|
4680
|
+
sketchId: z2.string().optional().describe("Target sketch ID (default: selected sketch)"),
|
|
4681
|
+
layerId: z2.string().describe("ID of the layer to inspect")
|
|
4682
|
+
},
|
|
4683
|
+
async (args) => {
|
|
4684
|
+
try {
|
|
4685
|
+
const result = await designGetLayer(state, args);
|
|
4686
|
+
return jsonResult(result);
|
|
4687
|
+
} catch (e) {
|
|
4688
|
+
return toolError(e instanceof Error ? e.message : String(e));
|
|
4689
|
+
}
|
|
4690
|
+
}
|
|
4691
|
+
);
|
|
4692
|
+
server.tool(
|
|
4693
|
+
"design_update_layer",
|
|
4694
|
+
"Update properties on a design layer (e.g. text content, filter intensity, shape fill color)",
|
|
4695
|
+
{
|
|
4696
|
+
sketchId: z2.string().optional().describe("Target sketch ID (default: selected sketch)"),
|
|
4697
|
+
layerId: z2.string().describe("ID of the layer to update"),
|
|
4698
|
+
name: z2.string().optional().describe("New display name"),
|
|
4699
|
+
properties: z2.record(z2.unknown()).optional().describe("Property key-value pairs to set")
|
|
4700
|
+
},
|
|
4701
|
+
async (args) => {
|
|
4702
|
+
try {
|
|
4703
|
+
const result = await designUpdateLayer(state, args);
|
|
4704
|
+
return jsonResult(result);
|
|
4705
|
+
} catch (e) {
|
|
4706
|
+
return toolError(e instanceof Error ? e.message : String(e));
|
|
4707
|
+
}
|
|
4708
|
+
}
|
|
4709
|
+
);
|
|
4710
|
+
server.tool(
|
|
4711
|
+
"design_set_transform",
|
|
4712
|
+
"Set the position, size, rotation, and scale of a design layer",
|
|
4713
|
+
{
|
|
4714
|
+
sketchId: z2.string().optional().describe("Target sketch ID (default: selected sketch)"),
|
|
4715
|
+
layerId: z2.string().describe("ID of the layer to transform"),
|
|
4716
|
+
x: z2.number().optional().describe("X position"),
|
|
4717
|
+
y: z2.number().optional().describe("Y position"),
|
|
4718
|
+
width: z2.number().optional().describe("Width"),
|
|
4719
|
+
height: z2.number().optional().describe("Height"),
|
|
4720
|
+
rotation: z2.number().optional().describe("Rotation in degrees"),
|
|
4721
|
+
scaleX: z2.number().optional().describe("Horizontal scale"),
|
|
4722
|
+
scaleY: z2.number().optional().describe("Vertical scale"),
|
|
4723
|
+
anchorX: z2.number().optional().describe("Anchor X (0\u20131)"),
|
|
4724
|
+
anchorY: z2.number().optional().describe("Anchor Y (0\u20131)")
|
|
4725
|
+
},
|
|
4726
|
+
async (args) => {
|
|
4727
|
+
try {
|
|
4728
|
+
const result = await designSetTransform(state, args);
|
|
4729
|
+
return jsonResult(result);
|
|
4730
|
+
} catch (e) {
|
|
4731
|
+
return toolError(e instanceof Error ? e.message : String(e));
|
|
4732
|
+
}
|
|
4733
|
+
}
|
|
4734
|
+
);
|
|
4735
|
+
server.tool(
|
|
4736
|
+
"design_set_blend",
|
|
4737
|
+
"Set blend mode and/or opacity on a design layer",
|
|
4738
|
+
{
|
|
4739
|
+
sketchId: z2.string().optional().describe("Target sketch ID (default: selected sketch)"),
|
|
4740
|
+
layerId: z2.string().describe("ID of the layer"),
|
|
4741
|
+
blendMode: z2.enum([
|
|
4742
|
+
"normal",
|
|
4743
|
+
"multiply",
|
|
4744
|
+
"screen",
|
|
4745
|
+
"overlay",
|
|
4746
|
+
"darken",
|
|
4747
|
+
"lighten",
|
|
4748
|
+
"color-dodge",
|
|
4749
|
+
"color-burn",
|
|
4750
|
+
"hard-light",
|
|
4751
|
+
"soft-light",
|
|
4752
|
+
"difference",
|
|
4753
|
+
"exclusion",
|
|
4754
|
+
"hue",
|
|
4755
|
+
"saturation",
|
|
4756
|
+
"color",
|
|
4757
|
+
"luminosity"
|
|
4758
|
+
]).optional().describe("CSS blend mode"),
|
|
4759
|
+
opacity: z2.number().optional().describe("Layer opacity 0\u20131")
|
|
4760
|
+
},
|
|
4761
|
+
async (args) => {
|
|
4762
|
+
try {
|
|
4763
|
+
const result = await designSetBlend(state, args);
|
|
4764
|
+
return jsonResult(result);
|
|
4765
|
+
} catch (e) {
|
|
4766
|
+
return toolError(e instanceof Error ? e.message : String(e));
|
|
4767
|
+
}
|
|
4768
|
+
}
|
|
4769
|
+
);
|
|
4770
|
+
server.tool(
|
|
4771
|
+
"design_reorder_layers",
|
|
4772
|
+
"Move a design layer to a new position in the z-order stack",
|
|
4773
|
+
{
|
|
4774
|
+
sketchId: z2.string().optional().describe("Target sketch ID (default: selected sketch)"),
|
|
4775
|
+
layerId: z2.string().describe("ID of the layer to move"),
|
|
4776
|
+
newIndex: z2.number().describe("New position (0 = bottom, n-1 = top)")
|
|
4777
|
+
},
|
|
4778
|
+
async (args) => {
|
|
4779
|
+
try {
|
|
4780
|
+
const result = await designReorderLayers(state, args);
|
|
4781
|
+
return jsonResult(result);
|
|
4782
|
+
} catch (e) {
|
|
4783
|
+
return toolError(e instanceof Error ? e.message : String(e));
|
|
4784
|
+
}
|
|
4785
|
+
}
|
|
4786
|
+
);
|
|
4787
|
+
server.tool(
|
|
4788
|
+
"design_duplicate_layer",
|
|
4789
|
+
"Clone a design layer with a new ID, inserted directly above the source",
|
|
4790
|
+
{
|
|
4791
|
+
sketchId: z2.string().optional().describe("Target sketch ID (default: selected sketch)"),
|
|
4792
|
+
layerId: z2.string().describe("ID of the layer to duplicate")
|
|
4793
|
+
},
|
|
4794
|
+
async (args) => {
|
|
4795
|
+
try {
|
|
4796
|
+
const result = await designDuplicateLayer(state, args);
|
|
4797
|
+
return jsonResult(result);
|
|
4798
|
+
} catch (e) {
|
|
4799
|
+
return toolError(e instanceof Error ? e.message : String(e));
|
|
4800
|
+
}
|
|
4801
|
+
}
|
|
4802
|
+
);
|
|
4803
|
+
server.tool(
|
|
4804
|
+
"design_toggle_visibility",
|
|
4805
|
+
"Show or hide a design layer",
|
|
4806
|
+
{
|
|
4807
|
+
sketchId: z2.string().optional().describe("Target sketch ID (default: selected sketch)"),
|
|
4808
|
+
layerId: z2.string().describe("ID of the layer"),
|
|
4809
|
+
visible: z2.boolean().optional().describe("Set visibility (default: toggle)")
|
|
4810
|
+
},
|
|
4811
|
+
async (args) => {
|
|
4812
|
+
try {
|
|
4813
|
+
const result = await designToggleVisibility(state, args);
|
|
4814
|
+
return jsonResult(result);
|
|
4815
|
+
} catch (e) {
|
|
4816
|
+
return toolError(e instanceof Error ? e.message : String(e));
|
|
4817
|
+
}
|
|
4818
|
+
}
|
|
4819
|
+
);
|
|
4820
|
+
server.tool(
|
|
4821
|
+
"design_lock_layer",
|
|
4822
|
+
"Lock or unlock a design layer to prevent accidental edits",
|
|
4823
|
+
{
|
|
4824
|
+
sketchId: z2.string().optional().describe("Target sketch ID (default: selected sketch)"),
|
|
4825
|
+
layerId: z2.string().describe("ID of the layer"),
|
|
4826
|
+
locked: z2.boolean().optional().describe("Set lock state (default: toggle)")
|
|
4827
|
+
},
|
|
4828
|
+
async (args) => {
|
|
4829
|
+
try {
|
|
4830
|
+
const result = await designLockLayer(state, args);
|
|
4831
|
+
return jsonResult(result);
|
|
4832
|
+
} catch (e) {
|
|
4833
|
+
return toolError(e instanceof Error ? e.message : String(e));
|
|
4834
|
+
}
|
|
4835
|
+
}
|
|
4836
|
+
);
|
|
4837
|
+
server.tool(
|
|
4838
|
+
"design_capture_composite",
|
|
4839
|
+
"Get info about the design layer composite for a sketch. For full visual capture use capture_screenshot.",
|
|
4840
|
+
{
|
|
4841
|
+
sketchId: z2.string().optional().describe("Target sketch ID (default: selected sketch)")
|
|
4842
|
+
},
|
|
4843
|
+
async (args) => {
|
|
4844
|
+
try {
|
|
4845
|
+
const result = await designCaptureComposite(state, args);
|
|
4846
|
+
return jsonResult(result);
|
|
4847
|
+
} catch (e) {
|
|
4848
|
+
return toolError(e instanceof Error ? e.message : String(e));
|
|
4849
|
+
}
|
|
4850
|
+
}
|
|
4851
|
+
);
|
|
4852
|
+
}
|
|
3814
4853
|
function registerKnowledgeTools(server, _state) {
|
|
3815
4854
|
server.tool(
|
|
3816
4855
|
"list_skills",
|