@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 CHANGED
@@ -67,6 +67,10 @@ var EditorState = class extends import_events.EventEmitter {
67
67
  * instead of writing to disk. Set by mcp-host for HTTP-based sessions.
68
68
  */
69
69
  remoteMode = false;
70
+ /** Plugin registry for design mode. Set during server initialization. */
71
+ pluginRegistry = null;
72
+ /** Layer stacks keyed by sketch ID. Created lazily when design tools are used. */
73
+ layerStacks = /* @__PURE__ */ new Map();
70
74
  constructor(options) {
71
75
  super();
72
76
  if (options?.basePath) {
@@ -131,6 +135,7 @@ var EditorState = class extends import_events.EventEmitter {
131
135
  this.workspace = ws;
132
136
  this.sketches.clear();
133
137
  this.selection.clear();
138
+ this.layerStacks.clear();
134
139
  for (const ref of ws.sketches) {
135
140
  const sketchPath = this.resolveSketchPath(ref.file);
136
141
  await this.loadSketch(sketchPath);
@@ -172,6 +177,7 @@ var EditorState = class extends import_events.EventEmitter {
172
177
  removeSketch(id) {
173
178
  this.sketches.delete(id);
174
179
  this.selection.delete(id);
180
+ this.layerStacks.delete(id);
175
181
  this.emitMutation("sketch:removed", { id });
176
182
  }
177
183
  /** Save the active workspace to disk. */
@@ -215,6 +221,81 @@ var EditorState = class extends import_events.EventEmitter {
215
221
  selection: Array.from(this.selection)
216
222
  };
217
223
  }
224
+ /**
225
+ * Get or create a LayerStackAccessor for a sketch.
226
+ * Initializes from the sketch's persisted design layers.
227
+ */
228
+ getLayerStack(sketchId) {
229
+ let stack = this.layerStacks.get(sketchId);
230
+ if (stack) return stack;
231
+ const loaded = this.requireSketch(sketchId);
232
+ const initialLayers = loaded.definition.layers ?? [];
233
+ stack = (0, import_core.createLayerStack)(initialLayers, (changeType) => {
234
+ this.syncLayersToDefinition(sketchId);
235
+ const mutationType = `design:${changeType}`;
236
+ this.emitMutation(mutationType, { sketchId, changeType });
237
+ });
238
+ this.layerStacks.set(sketchId, stack);
239
+ return stack;
240
+ }
241
+ /**
242
+ * Sync the layer stack's current state back to the sketch definition.
243
+ * Called automatically on every layer mutation.
244
+ */
245
+ syncLayersToDefinition(sketchId) {
246
+ const loaded = this.sketches.get(sketchId);
247
+ const stack = this.layerStacks.get(sketchId);
248
+ if (!loaded || !stack) return;
249
+ const layers = stack.getAll();
250
+ loaded.definition = {
251
+ ...loaded.definition,
252
+ layers: layers.length > 0 ? layers : void 0
253
+ };
254
+ }
255
+ /**
256
+ * Create an McpToolContext for a plugin's MCP tool handler.
257
+ * Provides access to the layer stack, sketch state, and change notifications.
258
+ */
259
+ createMcpToolContext(sketchId) {
260
+ const loaded = this.requireSketch(sketchId);
261
+ const layerStack = this.getLayerStack(sketchId);
262
+ const def = loaded.definition;
263
+ const sketchState = {
264
+ seed: def.state.seed,
265
+ params: def.state.params,
266
+ colorPalette: def.state.colorPalette,
267
+ canvasWidth: def.canvas.width,
268
+ canvasHeight: def.canvas.height,
269
+ rendererId: def.renderer.type
270
+ };
271
+ return {
272
+ layers: layerStack,
273
+ sketchState,
274
+ canvasWidth: def.canvas.width,
275
+ canvasHeight: def.canvas.height,
276
+ async resolveAsset(_assetId) {
277
+ return null;
278
+ },
279
+ async captureComposite(_format) {
280
+ throw new Error("captureComposite is not available in headless MCP mode");
281
+ },
282
+ emitChange(_changeType) {
283
+ }
284
+ };
285
+ }
286
+ /**
287
+ * Get the currently selected sketch ID for design operations.
288
+ * Returns the single selected sketch, or throws if none/multiple selected.
289
+ */
290
+ requireSelectedSketchId() {
291
+ if (this.selection.size === 0) {
292
+ throw new Error("No sketch is selected. Use select_sketch or open_sketch first.");
293
+ }
294
+ if (this.selection.size > 1) {
295
+ throw new Error("Multiple sketches are selected. Design operations require a single sketch.");
296
+ }
297
+ return this.selection.values().next().value;
298
+ }
218
299
  /** Emit a mutation event for external listeners (WebSocket broadcast, sidecar IPC). */
219
300
  emitMutation(type, payload) {
220
301
  this.emit("mutation", { type, payload });
@@ -225,6 +306,11 @@ var EditorState = class extends import_events.EventEmitter {
225
306
  // src/server.ts
226
307
  var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
227
308
  var import_zod2 = require("zod");
309
+ var import_core12 = require("@genart-dev/core");
310
+ var import_plugin_typography = __toESM(require("@genart-dev/plugin-typography"), 1);
311
+ var import_plugin_filters = __toESM(require("@genart-dev/plugin-filters"), 1);
312
+ var import_plugin_shapes = __toESM(require("@genart-dev/plugin-shapes"), 1);
313
+ var import_plugin_layout_guides = __toESM(require("@genart-dev/plugin-layout-guides"), 1);
228
314
 
229
315
  // src/tools/workspace.ts
230
316
  var import_promises3 = require("fs/promises");
@@ -657,10 +743,40 @@ async function createSketch(state, input) {
657
743
  const adapter = registry4.resolve(rendererType);
658
744
  algorithm = adapter.getAlgorithmTemplate();
659
745
  }
746
+ let resolvedComponents;
747
+ if (input.components && Object.keys(input.components).length > 0) {
748
+ const shorthand = {};
749
+ for (const [name, value] of Object.entries(input.components)) {
750
+ if (typeof value === "string") {
751
+ shorthand[name] = value;
752
+ } else if (value.version) {
753
+ shorthand[name] = value.version;
754
+ } else if (value.code) {
755
+ if (!resolvedComponents) resolvedComponents = {};
756
+ resolvedComponents[name] = {
757
+ ...value.version ? { version: value.version } : {},
758
+ code: value.code,
759
+ ...value.exports ? { exports: value.exports } : {}
760
+ };
761
+ }
762
+ }
763
+ if (Object.keys(shorthand).length > 0) {
764
+ const resolved = (0, import_core3.resolveComponents)(shorthand, rendererType);
765
+ if (!resolvedComponents) resolvedComponents = {};
766
+ for (const rc of resolved) {
767
+ resolvedComponents[rc.name] = {
768
+ version: rc.version,
769
+ code: rc.code,
770
+ exports: [...rc.exports]
771
+ };
772
+ }
773
+ }
774
+ }
660
775
  const seed = input.seed ?? Math.floor(Math.random() * 1e5);
661
776
  const ts = now2();
777
+ const hasComponents = resolvedComponents && Object.keys(resolvedComponents).length > 0;
662
778
  const sketch = {
663
- genart: "1.1",
779
+ genart: hasComponents ? "1.2" : "1.1",
664
780
  id: input.id,
665
781
  title: input.title,
666
782
  created: ts,
@@ -674,6 +790,7 @@ async function createSketch(state, input) {
674
790
  ...input.philosophy ? { philosophy: input.philosophy } : {},
675
791
  ...input.themes && input.themes.length > 0 ? { themes: input.themes } : {},
676
792
  ...input.skills && input.skills.length > 0 ? { skills: input.skills } : {},
793
+ ...hasComponents ? { components: resolvedComponents } : {},
677
794
  ...input.agent ? { agent: input.agent } : {},
678
795
  ...input.model ? { model: input.model } : {}
679
796
  };
@@ -857,10 +974,44 @@ async function updateAlgorithm(state, input) {
857
974
  );
858
975
  }
859
976
  }
977
+ let resolvedComponents;
978
+ if (input.components && Object.keys(input.components).length > 0) {
979
+ const renderer = def.renderer.type;
980
+ const shorthand = {};
981
+ for (const [name, value] of Object.entries(input.components)) {
982
+ if (typeof value === "string") {
983
+ shorthand[name] = value;
984
+ } else if (value.version) {
985
+ shorthand[name] = value.version;
986
+ } else if (value.code) {
987
+ if (!resolvedComponents) resolvedComponents = {};
988
+ resolvedComponents[name] = {
989
+ ...value.version ? { version: value.version } : {},
990
+ code: value.code,
991
+ ...value.exports ? { exports: value.exports } : {}
992
+ };
993
+ }
994
+ }
995
+ if (Object.keys(shorthand).length > 0) {
996
+ const resolved = (0, import_core3.resolveComponents)(shorthand, renderer);
997
+ if (!resolvedComponents) resolvedComponents = {};
998
+ for (const rc of resolved) {
999
+ resolvedComponents[rc.name] = {
1000
+ version: rc.version,
1001
+ code: rc.code,
1002
+ exports: [...rc.exports]
1003
+ };
1004
+ }
1005
+ }
1006
+ }
1007
+ const updated = ["algorithm"];
1008
+ const hasNewComponents = resolvedComponents && Object.keys(resolvedComponents).length > 0;
1009
+ if (hasNewComponents) updated.push("components");
860
1010
  const newDef = {
861
1011
  ...def,
862
1012
  modified: now2(),
863
1013
  algorithm: input.algorithm,
1014
+ ...hasNewComponents ? { genart: "1.2", components: resolvedComponents } : {},
864
1015
  ...input.agent ? { agent: input.agent } : {},
865
1016
  ...input.model ? { model: input.model } : {}
866
1017
  };
@@ -874,7 +1025,7 @@ async function updateAlgorithm(state, input) {
874
1025
  }
875
1026
  state.emitMutation("sketch:updated", {
876
1027
  id: input.sketchId,
877
- updated: ["algorithm"]
1028
+ updated
878
1029
  });
879
1030
  return {
880
1031
  success: true,
@@ -882,6 +1033,7 @@ async function updateAlgorithm(state, input) {
882
1033
  renderer: def.renderer.type,
883
1034
  algorithmLength: input.algorithm.length,
884
1035
  validationPassed,
1036
+ ...hasNewComponents ? { componentsUpdated: true } : {},
885
1037
  fileContent: json
886
1038
  };
887
1039
  }
@@ -2179,9 +2331,216 @@ ${guidelines}`,
2179
2331
  };
2180
2332
  }
2181
2333
 
2182
- // src/tools/capture.ts
2334
+ // src/tools/components.ts
2183
2335
  var import_promises7 = require("fs/promises");
2184
2336
  var import_core8 = require("@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(import_core8.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 = import_core8.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 = (0, import_core8.resolveComponents)(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 = (0, import_core8.serializeGenart)(newDef);
2447
+ if (!state.remoteMode) {
2448
+ await (0, import_promises7.writeFile)(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 = import_core8.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 exports2 = typeof removedValue === "string" ? import_core8.COMPONENT_REGISTRY[input.component]?.exports ?? [] : removedValue.exports ?? [];
2483
+ const usedExports = exports2.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 = import_core8.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 = (0, import_core8.serializeGenart)(newDef);
2512
+ if (!state.remoteMode) {
2513
+ await (0, import_promises7.writeFile)(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 = import_core8.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
+ var import_promises8 = require("fs/promises");
2543
+ var import_core9 = require("@genart-dev/core");
2185
2544
 
2186
2545
  // src/capture/headless.ts
2187
2546
  var cachedModule = null;
@@ -2275,7 +2634,7 @@ async function captureHtmlMulti(options) {
2275
2634
  }
2276
2635
 
2277
2636
  // src/tools/capture.ts
2278
- var registry2 = (0, import_core8.createDefaultRegistry)();
2637
+ var registry2 = (0, import_core9.createDefaultRegistry)();
2279
2638
  function applyOverrides(sketch, overrides) {
2280
2639
  if (overrides.seed === void 0 && overrides.params === void 0) {
2281
2640
  return sketch;
@@ -2356,7 +2715,7 @@ async function buildScreenshotMetadata(state, multi, info) {
2356
2715
  previewPath: info.previewPath
2357
2716
  };
2358
2717
  if (!state.remoteMode) {
2359
- await (0, import_promises7.writeFile)(info.previewPath, multi.previewPng);
2718
+ await (0, import_promises8.writeFile)(info.previewPath, multi.previewPng);
2360
2719
  metadata.savedPreviewTo = info.previewPath;
2361
2720
  }
2362
2721
  return metadata;
@@ -2417,15 +2776,15 @@ async function captureBatch(state, input) {
2417
2776
 
2418
2777
  // src/tools/export.ts
2419
2778
  var import_fs = require("fs");
2420
- var import_promises8 = require("fs/promises");
2779
+ var import_promises9 = require("fs/promises");
2421
2780
  var import_path9 = require("path");
2422
2781
  var import_archiver = __toESM(require("archiver"), 1);
2423
- var import_core9 = require("@genart-dev/core");
2424
- var registry3 = (0, import_core9.createDefaultRegistry)();
2782
+ var import_core10 = require("@genart-dev/core");
2783
+ var registry3 = (0, import_core10.createDefaultRegistry)();
2425
2784
  async function validateOutputPath(outputPath) {
2426
2785
  const parentDir = (0, import_path9.dirname)(outputPath);
2427
2786
  try {
2428
- const s = await (0, import_promises8.stat)(parentDir);
2787
+ const s = await (0, import_promises9.stat)(parentDir);
2429
2788
  if (!s.isDirectory()) {
2430
2789
  throw new Error(`Parent directory does not exist: ${parentDir}`);
2431
2790
  }
@@ -2434,7 +2793,7 @@ async function validateOutputPath(outputPath) {
2434
2793
  throw new Error(`Parent directory does not exist: ${parentDir}`);
2435
2794
  }
2436
2795
  try {
2437
- await (0, import_promises8.stat)(outputPath);
2796
+ await (0, import_promises9.stat)(outputPath);
2438
2797
  throw new Error(
2439
2798
  `File already exists at ${outputPath}. Delete it first or use a different path.`
2440
2799
  );
@@ -2489,7 +2848,7 @@ async function exportHtml(sketch, outputPath) {
2489
2848
  const adapter = registry3.resolve(sketch.renderer.type);
2490
2849
  const html = adapter.generateStandaloneHTML(sketch);
2491
2850
  const content = Buffer.from(html, "utf-8");
2492
- await (0, import_promises8.writeFile)(outputPath, content);
2851
+ await (0, import_promises9.writeFile)(outputPath, content);
2493
2852
  return {
2494
2853
  success: true,
2495
2854
  sketchId: sketch.id,
@@ -2505,7 +2864,7 @@ async function exportPng(sketch, input) {
2505
2864
  const width = input.width ?? sketch.canvas.width;
2506
2865
  const height = input.height ?? sketch.canvas.height;
2507
2866
  const result = await captureHtml({ html, width, height });
2508
- await (0, import_promises8.writeFile)(input.outputPath, result.bytes);
2867
+ await (0, import_promises9.writeFile)(input.outputPath, result.bytes);
2509
2868
  return {
2510
2869
  success: true,
2511
2870
  sketchId: sketch.id,
@@ -2520,7 +2879,7 @@ async function exportSvg(sketch, input) {
2520
2879
  const height = input.height ?? sketch.canvas.height;
2521
2880
  if (sketch.renderer.type === "svg") {
2522
2881
  const content2 = Buffer.from(sketch.algorithm, "utf-8");
2523
- await (0, import_promises8.writeFile)(input.outputPath, content2);
2882
+ await (0, import_promises9.writeFile)(input.outputPath, content2);
2524
2883
  return {
2525
2884
  success: true,
2526
2885
  sketchId: sketch.id,
@@ -2542,7 +2901,7 @@ async function exportSvg(sketch, input) {
2542
2901
  href="data:image/png;base64,${b64}"/>
2543
2902
  </svg>`;
2544
2903
  const content = Buffer.from(svg, "utf-8");
2545
- await (0, import_promises8.writeFile)(input.outputPath, content);
2904
+ await (0, import_promises9.writeFile)(input.outputPath, content);
2546
2905
  return {
2547
2906
  success: true,
2548
2907
  sketchId: sketch.id,
@@ -2555,7 +2914,7 @@ async function exportSvg(sketch, input) {
2555
2914
  }
2556
2915
  async function exportAlgorithm(sketch, outputPath) {
2557
2916
  const content = Buffer.from(sketch.algorithm, "utf-8");
2558
- await (0, import_promises8.writeFile)(outputPath, content);
2917
+ await (0, import_promises9.writeFile)(outputPath, content);
2559
2918
  return {
2560
2919
  success: true,
2561
2920
  sketchId: sketch.id,
@@ -2570,7 +2929,7 @@ async function exportZip(sketch, input) {
2570
2929
  const width = input.width ?? sketch.canvas.width;
2571
2930
  const height = input.height ?? sketch.canvas.height;
2572
2931
  const html = adapter.generateStandaloneHTML(sketch);
2573
- const genartJson = (0, import_core9.serializeGenart)(sketch);
2932
+ const genartJson = (0, import_core10.serializeGenart)(sketch);
2574
2933
  const algorithm = sketch.algorithm;
2575
2934
  const algExt = algorithmExtension(sketch.renderer.type);
2576
2935
  const captureResult = await captureHtml({ html, width, height });
@@ -2587,7 +2946,7 @@ async function exportZip(sketch, input) {
2587
2946
  archive.append(genartJson, { name: `${sketch.id}.genart` });
2588
2947
  await archive.finalize();
2589
2948
  await finished;
2590
- const s = await (0, import_promises8.stat)(input.outputPath);
2949
+ const s = await (0, import_promises9.stat)(input.outputPath);
2591
2950
  return {
2592
2951
  success: true,
2593
2952
  sketchId: sketch.id,
@@ -2604,8 +2963,332 @@ async function exportZip(sketch, input) {
2604
2963
  };
2605
2964
  }
2606
2965
 
2966
+ // src/tools/design.ts
2967
+ function requireSketchId(state, args) {
2968
+ return args.sketchId ?? state.requireSelectedSketchId();
2969
+ }
2970
+ var BLEND_MODES = [
2971
+ "normal",
2972
+ "multiply",
2973
+ "screen",
2974
+ "overlay",
2975
+ "darken",
2976
+ "lighten",
2977
+ "color-dodge",
2978
+ "color-burn",
2979
+ "hard-light",
2980
+ "soft-light",
2981
+ "difference",
2982
+ "exclusion",
2983
+ "hue",
2984
+ "saturation",
2985
+ "color",
2986
+ "luminosity"
2987
+ ];
2988
+ function generateLayerId() {
2989
+ return `layer-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
2990
+ }
2991
+ async function designAddLayer(state, args) {
2992
+ const sketchId = requireSketchId(state, args);
2993
+ const loaded = state.requireSketch(sketchId);
2994
+ const stack = state.getLayerStack(sketchId);
2995
+ const registry4 = state.pluginRegistry;
2996
+ const layerTypeDef = registry4?.resolveLayerType(args.type);
2997
+ if (!layerTypeDef) {
2998
+ throw new Error(
2999
+ `Unknown layer type: '${args.type}'. Use design_list_layers types from registered plugins.`
3000
+ );
3001
+ }
3002
+ const defaults = layerTypeDef.createDefault();
3003
+ const id = generateLayerId();
3004
+ const { width, height } = loaded.definition.canvas;
3005
+ const layer = {
3006
+ id,
3007
+ type: args.type,
3008
+ name: args.name ?? layerTypeDef.displayName,
3009
+ visible: true,
3010
+ locked: false,
3011
+ opacity: args.opacity ?? 1,
3012
+ blendMode: args.blendMode ?? "normal",
3013
+ transform: {
3014
+ x: 0,
3015
+ y: 0,
3016
+ width,
3017
+ height,
3018
+ rotation: 0,
3019
+ scaleX: 1,
3020
+ scaleY: 1,
3021
+ anchorX: 0.5,
3022
+ anchorY: 0.5,
3023
+ ...args.transform
3024
+ },
3025
+ properties: { ...defaults, ...args.properties }
3026
+ };
3027
+ stack.add(layer, args.index);
3028
+ await state.saveSketch(sketchId);
3029
+ return {
3030
+ layerId: id,
3031
+ type: args.type,
3032
+ name: layer.name,
3033
+ index: args.index ?? stack.count - 1,
3034
+ sketchId
3035
+ };
3036
+ }
3037
+ async function designRemoveLayer(state, args) {
3038
+ const sketchId = requireSketchId(state, args);
3039
+ const stack = state.getLayerStack(sketchId);
3040
+ const removed = stack.remove(args.layerId);
3041
+ if (!removed) {
3042
+ throw new Error(`Layer '${args.layerId}' not found in sketch '${sketchId}'.`);
3043
+ }
3044
+ await state.saveSketch(sketchId);
3045
+ return { removed: true, layerId: args.layerId, sketchId };
3046
+ }
3047
+ async function designListLayers(state, args) {
3048
+ const sketchId = requireSketchId(state, args);
3049
+ const stack = state.getLayerStack(sketchId);
3050
+ const layers = stack.getAll();
3051
+ return {
3052
+ sketchId,
3053
+ count: layers.length,
3054
+ layers: layers.map((l, i) => ({
3055
+ index: i,
3056
+ id: l.id,
3057
+ type: l.type,
3058
+ name: l.name,
3059
+ visible: l.visible,
3060
+ locked: l.locked,
3061
+ opacity: l.opacity,
3062
+ blendMode: l.blendMode
3063
+ }))
3064
+ };
3065
+ }
3066
+ async function designGetLayer(state, args) {
3067
+ const sketchId = requireSketchId(state, args);
3068
+ const stack = state.getLayerStack(sketchId);
3069
+ const layer = stack.get(args.layerId);
3070
+ if (!layer) {
3071
+ throw new Error(`Layer '${args.layerId}' not found in sketch '${sketchId}'.`);
3072
+ }
3073
+ return {
3074
+ sketchId,
3075
+ layer: {
3076
+ id: layer.id,
3077
+ type: layer.type,
3078
+ name: layer.name,
3079
+ visible: layer.visible,
3080
+ locked: layer.locked,
3081
+ opacity: layer.opacity,
3082
+ blendMode: layer.blendMode,
3083
+ transform: layer.transform,
3084
+ properties: layer.properties
3085
+ }
3086
+ };
3087
+ }
3088
+ async function designUpdateLayer(state, args) {
3089
+ const sketchId = requireSketchId(state, args);
3090
+ const stack = state.getLayerStack(sketchId);
3091
+ const layer = stack.get(args.layerId);
3092
+ if (!layer) {
3093
+ throw new Error(`Layer '${args.layerId}' not found in sketch '${sketchId}'.`);
3094
+ }
3095
+ const updates = {};
3096
+ if (args.properties) {
3097
+ Object.assign(updates, args.properties);
3098
+ }
3099
+ if (Object.keys(updates).length > 0) {
3100
+ stack.updateProperties(args.layerId, updates);
3101
+ }
3102
+ if (args.name !== void 0) {
3103
+ const current = stack.get(args.layerId);
3104
+ stack.updateProperties(args.layerId, { ...current.properties });
3105
+ const mutableLayer = stack.get(args.layerId);
3106
+ mutableLayer.name = args.name;
3107
+ }
3108
+ await state.saveSketch(sketchId);
3109
+ return { updated: true, layerId: args.layerId, sketchId };
3110
+ }
3111
+ async function designSetTransform(state, args) {
3112
+ const sketchId = requireSketchId(state, args);
3113
+ const stack = state.getLayerStack(sketchId);
3114
+ const layer = stack.get(args.layerId);
3115
+ if (!layer) {
3116
+ throw new Error(`Layer '${args.layerId}' not found in sketch '${sketchId}'.`);
3117
+ }
3118
+ const partial = {};
3119
+ if (args.x !== void 0) partial.x = args.x;
3120
+ if (args.y !== void 0) partial.y = args.y;
3121
+ if (args.width !== void 0) partial.width = args.width;
3122
+ if (args.height !== void 0) partial.height = args.height;
3123
+ if (args.rotation !== void 0) partial.rotation = args.rotation;
3124
+ if (args.scaleX !== void 0) partial.scaleX = args.scaleX;
3125
+ if (args.scaleY !== void 0) partial.scaleY = args.scaleY;
3126
+ if (args.anchorX !== void 0) partial.anchorX = args.anchorX;
3127
+ if (args.anchorY !== void 0) partial.anchorY = args.anchorY;
3128
+ stack.updateTransform(args.layerId, partial);
3129
+ await state.saveSketch(sketchId);
3130
+ return {
3131
+ updated: true,
3132
+ layerId: args.layerId,
3133
+ transform: stack.get(args.layerId).transform,
3134
+ sketchId
3135
+ };
3136
+ }
3137
+ async function designSetBlend(state, args) {
3138
+ const sketchId = requireSketchId(state, args);
3139
+ const stack = state.getLayerStack(sketchId);
3140
+ const layer = stack.get(args.layerId);
3141
+ if (!layer) {
3142
+ throw new Error(`Layer '${args.layerId}' not found in sketch '${sketchId}'.`);
3143
+ }
3144
+ if (args.blendMode && !BLEND_MODES.includes(args.blendMode)) {
3145
+ throw new Error(
3146
+ `Invalid blend mode '${args.blendMode}'. Must be one of: ${BLEND_MODES.join(", ")}`
3147
+ );
3148
+ }
3149
+ stack.updateBlend(
3150
+ args.layerId,
3151
+ args.blendMode,
3152
+ args.opacity
3153
+ );
3154
+ await state.saveSketch(sketchId);
3155
+ const updated = stack.get(args.layerId);
3156
+ return {
3157
+ updated: true,
3158
+ layerId: args.layerId,
3159
+ blendMode: updated.blendMode,
3160
+ opacity: updated.opacity,
3161
+ sketchId
3162
+ };
3163
+ }
3164
+ async function designReorderLayers(state, args) {
3165
+ const sketchId = requireSketchId(state, args);
3166
+ const stack = state.getLayerStack(sketchId);
3167
+ const layer = stack.get(args.layerId);
3168
+ if (!layer) {
3169
+ throw new Error(`Layer '${args.layerId}' not found in sketch '${sketchId}'.`);
3170
+ }
3171
+ stack.reorder(args.layerId, args.newIndex);
3172
+ await state.saveSketch(sketchId);
3173
+ return {
3174
+ reordered: true,
3175
+ layerId: args.layerId,
3176
+ newIndex: args.newIndex,
3177
+ sketchId
3178
+ };
3179
+ }
3180
+ async function designDuplicateLayer(state, args) {
3181
+ const sketchId = requireSketchId(state, args);
3182
+ const stack = state.getLayerStack(sketchId);
3183
+ const layer = stack.get(args.layerId);
3184
+ if (!layer) {
3185
+ throw new Error(`Layer '${args.layerId}' not found in sketch '${sketchId}'.`);
3186
+ }
3187
+ const newId = stack.duplicate(args.layerId);
3188
+ await state.saveSketch(sketchId);
3189
+ return {
3190
+ duplicated: true,
3191
+ sourceLayerId: args.layerId,
3192
+ newLayerId: newId,
3193
+ sketchId
3194
+ };
3195
+ }
3196
+ async function designToggleVisibility(state, args) {
3197
+ const sketchId = requireSketchId(state, args);
3198
+ const stack = state.getLayerStack(sketchId);
3199
+ const layer = stack.get(args.layerId);
3200
+ if (!layer) {
3201
+ throw new Error(`Layer '${args.layerId}' not found in sketch '${sketchId}'.`);
3202
+ }
3203
+ const newVisible = args.visible ?? !layer.visible;
3204
+ const mutableLayer = layer;
3205
+ mutableLayer.visible = newVisible;
3206
+ stack.updateProperties(args.layerId, { ...layer.properties });
3207
+ await state.saveSketch(sketchId);
3208
+ return {
3209
+ layerId: args.layerId,
3210
+ visible: newVisible,
3211
+ sketchId
3212
+ };
3213
+ }
3214
+ async function designLockLayer(state, args) {
3215
+ const sketchId = requireSketchId(state, args);
3216
+ const stack = state.getLayerStack(sketchId);
3217
+ const layer = stack.get(args.layerId);
3218
+ if (!layer) {
3219
+ throw new Error(`Layer '${args.layerId}' not found in sketch '${sketchId}'.`);
3220
+ }
3221
+ const newLocked = args.locked ?? !layer.locked;
3222
+ const mutableLayer = layer;
3223
+ mutableLayer.locked = newLocked;
3224
+ stack.updateProperties(args.layerId, { ...layer.properties });
3225
+ await state.saveSketch(sketchId);
3226
+ return {
3227
+ layerId: args.layerId,
3228
+ locked: newLocked,
3229
+ sketchId
3230
+ };
3231
+ }
3232
+ async function designCaptureComposite(state, args) {
3233
+ const sketchId = requireSketchId(state, args);
3234
+ const stack = state.getLayerStack(sketchId);
3235
+ const layers = stack.getAll();
3236
+ return {
3237
+ sketchId,
3238
+ layerCount: layers.length,
3239
+ visibleCount: layers.filter((l) => l.visible).length,
3240
+ 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."
3241
+ };
3242
+ }
3243
+
3244
+ // src/tools/design-plugins.ts
3245
+ function registerPluginMcpTools(server, registry4, state) {
3246
+ for (const tool of registry4.getMcpTools()) {
3247
+ const inputSchema = tool.definition.inputSchema;
3248
+ server.tool(
3249
+ tool.name,
3250
+ tool.definition.description,
3251
+ // Pass raw JSON schema — MCP SDK accepts this alongside Zod
3252
+ inputSchema,
3253
+ async (args) => {
3254
+ try {
3255
+ const sketchId = args.sketchId ?? state.requireSelectedSketchId();
3256
+ const context = state.createMcpToolContext(sketchId);
3257
+ const result = await tool.definition.handler(args, context);
3258
+ await state.saveSketch(sketchId);
3259
+ return {
3260
+ content: result.content.map((c) => {
3261
+ if (c.type === "text") {
3262
+ return { type: "text", text: c.text };
3263
+ }
3264
+ return {
3265
+ type: "image",
3266
+ data: c.data,
3267
+ mimeType: c.mimeType
3268
+ };
3269
+ }),
3270
+ isError: result.isError
3271
+ };
3272
+ } catch (e) {
3273
+ return {
3274
+ content: [
3275
+ {
3276
+ type: "text",
3277
+ text: JSON.stringify({
3278
+ error: e instanceof Error ? e.message : String(e)
3279
+ })
3280
+ }
3281
+ ],
3282
+ isError: true
3283
+ };
3284
+ }
3285
+ }
3286
+ );
3287
+ }
3288
+ }
3289
+
2607
3290
  // src/resources/index.ts
2608
- var import_core10 = require("@genart-dev/core");
3291
+ var import_core11 = require("@genart-dev/core");
2609
3292
  function registerResources(server, state) {
2610
3293
  registerSkillsResource(server);
2611
3294
  registerCanvasPresetsResource(server);
@@ -2613,7 +3296,7 @@ function registerResources(server, state) {
2613
3296
  registerRenderersResource(server);
2614
3297
  }
2615
3298
  function registerSkillsResource(server) {
2616
- const skillRegistry = (0, import_core10.createDefaultSkillRegistry)();
3299
+ const skillRegistry = (0, import_core11.createDefaultSkillRegistry)();
2617
3300
  server.resource(
2618
3301
  "skills",
2619
3302
  "genart://skills",
@@ -2664,7 +3347,7 @@ function registerCanvasPresetsResource(server) {
2664
3347
  mimeType: "application/json",
2665
3348
  text: JSON.stringify(
2666
3349
  {
2667
- presets: import_core10.CANVAS_PRESETS.map((p) => ({
3350
+ presets: import_core11.CANVAS_PRESETS.map((p) => ({
2668
3351
  id: p.id,
2669
3352
  label: p.label,
2670
3353
  category: p.category,
@@ -2721,7 +3404,7 @@ function registerGalleryResource(server, state) {
2721
3404
  );
2722
3405
  }
2723
3406
  function registerRenderersResource(server) {
2724
- const registry4 = (0, import_core10.createDefaultRegistry)();
3407
+ const registry4 = (0, import_core11.createDefaultRegistry)();
2725
3408
  server.resource(
2726
3409
  "renderers",
2727
3410
  "genart://renderers",
@@ -3087,11 +3770,23 @@ function toolError(message) {
3087
3770
  isError: true
3088
3771
  };
3089
3772
  }
3773
+ async function initializePluginRegistry() {
3774
+ const registry4 = (0, import_core12.createPluginRegistry)({
3775
+ surface: "mcp",
3776
+ supportsInteractiveTools: false,
3777
+ supportsRendering: false
3778
+ });
3779
+ await registry4.register(import_plugin_typography.default);
3780
+ await registry4.register(import_plugin_filters.default);
3781
+ await registry4.register(import_plugin_shapes.default);
3782
+ await registry4.register(import_plugin_layout_guides.default);
3783
+ return registry4;
3784
+ }
3090
3785
  function createServer(state) {
3091
3786
  const server = new import_mcp.McpServer(
3092
3787
  {
3093
3788
  name: "@genart/mcp-server",
3094
- version: "0.0.1"
3789
+ version: "0.3.0"
3095
3790
  },
3096
3791
  {
3097
3792
  capabilities: {
@@ -3101,8 +3796,14 @@ function createServer(state) {
3101
3796
  }
3102
3797
  }
3103
3798
  );
3799
+ const registryReady = initializePluginRegistry().then((registry4) => {
3800
+ state.pluginRegistry = registry4;
3801
+ registerPluginMcpTools(server, registry4, state);
3802
+ });
3803
+ server._pluginsReady = registryReady;
3104
3804
  registerWorkspaceTools(server, state);
3105
3805
  registerSketchTools(server, state);
3806
+ registerComponentTools(server, state);
3106
3807
  registerSelectionTools(server, state);
3107
3808
  registerParameterTools(server, state);
3108
3809
  registerArrangementTools(server, state);
@@ -3110,6 +3811,7 @@ function createServer(state) {
3110
3811
  registerMergeTools(server, state);
3111
3812
  registerSnapshotTools(server, state);
3112
3813
  registerKnowledgeTools(server, state);
3814
+ registerDesignTools(server, state);
3113
3815
  registerCaptureTools(server, state);
3114
3816
  registerExportTools(server, state);
3115
3817
  registerResources(server, state);
@@ -3206,7 +3908,7 @@ function registerWorkspaceTools(server, state) {
3206
3908
  function registerSketchTools(server, state) {
3207
3909
  server.tool(
3208
3910
  "create_sketch",
3209
- "Create a new .genart sketch file from metadata, parameters, and algorithm",
3911
+ '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.',
3210
3912
  {
3211
3913
  id: import_zod2.z.string().describe("URL-safe kebab-case identifier"),
3212
3914
  title: import_zod2.z.string().describe("Human-readable title"),
@@ -3244,6 +3946,16 @@ function registerSketchTools(server, state) {
3244
3946
  algorithm: import_zod2.z.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)."),
3245
3947
  seed: import_zod2.z.number().optional().describe("Initial random seed (default: random)"),
3246
3948
  skills: import_zod2.z.array(import_zod2.z.string()).optional().describe("Design skill references"),
3949
+ components: import_zod2.z.record(
3950
+ import_zod2.z.union([
3951
+ import_zod2.z.string(),
3952
+ import_zod2.z.object({
3953
+ version: import_zod2.z.string().optional(),
3954
+ code: import_zod2.z.string().optional(),
3955
+ exports: import_zod2.z.array(import_zod2.z.string()).optional()
3956
+ })
3957
+ ])
3958
+ ).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.'),
3247
3959
  addToWorkspace: import_zod2.z.string().optional().describe("Path to workspace to add sketch to after creation"),
3248
3960
  agent: import_zod2.z.string().optional().describe("Your CLI agent name (e.g. 'claude-code', 'codex-cli', 'gemini-cli', 'opencode', 'kiro')"),
3249
3961
  model: import_zod2.z.string().optional().describe("Your AI model identifier (e.g. 'claude-opus-4-6', 'gpt-4o', 'gemini-2.5-pro')")
@@ -3323,11 +4035,21 @@ function registerSketchTools(server, state) {
3323
4035
  );
3324
4036
  server.tool(
3325
4037
  "update_algorithm",
3326
- "Replace the algorithm source code of a sketch",
4038
+ "Replace the algorithm source code of a sketch. If adding/changing components, pass them in the components field alongside the algorithm.",
3327
4039
  {
3328
4040
  sketchId: import_zod2.z.string().describe("ID of the sketch to update"),
3329
4041
  algorithm: import_zod2.z.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)."),
3330
4042
  validate: import_zod2.z.boolean().optional().describe("Run renderer-specific validation before saving (default: true)"),
4043
+ components: import_zod2.z.record(
4044
+ import_zod2.z.union([
4045
+ import_zod2.z.string(),
4046
+ import_zod2.z.object({
4047
+ version: import_zod2.z.string().optional(),
4048
+ code: import_zod2.z.string().optional(),
4049
+ exports: import_zod2.z.array(import_zod2.z.string()).optional()
4050
+ })
4051
+ ])
4052
+ ).optional().describe("Component dependencies to resolve alongside the algorithm update. Use list_components to see available."),
3331
4053
  agent: import_zod2.z.string().optional().describe("Your CLI agent name (e.g. 'claude-code', 'codex-cli', 'gemini-cli', 'opencode', 'kiro')"),
3332
4054
  model: import_zod2.z.string().optional().describe("Your AI model identifier (e.g. 'claude-opus-4-6', 'gpt-4o', 'gemini-2.5-pro')")
3333
4055
  },
@@ -3423,6 +4145,76 @@ function registerSketchTools(server, state) {
3423
4145
  }
3424
4146
  );
3425
4147
  }
4148
+ function registerComponentTools(server, state) {
4149
+ server.tool(
4150
+ "list_components",
4151
+ "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.",
4152
+ {
4153
+ renderer: import_zod2.z.enum(["p5", "three", "glsl", "canvas2d", "svg"]).optional().describe("Filter by renderer compatibility"),
4154
+ category: import_zod2.z.enum([
4155
+ "randomness",
4156
+ "noise",
4157
+ "math",
4158
+ "easing",
4159
+ "color",
4160
+ "vector",
4161
+ "geometry",
4162
+ "grid",
4163
+ "particle",
4164
+ "physics",
4165
+ "distribution",
4166
+ "pattern",
4167
+ "sdf",
4168
+ "transform",
4169
+ "animation",
4170
+ "string",
4171
+ "data-structure",
4172
+ "imaging"
4173
+ ]).optional().describe("Filter by component category")
4174
+ },
4175
+ async (args) => {
4176
+ try {
4177
+ const result = await listComponents(state, args);
4178
+ return jsonResult(result);
4179
+ } catch (e) {
4180
+ return toolError(e instanceof Error ? e.message : String(e));
4181
+ }
4182
+ }
4183
+ );
4184
+ server.tool(
4185
+ "add_component",
4186
+ "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.",
4187
+ {
4188
+ sketchId: import_zod2.z.string().describe("ID of the sketch to add the component to"),
4189
+ component: import_zod2.z.string().describe("Component name (e.g. 'prng', 'noise-2d', 'glsl-noise')"),
4190
+ version: import_zod2.z.string().optional().describe("Version range (default: '^1.0.0')")
4191
+ },
4192
+ async (args) => {
4193
+ try {
4194
+ const result = await addComponent(state, args);
4195
+ return jsonResult(result);
4196
+ } catch (e) {
4197
+ return toolError(e instanceof Error ? e.message : String(e));
4198
+ }
4199
+ }
4200
+ );
4201
+ server.tool(
4202
+ "remove_component",
4203
+ "Remove a component dependency from a sketch. Checks for dependent components and warns if the algorithm references the component's exports.",
4204
+ {
4205
+ sketchId: import_zod2.z.string().describe("ID of the sketch to remove the component from"),
4206
+ component: import_zod2.z.string().describe("Component name to remove")
4207
+ },
4208
+ async (args) => {
4209
+ try {
4210
+ const result = await removeComponent(state, args);
4211
+ return jsonResult(result);
4212
+ } catch (e) {
4213
+ return toolError(e instanceof Error ? e.message : String(e));
4214
+ }
4215
+ }
4216
+ );
4217
+ }
3426
4218
  function registerSelectionTools(server, state) {
3427
4219
  server.tool(
3428
4220
  "get_selection",
@@ -3808,6 +4600,247 @@ function registerExportTools(server, state) {
3808
4600
  }
3809
4601
  );
3810
4602
  }
4603
+ function registerDesignTools(server, state) {
4604
+ server.tool(
4605
+ "design_add_layer",
4606
+ "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').",
4607
+ {
4608
+ sketchId: import_zod2.z.string().optional().describe("Target sketch ID (default: selected sketch)"),
4609
+ type: import_zod2.z.string().describe("Layer type ID (e.g. 'typography:text', 'filter:grain', 'shapes:rect')"),
4610
+ name: import_zod2.z.string().optional().describe("Layer display name (default: type's display name)"),
4611
+ properties: import_zod2.z.record(import_zod2.z.unknown()).optional().describe("Initial layer properties (merged with type defaults)"),
4612
+ transform: import_zod2.z.object({
4613
+ x: import_zod2.z.number().optional(),
4614
+ y: import_zod2.z.number().optional(),
4615
+ width: import_zod2.z.number().optional(),
4616
+ height: import_zod2.z.number().optional(),
4617
+ rotation: import_zod2.z.number().optional(),
4618
+ scaleX: import_zod2.z.number().optional(),
4619
+ scaleY: import_zod2.z.number().optional(),
4620
+ anchorX: import_zod2.z.number().optional(),
4621
+ anchorY: import_zod2.z.number().optional()
4622
+ }).optional().describe("Layer transform (default: full canvas)"),
4623
+ opacity: import_zod2.z.number().optional().describe("Layer opacity 0\u20131 (default: 1)"),
4624
+ blendMode: import_zod2.z.string().optional().describe("Blend mode (default: 'normal')"),
4625
+ index: import_zod2.z.number().optional().describe("Insert position in layer stack (default: top)")
4626
+ },
4627
+ async (args) => {
4628
+ try {
4629
+ const result = await designAddLayer(state, args);
4630
+ return jsonResult(result);
4631
+ } catch (e) {
4632
+ return toolError(e instanceof Error ? e.message : String(e));
4633
+ }
4634
+ }
4635
+ );
4636
+ server.tool(
4637
+ "design_remove_layer",
4638
+ "Remove a design layer from the active sketch",
4639
+ {
4640
+ sketchId: import_zod2.z.string().optional().describe("Target sketch ID (default: selected sketch)"),
4641
+ layerId: import_zod2.z.string().describe("ID of the layer to remove")
4642
+ },
4643
+ async (args) => {
4644
+ try {
4645
+ const result = await designRemoveLayer(state, args);
4646
+ return jsonResult(result);
4647
+ } catch (e) {
4648
+ return toolError(e instanceof Error ? e.message : String(e));
4649
+ }
4650
+ }
4651
+ );
4652
+ server.tool(
4653
+ "design_list_layers",
4654
+ "List all design layers in the active sketch with their types, visibility, and key properties",
4655
+ {
4656
+ sketchId: import_zod2.z.string().optional().describe("Target sketch ID (default: selected sketch)")
4657
+ },
4658
+ async (args) => {
4659
+ try {
4660
+ const result = await designListLayers(state, args);
4661
+ return jsonResult(result);
4662
+ } catch (e) {
4663
+ return toolError(e instanceof Error ? e.message : String(e));
4664
+ }
4665
+ }
4666
+ );
4667
+ server.tool(
4668
+ "design_get_layer",
4669
+ "Get full details of a single design layer including all properties and transform",
4670
+ {
4671
+ sketchId: import_zod2.z.string().optional().describe("Target sketch ID (default: selected sketch)"),
4672
+ layerId: import_zod2.z.string().describe("ID of the layer to inspect")
4673
+ },
4674
+ async (args) => {
4675
+ try {
4676
+ const result = await designGetLayer(state, args);
4677
+ return jsonResult(result);
4678
+ } catch (e) {
4679
+ return toolError(e instanceof Error ? e.message : String(e));
4680
+ }
4681
+ }
4682
+ );
4683
+ server.tool(
4684
+ "design_update_layer",
4685
+ "Update properties on a design layer (e.g. text content, filter intensity, shape fill color)",
4686
+ {
4687
+ sketchId: import_zod2.z.string().optional().describe("Target sketch ID (default: selected sketch)"),
4688
+ layerId: import_zod2.z.string().describe("ID of the layer to update"),
4689
+ name: import_zod2.z.string().optional().describe("New display name"),
4690
+ properties: import_zod2.z.record(import_zod2.z.unknown()).optional().describe("Property key-value pairs to set")
4691
+ },
4692
+ async (args) => {
4693
+ try {
4694
+ const result = await designUpdateLayer(state, args);
4695
+ return jsonResult(result);
4696
+ } catch (e) {
4697
+ return toolError(e instanceof Error ? e.message : String(e));
4698
+ }
4699
+ }
4700
+ );
4701
+ server.tool(
4702
+ "design_set_transform",
4703
+ "Set the position, size, rotation, and scale of a design layer",
4704
+ {
4705
+ sketchId: import_zod2.z.string().optional().describe("Target sketch ID (default: selected sketch)"),
4706
+ layerId: import_zod2.z.string().describe("ID of the layer to transform"),
4707
+ x: import_zod2.z.number().optional().describe("X position"),
4708
+ y: import_zod2.z.number().optional().describe("Y position"),
4709
+ width: import_zod2.z.number().optional().describe("Width"),
4710
+ height: import_zod2.z.number().optional().describe("Height"),
4711
+ rotation: import_zod2.z.number().optional().describe("Rotation in degrees"),
4712
+ scaleX: import_zod2.z.number().optional().describe("Horizontal scale"),
4713
+ scaleY: import_zod2.z.number().optional().describe("Vertical scale"),
4714
+ anchorX: import_zod2.z.number().optional().describe("Anchor X (0\u20131)"),
4715
+ anchorY: import_zod2.z.number().optional().describe("Anchor Y (0\u20131)")
4716
+ },
4717
+ async (args) => {
4718
+ try {
4719
+ const result = await designSetTransform(state, args);
4720
+ return jsonResult(result);
4721
+ } catch (e) {
4722
+ return toolError(e instanceof Error ? e.message : String(e));
4723
+ }
4724
+ }
4725
+ );
4726
+ server.tool(
4727
+ "design_set_blend",
4728
+ "Set blend mode and/or opacity on a design layer",
4729
+ {
4730
+ sketchId: import_zod2.z.string().optional().describe("Target sketch ID (default: selected sketch)"),
4731
+ layerId: import_zod2.z.string().describe("ID of the layer"),
4732
+ blendMode: import_zod2.z.enum([
4733
+ "normal",
4734
+ "multiply",
4735
+ "screen",
4736
+ "overlay",
4737
+ "darken",
4738
+ "lighten",
4739
+ "color-dodge",
4740
+ "color-burn",
4741
+ "hard-light",
4742
+ "soft-light",
4743
+ "difference",
4744
+ "exclusion",
4745
+ "hue",
4746
+ "saturation",
4747
+ "color",
4748
+ "luminosity"
4749
+ ]).optional().describe("CSS blend mode"),
4750
+ opacity: import_zod2.z.number().optional().describe("Layer opacity 0\u20131")
4751
+ },
4752
+ async (args) => {
4753
+ try {
4754
+ const result = await designSetBlend(state, args);
4755
+ return jsonResult(result);
4756
+ } catch (e) {
4757
+ return toolError(e instanceof Error ? e.message : String(e));
4758
+ }
4759
+ }
4760
+ );
4761
+ server.tool(
4762
+ "design_reorder_layers",
4763
+ "Move a design layer to a new position in the z-order stack",
4764
+ {
4765
+ sketchId: import_zod2.z.string().optional().describe("Target sketch ID (default: selected sketch)"),
4766
+ layerId: import_zod2.z.string().describe("ID of the layer to move"),
4767
+ newIndex: import_zod2.z.number().describe("New position (0 = bottom, n-1 = top)")
4768
+ },
4769
+ async (args) => {
4770
+ try {
4771
+ const result = await designReorderLayers(state, args);
4772
+ return jsonResult(result);
4773
+ } catch (e) {
4774
+ return toolError(e instanceof Error ? e.message : String(e));
4775
+ }
4776
+ }
4777
+ );
4778
+ server.tool(
4779
+ "design_duplicate_layer",
4780
+ "Clone a design layer with a new ID, inserted directly above the source",
4781
+ {
4782
+ sketchId: import_zod2.z.string().optional().describe("Target sketch ID (default: selected sketch)"),
4783
+ layerId: import_zod2.z.string().describe("ID of the layer to duplicate")
4784
+ },
4785
+ async (args) => {
4786
+ try {
4787
+ const result = await designDuplicateLayer(state, args);
4788
+ return jsonResult(result);
4789
+ } catch (e) {
4790
+ return toolError(e instanceof Error ? e.message : String(e));
4791
+ }
4792
+ }
4793
+ );
4794
+ server.tool(
4795
+ "design_toggle_visibility",
4796
+ "Show or hide a design layer",
4797
+ {
4798
+ sketchId: import_zod2.z.string().optional().describe("Target sketch ID (default: selected sketch)"),
4799
+ layerId: import_zod2.z.string().describe("ID of the layer"),
4800
+ visible: import_zod2.z.boolean().optional().describe("Set visibility (default: toggle)")
4801
+ },
4802
+ async (args) => {
4803
+ try {
4804
+ const result = await designToggleVisibility(state, args);
4805
+ return jsonResult(result);
4806
+ } catch (e) {
4807
+ return toolError(e instanceof Error ? e.message : String(e));
4808
+ }
4809
+ }
4810
+ );
4811
+ server.tool(
4812
+ "design_lock_layer",
4813
+ "Lock or unlock a design layer to prevent accidental edits",
4814
+ {
4815
+ sketchId: import_zod2.z.string().optional().describe("Target sketch ID (default: selected sketch)"),
4816
+ layerId: import_zod2.z.string().describe("ID of the layer"),
4817
+ locked: import_zod2.z.boolean().optional().describe("Set lock state (default: toggle)")
4818
+ },
4819
+ async (args) => {
4820
+ try {
4821
+ const result = await designLockLayer(state, args);
4822
+ return jsonResult(result);
4823
+ } catch (e) {
4824
+ return toolError(e instanceof Error ? e.message : String(e));
4825
+ }
4826
+ }
4827
+ );
4828
+ server.tool(
4829
+ "design_capture_composite",
4830
+ "Get info about the design layer composite for a sketch. For full visual capture use capture_screenshot.",
4831
+ {
4832
+ sketchId: import_zod2.z.string().optional().describe("Target sketch ID (default: selected sketch)")
4833
+ },
4834
+ async (args) => {
4835
+ try {
4836
+ const result = await designCaptureComposite(state, args);
4837
+ return jsonResult(result);
4838
+ } catch (e) {
4839
+ return toolError(e instanceof Error ? e.message : String(e));
4840
+ }
4841
+ }
4842
+ );
4843
+ }
3811
4844
  function registerKnowledgeTools(server, _state) {
3812
4845
  server.tool(
3813
4846
  "list_skills",