@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/lib.cjs CHANGED
@@ -38,6 +38,11 @@ module.exports = __toCommonJS(lib_exports);
38
38
  // src/server.ts
39
39
  var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
40
40
  var import_zod2 = require("zod");
41
+ var import_core11 = require("@genart-dev/core");
42
+ var import_plugin_typography = __toESM(require("@genart-dev/plugin-typography"), 1);
43
+ var import_plugin_filters = __toESM(require("@genart-dev/plugin-filters"), 1);
44
+ var import_plugin_shapes = __toESM(require("@genart-dev/plugin-shapes"), 1);
45
+ var import_plugin_layout_guides = __toESM(require("@genart-dev/plugin-layout-guides"), 1);
41
46
 
42
47
  // src/tools/workspace.ts
43
48
  var import_promises = require("fs/promises");
@@ -470,10 +475,40 @@ async function createSketch(state, input) {
470
475
  const adapter = registry4.resolve(rendererType);
471
476
  algorithm = adapter.getAlgorithmTemplate();
472
477
  }
478
+ let resolvedComponents;
479
+ if (input.components && Object.keys(input.components).length > 0) {
480
+ const shorthand = {};
481
+ for (const [name, value] of Object.entries(input.components)) {
482
+ if (typeof value === "string") {
483
+ shorthand[name] = value;
484
+ } else if (value.version) {
485
+ shorthand[name] = value.version;
486
+ } else if (value.code) {
487
+ if (!resolvedComponents) resolvedComponents = {};
488
+ resolvedComponents[name] = {
489
+ ...value.version ? { version: value.version } : {},
490
+ code: value.code,
491
+ ...value.exports ? { exports: value.exports } : {}
492
+ };
493
+ }
494
+ }
495
+ if (Object.keys(shorthand).length > 0) {
496
+ const resolved = (0, import_core2.resolveComponents)(shorthand, rendererType);
497
+ if (!resolvedComponents) resolvedComponents = {};
498
+ for (const rc of resolved) {
499
+ resolvedComponents[rc.name] = {
500
+ version: rc.version,
501
+ code: rc.code,
502
+ exports: [...rc.exports]
503
+ };
504
+ }
505
+ }
506
+ }
473
507
  const seed = input.seed ?? Math.floor(Math.random() * 1e5);
474
508
  const ts = now2();
509
+ const hasComponents = resolvedComponents && Object.keys(resolvedComponents).length > 0;
475
510
  const sketch = {
476
- genart: "1.1",
511
+ genart: hasComponents ? "1.2" : "1.1",
477
512
  id: input.id,
478
513
  title: input.title,
479
514
  created: ts,
@@ -487,6 +522,7 @@ async function createSketch(state, input) {
487
522
  ...input.philosophy ? { philosophy: input.philosophy } : {},
488
523
  ...input.themes && input.themes.length > 0 ? { themes: input.themes } : {},
489
524
  ...input.skills && input.skills.length > 0 ? { skills: input.skills } : {},
525
+ ...hasComponents ? { components: resolvedComponents } : {},
490
526
  ...input.agent ? { agent: input.agent } : {},
491
527
  ...input.model ? { model: input.model } : {}
492
528
  };
@@ -670,10 +706,44 @@ async function updateAlgorithm(state, input) {
670
706
  );
671
707
  }
672
708
  }
709
+ let resolvedComponents;
710
+ if (input.components && Object.keys(input.components).length > 0) {
711
+ const renderer = def.renderer.type;
712
+ const shorthand = {};
713
+ for (const [name, value] of Object.entries(input.components)) {
714
+ if (typeof value === "string") {
715
+ shorthand[name] = value;
716
+ } else if (value.version) {
717
+ shorthand[name] = value.version;
718
+ } else if (value.code) {
719
+ if (!resolvedComponents) resolvedComponents = {};
720
+ resolvedComponents[name] = {
721
+ ...value.version ? { version: value.version } : {},
722
+ code: value.code,
723
+ ...value.exports ? { exports: value.exports } : {}
724
+ };
725
+ }
726
+ }
727
+ if (Object.keys(shorthand).length > 0) {
728
+ const resolved = (0, import_core2.resolveComponents)(shorthand, renderer);
729
+ if (!resolvedComponents) resolvedComponents = {};
730
+ for (const rc of resolved) {
731
+ resolvedComponents[rc.name] = {
732
+ version: rc.version,
733
+ code: rc.code,
734
+ exports: [...rc.exports]
735
+ };
736
+ }
737
+ }
738
+ }
739
+ const updated = ["algorithm"];
740
+ const hasNewComponents = resolvedComponents && Object.keys(resolvedComponents).length > 0;
741
+ if (hasNewComponents) updated.push("components");
673
742
  const newDef = {
674
743
  ...def,
675
744
  modified: now2(),
676
745
  algorithm: input.algorithm,
746
+ ...hasNewComponents ? { genart: "1.2", components: resolvedComponents } : {},
677
747
  ...input.agent ? { agent: input.agent } : {},
678
748
  ...input.model ? { model: input.model } : {}
679
749
  };
@@ -687,7 +757,7 @@ async function updateAlgorithm(state, input) {
687
757
  }
688
758
  state.emitMutation("sketch:updated", {
689
759
  id: input.sketchId,
690
- updated: ["algorithm"]
760
+ updated
691
761
  });
692
762
  return {
693
763
  success: true,
@@ -695,6 +765,7 @@ async function updateAlgorithm(state, input) {
695
765
  renderer: def.renderer.type,
696
766
  algorithmLength: input.algorithm.length,
697
767
  validationPassed,
768
+ ...hasNewComponents ? { componentsUpdated: true } : {},
698
769
  fileContent: json
699
770
  };
700
771
  }
@@ -1992,9 +2063,216 @@ ${guidelines}`,
1992
2063
  };
1993
2064
  }
1994
2065
 
1995
- // src/tools/capture.ts
2066
+ // src/tools/components.ts
1996
2067
  var import_promises5 = require("fs/promises");
1997
2068
  var import_core7 = require("@genart-dev/core");
2069
+ var VALID_RENDERERS2 = [
2070
+ "p5",
2071
+ "three",
2072
+ "glsl",
2073
+ "canvas2d",
2074
+ "svg"
2075
+ ];
2076
+ var RENDERER_TARGET = {
2077
+ p5: "js",
2078
+ three: "js",
2079
+ canvas2d: "js",
2080
+ svg: "js",
2081
+ glsl: "glsl"
2082
+ };
2083
+ async function listComponents(_state, input) {
2084
+ let entries = Object.values(import_core7.COMPONENT_REGISTRY);
2085
+ if (input.renderer) {
2086
+ const renderer = input.renderer;
2087
+ const target = RENDERER_TARGET[renderer];
2088
+ if (!target) {
2089
+ throw new Error(
2090
+ `Unknown renderer type: '${input.renderer}'. Valid types: ${VALID_RENDERERS2.join(", ")}`
2091
+ );
2092
+ }
2093
+ entries = entries.filter(
2094
+ (e) => e.target === target && (e.renderers.length === 0 || e.renderers.includes(renderer))
2095
+ );
2096
+ }
2097
+ if (input.category) {
2098
+ const cat = input.category;
2099
+ entries = entries.filter((e) => e.category === cat);
2100
+ }
2101
+ entries.sort((a, b) => {
2102
+ const catCmp = a.category.localeCompare(b.category);
2103
+ if (catCmp !== 0) return catCmp;
2104
+ return a.name.localeCompare(b.name);
2105
+ });
2106
+ const components = entries.map((e) => ({
2107
+ name: e.name,
2108
+ version: e.version,
2109
+ category: e.category,
2110
+ target: e.target,
2111
+ exports: [...e.exports],
2112
+ dependencies: [...e.dependencies],
2113
+ description: e.description
2114
+ }));
2115
+ return {
2116
+ count: components.length,
2117
+ components
2118
+ };
2119
+ }
2120
+ async function addComponent(state, input) {
2121
+ const loaded = state.requireSketch(input.sketchId);
2122
+ const def = loaded.definition;
2123
+ const renderer = def.renderer.type;
2124
+ const entry = import_core7.COMPONENT_REGISTRY[input.component];
2125
+ if (!entry) {
2126
+ throw new Error(`Unknown component: "${input.component}"`);
2127
+ }
2128
+ const target = RENDERER_TARGET[renderer];
2129
+ if (entry.target !== target) {
2130
+ throw new Error(
2131
+ `Component "${input.component}" has target "${entry.target}" but renderer "${renderer}" requires target "${target}"`
2132
+ );
2133
+ }
2134
+ if (entry.renderers.length > 0 && !entry.renderers.includes(renderer)) {
2135
+ throw new Error(
2136
+ `Component "${input.component}" is not compatible with renderer "${renderer}". Compatible: ${entry.renderers.join(", ")}`
2137
+ );
2138
+ }
2139
+ const existingComponents = {};
2140
+ if (def.components) {
2141
+ for (const [name, value] of Object.entries(def.components)) {
2142
+ if (typeof value === "string") {
2143
+ existingComponents[name] = value;
2144
+ } else if (value.version) {
2145
+ existingComponents[name] = value.version;
2146
+ }
2147
+ }
2148
+ }
2149
+ if (existingComponents[input.component]) {
2150
+ throw new Error(
2151
+ `Component "${input.component}" is already present in sketch "${input.sketchId}"`
2152
+ );
2153
+ }
2154
+ existingComponents[input.component] = input.version ?? "^1.0.0";
2155
+ const resolved = (0, import_core7.resolveComponents)(existingComponents, renderer);
2156
+ const resolvedRecord = {};
2157
+ for (const rc of resolved) {
2158
+ resolvedRecord[rc.name] = {
2159
+ version: rc.version,
2160
+ code: rc.code,
2161
+ exports: [...rc.exports]
2162
+ };
2163
+ }
2164
+ const previousNames = new Set(
2165
+ def.components ? Object.keys(def.components) : []
2166
+ );
2167
+ const added = resolved.map((rc) => rc.name).filter((name) => !previousNames.has(name));
2168
+ const newDef = {
2169
+ ...def,
2170
+ genart: "1.2",
2171
+ modified: (/* @__PURE__ */ new Date()).toISOString(),
2172
+ components: resolvedRecord
2173
+ };
2174
+ state.sketches.set(input.sketchId, {
2175
+ definition: newDef,
2176
+ path: loaded.path
2177
+ });
2178
+ const json = (0, import_core7.serializeGenart)(newDef);
2179
+ if (!state.remoteMode) {
2180
+ await (0, import_promises5.writeFile)(loaded.path, json, "utf-8");
2181
+ }
2182
+ state.emitMutation("sketch:updated", {
2183
+ id: input.sketchId,
2184
+ updated: ["components"]
2185
+ });
2186
+ return {
2187
+ success: true,
2188
+ sketchId: input.sketchId,
2189
+ components: resolvedRecord,
2190
+ added,
2191
+ fileContent: json
2192
+ };
2193
+ }
2194
+ async function removeComponent(state, input) {
2195
+ const loaded = state.requireSketch(input.sketchId);
2196
+ const def = loaded.definition;
2197
+ if (!def.components || !def.components[input.component]) {
2198
+ throw new Error(
2199
+ `Component "${input.component}" is not present in sketch "${input.sketchId}"`
2200
+ );
2201
+ }
2202
+ const remaining = { ...def.components };
2203
+ delete remaining[input.component];
2204
+ for (const [name, value] of Object.entries(remaining)) {
2205
+ const entry = import_core7.COMPONENT_REGISTRY[name];
2206
+ if (entry && entry.dependencies.includes(input.component)) {
2207
+ throw new Error(
2208
+ `Cannot remove "${input.component}": component "${name}" depends on it`
2209
+ );
2210
+ }
2211
+ }
2212
+ let warning;
2213
+ const removedValue = def.components[input.component];
2214
+ const exports2 = typeof removedValue === "string" ? import_core7.COMPONENT_REGISTRY[input.component]?.exports ?? [] : removedValue.exports ?? [];
2215
+ const usedExports = exports2.filter((exp) => def.algorithm.includes(exp));
2216
+ if (usedExports.length > 0) {
2217
+ warning = `Algorithm may reference these exports from "${input.component}": ${usedExports.join(", ")}. Review your algorithm after removal.`;
2218
+ }
2219
+ const removed = [input.component];
2220
+ const neededDeps = /* @__PURE__ */ new Set();
2221
+ for (const name of Object.keys(remaining)) {
2222
+ const entry = import_core7.COMPONENT_REGISTRY[name];
2223
+ if (entry) {
2224
+ collectTransitiveDeps(name, neededDeps);
2225
+ }
2226
+ }
2227
+ for (const name of Object.keys(remaining)) {
2228
+ if (!neededDeps.has(name) && !isDirectComponent(name, remaining)) {
2229
+ delete remaining[name];
2230
+ removed.push(name);
2231
+ }
2232
+ }
2233
+ const hasRemaining = Object.keys(remaining).length > 0;
2234
+ const newDef = {
2235
+ ...def,
2236
+ modified: (/* @__PURE__ */ new Date()).toISOString(),
2237
+ ...hasRemaining ? { components: remaining } : { components: void 0 }
2238
+ };
2239
+ state.sketches.set(input.sketchId, {
2240
+ definition: newDef,
2241
+ path: loaded.path
2242
+ });
2243
+ const json = (0, import_core7.serializeGenart)(newDef);
2244
+ if (!state.remoteMode) {
2245
+ await (0, import_promises5.writeFile)(loaded.path, json, "utf-8");
2246
+ }
2247
+ state.emitMutation("sketch:updated", {
2248
+ id: input.sketchId,
2249
+ updated: ["components"]
2250
+ });
2251
+ return {
2252
+ success: true,
2253
+ sketchId: input.sketchId,
2254
+ removed,
2255
+ ...warning ? { warning } : {},
2256
+ fileContent: json
2257
+ };
2258
+ }
2259
+ function collectTransitiveDeps(name, deps) {
2260
+ const entry = import_core7.COMPONENT_REGISTRY[name];
2261
+ if (!entry) return;
2262
+ deps.add(name);
2263
+ for (const dep of entry.dependencies) {
2264
+ if (!deps.has(dep)) {
2265
+ collectTransitiveDeps(dep, deps);
2266
+ }
2267
+ }
2268
+ }
2269
+ function isDirectComponent(name, components) {
2270
+ return name in components;
2271
+ }
2272
+
2273
+ // src/tools/capture.ts
2274
+ var import_promises6 = require("fs/promises");
2275
+ var import_core8 = require("@genart-dev/core");
1998
2276
 
1999
2277
  // src/capture/headless.ts
2000
2278
  var cachedModule = null;
@@ -2088,7 +2366,7 @@ async function captureHtmlMulti(options) {
2088
2366
  }
2089
2367
 
2090
2368
  // src/tools/capture.ts
2091
- var registry2 = (0, import_core7.createDefaultRegistry)();
2369
+ var registry2 = (0, import_core8.createDefaultRegistry)();
2092
2370
  function applyOverrides(sketch, overrides) {
2093
2371
  if (overrides.seed === void 0 && overrides.params === void 0) {
2094
2372
  return sketch;
@@ -2169,7 +2447,7 @@ async function buildScreenshotMetadata(state, multi, info) {
2169
2447
  previewPath: info.previewPath
2170
2448
  };
2171
2449
  if (!state.remoteMode) {
2172
- await (0, import_promises5.writeFile)(info.previewPath, multi.previewPng);
2450
+ await (0, import_promises6.writeFile)(info.previewPath, multi.previewPng);
2173
2451
  metadata.savedPreviewTo = info.previewPath;
2174
2452
  }
2175
2453
  return metadata;
@@ -2230,15 +2508,15 @@ async function captureBatch(state, input) {
2230
2508
 
2231
2509
  // src/tools/export.ts
2232
2510
  var import_fs = require("fs");
2233
- var import_promises6 = require("fs/promises");
2511
+ var import_promises7 = require("fs/promises");
2234
2512
  var import_path8 = require("path");
2235
2513
  var import_archiver = __toESM(require("archiver"), 1);
2236
- var import_core8 = require("@genart-dev/core");
2237
- var registry3 = (0, import_core8.createDefaultRegistry)();
2514
+ var import_core9 = require("@genart-dev/core");
2515
+ var registry3 = (0, import_core9.createDefaultRegistry)();
2238
2516
  async function validateOutputPath(outputPath) {
2239
2517
  const parentDir = (0, import_path8.dirname)(outputPath);
2240
2518
  try {
2241
- const s = await (0, import_promises6.stat)(parentDir);
2519
+ const s = await (0, import_promises7.stat)(parentDir);
2242
2520
  if (!s.isDirectory()) {
2243
2521
  throw new Error(`Parent directory does not exist: ${parentDir}`);
2244
2522
  }
@@ -2247,7 +2525,7 @@ async function validateOutputPath(outputPath) {
2247
2525
  throw new Error(`Parent directory does not exist: ${parentDir}`);
2248
2526
  }
2249
2527
  try {
2250
- await (0, import_promises6.stat)(outputPath);
2528
+ await (0, import_promises7.stat)(outputPath);
2251
2529
  throw new Error(
2252
2530
  `File already exists at ${outputPath}. Delete it first or use a different path.`
2253
2531
  );
@@ -2302,7 +2580,7 @@ async function exportHtml(sketch, outputPath) {
2302
2580
  const adapter = registry3.resolve(sketch.renderer.type);
2303
2581
  const html = adapter.generateStandaloneHTML(sketch);
2304
2582
  const content = Buffer.from(html, "utf-8");
2305
- await (0, import_promises6.writeFile)(outputPath, content);
2583
+ await (0, import_promises7.writeFile)(outputPath, content);
2306
2584
  return {
2307
2585
  success: true,
2308
2586
  sketchId: sketch.id,
@@ -2318,7 +2596,7 @@ async function exportPng(sketch, input) {
2318
2596
  const width = input.width ?? sketch.canvas.width;
2319
2597
  const height = input.height ?? sketch.canvas.height;
2320
2598
  const result = await captureHtml({ html, width, height });
2321
- await (0, import_promises6.writeFile)(input.outputPath, result.bytes);
2599
+ await (0, import_promises7.writeFile)(input.outputPath, result.bytes);
2322
2600
  return {
2323
2601
  success: true,
2324
2602
  sketchId: sketch.id,
@@ -2333,7 +2611,7 @@ async function exportSvg(sketch, input) {
2333
2611
  const height = input.height ?? sketch.canvas.height;
2334
2612
  if (sketch.renderer.type === "svg") {
2335
2613
  const content2 = Buffer.from(sketch.algorithm, "utf-8");
2336
- await (0, import_promises6.writeFile)(input.outputPath, content2);
2614
+ await (0, import_promises7.writeFile)(input.outputPath, content2);
2337
2615
  return {
2338
2616
  success: true,
2339
2617
  sketchId: sketch.id,
@@ -2355,7 +2633,7 @@ async function exportSvg(sketch, input) {
2355
2633
  href="data:image/png;base64,${b64}"/>
2356
2634
  </svg>`;
2357
2635
  const content = Buffer.from(svg, "utf-8");
2358
- await (0, import_promises6.writeFile)(input.outputPath, content);
2636
+ await (0, import_promises7.writeFile)(input.outputPath, content);
2359
2637
  return {
2360
2638
  success: true,
2361
2639
  sketchId: sketch.id,
@@ -2368,7 +2646,7 @@ async function exportSvg(sketch, input) {
2368
2646
  }
2369
2647
  async function exportAlgorithm(sketch, outputPath) {
2370
2648
  const content = Buffer.from(sketch.algorithm, "utf-8");
2371
- await (0, import_promises6.writeFile)(outputPath, content);
2649
+ await (0, import_promises7.writeFile)(outputPath, content);
2372
2650
  return {
2373
2651
  success: true,
2374
2652
  sketchId: sketch.id,
@@ -2383,7 +2661,7 @@ async function exportZip(sketch, input) {
2383
2661
  const width = input.width ?? sketch.canvas.width;
2384
2662
  const height = input.height ?? sketch.canvas.height;
2385
2663
  const html = adapter.generateStandaloneHTML(sketch);
2386
- const genartJson = (0, import_core8.serializeGenart)(sketch);
2664
+ const genartJson = (0, import_core9.serializeGenart)(sketch);
2387
2665
  const algorithm = sketch.algorithm;
2388
2666
  const algExt = algorithmExtension(sketch.renderer.type);
2389
2667
  const captureResult = await captureHtml({ html, width, height });
@@ -2400,7 +2678,7 @@ async function exportZip(sketch, input) {
2400
2678
  archive.append(genartJson, { name: `${sketch.id}.genart` });
2401
2679
  await archive.finalize();
2402
2680
  await finished;
2403
- const s = await (0, import_promises6.stat)(input.outputPath);
2681
+ const s = await (0, import_promises7.stat)(input.outputPath);
2404
2682
  return {
2405
2683
  success: true,
2406
2684
  sketchId: sketch.id,
@@ -2417,8 +2695,332 @@ async function exportZip(sketch, input) {
2417
2695
  };
2418
2696
  }
2419
2697
 
2698
+ // src/tools/design.ts
2699
+ function requireSketchId(state, args) {
2700
+ return args.sketchId ?? state.requireSelectedSketchId();
2701
+ }
2702
+ var BLEND_MODES = [
2703
+ "normal",
2704
+ "multiply",
2705
+ "screen",
2706
+ "overlay",
2707
+ "darken",
2708
+ "lighten",
2709
+ "color-dodge",
2710
+ "color-burn",
2711
+ "hard-light",
2712
+ "soft-light",
2713
+ "difference",
2714
+ "exclusion",
2715
+ "hue",
2716
+ "saturation",
2717
+ "color",
2718
+ "luminosity"
2719
+ ];
2720
+ function generateLayerId() {
2721
+ return `layer-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
2722
+ }
2723
+ async function designAddLayer(state, args) {
2724
+ const sketchId = requireSketchId(state, args);
2725
+ const loaded = state.requireSketch(sketchId);
2726
+ const stack = state.getLayerStack(sketchId);
2727
+ const registry4 = state.pluginRegistry;
2728
+ const layerTypeDef = registry4?.resolveLayerType(args.type);
2729
+ if (!layerTypeDef) {
2730
+ throw new Error(
2731
+ `Unknown layer type: '${args.type}'. Use design_list_layers types from registered plugins.`
2732
+ );
2733
+ }
2734
+ const defaults = layerTypeDef.createDefault();
2735
+ const id = generateLayerId();
2736
+ const { width, height } = loaded.definition.canvas;
2737
+ const layer = {
2738
+ id,
2739
+ type: args.type,
2740
+ name: args.name ?? layerTypeDef.displayName,
2741
+ visible: true,
2742
+ locked: false,
2743
+ opacity: args.opacity ?? 1,
2744
+ blendMode: args.blendMode ?? "normal",
2745
+ transform: {
2746
+ x: 0,
2747
+ y: 0,
2748
+ width,
2749
+ height,
2750
+ rotation: 0,
2751
+ scaleX: 1,
2752
+ scaleY: 1,
2753
+ anchorX: 0.5,
2754
+ anchorY: 0.5,
2755
+ ...args.transform
2756
+ },
2757
+ properties: { ...defaults, ...args.properties }
2758
+ };
2759
+ stack.add(layer, args.index);
2760
+ await state.saveSketch(sketchId);
2761
+ return {
2762
+ layerId: id,
2763
+ type: args.type,
2764
+ name: layer.name,
2765
+ index: args.index ?? stack.count - 1,
2766
+ sketchId
2767
+ };
2768
+ }
2769
+ async function designRemoveLayer(state, args) {
2770
+ const sketchId = requireSketchId(state, args);
2771
+ const stack = state.getLayerStack(sketchId);
2772
+ const removed = stack.remove(args.layerId);
2773
+ if (!removed) {
2774
+ throw new Error(`Layer '${args.layerId}' not found in sketch '${sketchId}'.`);
2775
+ }
2776
+ await state.saveSketch(sketchId);
2777
+ return { removed: true, layerId: args.layerId, sketchId };
2778
+ }
2779
+ async function designListLayers(state, args) {
2780
+ const sketchId = requireSketchId(state, args);
2781
+ const stack = state.getLayerStack(sketchId);
2782
+ const layers = stack.getAll();
2783
+ return {
2784
+ sketchId,
2785
+ count: layers.length,
2786
+ layers: layers.map((l, i) => ({
2787
+ index: i,
2788
+ id: l.id,
2789
+ type: l.type,
2790
+ name: l.name,
2791
+ visible: l.visible,
2792
+ locked: l.locked,
2793
+ opacity: l.opacity,
2794
+ blendMode: l.blendMode
2795
+ }))
2796
+ };
2797
+ }
2798
+ async function designGetLayer(state, args) {
2799
+ const sketchId = requireSketchId(state, args);
2800
+ const stack = state.getLayerStack(sketchId);
2801
+ const layer = stack.get(args.layerId);
2802
+ if (!layer) {
2803
+ throw new Error(`Layer '${args.layerId}' not found in sketch '${sketchId}'.`);
2804
+ }
2805
+ return {
2806
+ sketchId,
2807
+ layer: {
2808
+ id: layer.id,
2809
+ type: layer.type,
2810
+ name: layer.name,
2811
+ visible: layer.visible,
2812
+ locked: layer.locked,
2813
+ opacity: layer.opacity,
2814
+ blendMode: layer.blendMode,
2815
+ transform: layer.transform,
2816
+ properties: layer.properties
2817
+ }
2818
+ };
2819
+ }
2820
+ async function designUpdateLayer(state, args) {
2821
+ const sketchId = requireSketchId(state, args);
2822
+ const stack = state.getLayerStack(sketchId);
2823
+ const layer = stack.get(args.layerId);
2824
+ if (!layer) {
2825
+ throw new Error(`Layer '${args.layerId}' not found in sketch '${sketchId}'.`);
2826
+ }
2827
+ const updates = {};
2828
+ if (args.properties) {
2829
+ Object.assign(updates, args.properties);
2830
+ }
2831
+ if (Object.keys(updates).length > 0) {
2832
+ stack.updateProperties(args.layerId, updates);
2833
+ }
2834
+ if (args.name !== void 0) {
2835
+ const current = stack.get(args.layerId);
2836
+ stack.updateProperties(args.layerId, { ...current.properties });
2837
+ const mutableLayer = stack.get(args.layerId);
2838
+ mutableLayer.name = args.name;
2839
+ }
2840
+ await state.saveSketch(sketchId);
2841
+ return { updated: true, layerId: args.layerId, sketchId };
2842
+ }
2843
+ async function designSetTransform(state, args) {
2844
+ const sketchId = requireSketchId(state, args);
2845
+ const stack = state.getLayerStack(sketchId);
2846
+ const layer = stack.get(args.layerId);
2847
+ if (!layer) {
2848
+ throw new Error(`Layer '${args.layerId}' not found in sketch '${sketchId}'.`);
2849
+ }
2850
+ const partial = {};
2851
+ if (args.x !== void 0) partial.x = args.x;
2852
+ if (args.y !== void 0) partial.y = args.y;
2853
+ if (args.width !== void 0) partial.width = args.width;
2854
+ if (args.height !== void 0) partial.height = args.height;
2855
+ if (args.rotation !== void 0) partial.rotation = args.rotation;
2856
+ if (args.scaleX !== void 0) partial.scaleX = args.scaleX;
2857
+ if (args.scaleY !== void 0) partial.scaleY = args.scaleY;
2858
+ if (args.anchorX !== void 0) partial.anchorX = args.anchorX;
2859
+ if (args.anchorY !== void 0) partial.anchorY = args.anchorY;
2860
+ stack.updateTransform(args.layerId, partial);
2861
+ await state.saveSketch(sketchId);
2862
+ return {
2863
+ updated: true,
2864
+ layerId: args.layerId,
2865
+ transform: stack.get(args.layerId).transform,
2866
+ sketchId
2867
+ };
2868
+ }
2869
+ async function designSetBlend(state, args) {
2870
+ const sketchId = requireSketchId(state, args);
2871
+ const stack = state.getLayerStack(sketchId);
2872
+ const layer = stack.get(args.layerId);
2873
+ if (!layer) {
2874
+ throw new Error(`Layer '${args.layerId}' not found in sketch '${sketchId}'.`);
2875
+ }
2876
+ if (args.blendMode && !BLEND_MODES.includes(args.blendMode)) {
2877
+ throw new Error(
2878
+ `Invalid blend mode '${args.blendMode}'. Must be one of: ${BLEND_MODES.join(", ")}`
2879
+ );
2880
+ }
2881
+ stack.updateBlend(
2882
+ args.layerId,
2883
+ args.blendMode,
2884
+ args.opacity
2885
+ );
2886
+ await state.saveSketch(sketchId);
2887
+ const updated = stack.get(args.layerId);
2888
+ return {
2889
+ updated: true,
2890
+ layerId: args.layerId,
2891
+ blendMode: updated.blendMode,
2892
+ opacity: updated.opacity,
2893
+ sketchId
2894
+ };
2895
+ }
2896
+ async function designReorderLayers(state, args) {
2897
+ const sketchId = requireSketchId(state, args);
2898
+ const stack = state.getLayerStack(sketchId);
2899
+ const layer = stack.get(args.layerId);
2900
+ if (!layer) {
2901
+ throw new Error(`Layer '${args.layerId}' not found in sketch '${sketchId}'.`);
2902
+ }
2903
+ stack.reorder(args.layerId, args.newIndex);
2904
+ await state.saveSketch(sketchId);
2905
+ return {
2906
+ reordered: true,
2907
+ layerId: args.layerId,
2908
+ newIndex: args.newIndex,
2909
+ sketchId
2910
+ };
2911
+ }
2912
+ async function designDuplicateLayer(state, args) {
2913
+ const sketchId = requireSketchId(state, args);
2914
+ const stack = state.getLayerStack(sketchId);
2915
+ const layer = stack.get(args.layerId);
2916
+ if (!layer) {
2917
+ throw new Error(`Layer '${args.layerId}' not found in sketch '${sketchId}'.`);
2918
+ }
2919
+ const newId = stack.duplicate(args.layerId);
2920
+ await state.saveSketch(sketchId);
2921
+ return {
2922
+ duplicated: true,
2923
+ sourceLayerId: args.layerId,
2924
+ newLayerId: newId,
2925
+ sketchId
2926
+ };
2927
+ }
2928
+ async function designToggleVisibility(state, args) {
2929
+ const sketchId = requireSketchId(state, args);
2930
+ const stack = state.getLayerStack(sketchId);
2931
+ const layer = stack.get(args.layerId);
2932
+ if (!layer) {
2933
+ throw new Error(`Layer '${args.layerId}' not found in sketch '${sketchId}'.`);
2934
+ }
2935
+ const newVisible = args.visible ?? !layer.visible;
2936
+ const mutableLayer = layer;
2937
+ mutableLayer.visible = newVisible;
2938
+ stack.updateProperties(args.layerId, { ...layer.properties });
2939
+ await state.saveSketch(sketchId);
2940
+ return {
2941
+ layerId: args.layerId,
2942
+ visible: newVisible,
2943
+ sketchId
2944
+ };
2945
+ }
2946
+ async function designLockLayer(state, args) {
2947
+ const sketchId = requireSketchId(state, args);
2948
+ const stack = state.getLayerStack(sketchId);
2949
+ const layer = stack.get(args.layerId);
2950
+ if (!layer) {
2951
+ throw new Error(`Layer '${args.layerId}' not found in sketch '${sketchId}'.`);
2952
+ }
2953
+ const newLocked = args.locked ?? !layer.locked;
2954
+ const mutableLayer = layer;
2955
+ mutableLayer.locked = newLocked;
2956
+ stack.updateProperties(args.layerId, { ...layer.properties });
2957
+ await state.saveSketch(sketchId);
2958
+ return {
2959
+ layerId: args.layerId,
2960
+ locked: newLocked,
2961
+ sketchId
2962
+ };
2963
+ }
2964
+ async function designCaptureComposite(state, args) {
2965
+ const sketchId = requireSketchId(state, args);
2966
+ const stack = state.getLayerStack(sketchId);
2967
+ const layers = stack.getAll();
2968
+ return {
2969
+ sketchId,
2970
+ layerCount: layers.length,
2971
+ visibleCount: layers.filter((l) => l.visible).length,
2972
+ 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."
2973
+ };
2974
+ }
2975
+
2976
+ // src/tools/design-plugins.ts
2977
+ function registerPluginMcpTools(server, registry4, state) {
2978
+ for (const tool of registry4.getMcpTools()) {
2979
+ const inputSchema = tool.definition.inputSchema;
2980
+ server.tool(
2981
+ tool.name,
2982
+ tool.definition.description,
2983
+ // Pass raw JSON schema — MCP SDK accepts this alongside Zod
2984
+ inputSchema,
2985
+ async (args) => {
2986
+ try {
2987
+ const sketchId = args.sketchId ?? state.requireSelectedSketchId();
2988
+ const context = state.createMcpToolContext(sketchId);
2989
+ const result = await tool.definition.handler(args, context);
2990
+ await state.saveSketch(sketchId);
2991
+ return {
2992
+ content: result.content.map((c) => {
2993
+ if (c.type === "text") {
2994
+ return { type: "text", text: c.text };
2995
+ }
2996
+ return {
2997
+ type: "image",
2998
+ data: c.data,
2999
+ mimeType: c.mimeType
3000
+ };
3001
+ }),
3002
+ isError: result.isError
3003
+ };
3004
+ } catch (e) {
3005
+ return {
3006
+ content: [
3007
+ {
3008
+ type: "text",
3009
+ text: JSON.stringify({
3010
+ error: e instanceof Error ? e.message : String(e)
3011
+ })
3012
+ }
3013
+ ],
3014
+ isError: true
3015
+ };
3016
+ }
3017
+ }
3018
+ );
3019
+ }
3020
+ }
3021
+
2420
3022
  // src/resources/index.ts
2421
- var import_core9 = require("@genart-dev/core");
3023
+ var import_core10 = require("@genart-dev/core");
2422
3024
  function registerResources(server, state) {
2423
3025
  registerSkillsResource(server);
2424
3026
  registerCanvasPresetsResource(server);
@@ -2426,7 +3028,7 @@ function registerResources(server, state) {
2426
3028
  registerRenderersResource(server);
2427
3029
  }
2428
3030
  function registerSkillsResource(server) {
2429
- const skillRegistry = (0, import_core9.createDefaultSkillRegistry)();
3031
+ const skillRegistry = (0, import_core10.createDefaultSkillRegistry)();
2430
3032
  server.resource(
2431
3033
  "skills",
2432
3034
  "genart://skills",
@@ -2477,7 +3079,7 @@ function registerCanvasPresetsResource(server) {
2477
3079
  mimeType: "application/json",
2478
3080
  text: JSON.stringify(
2479
3081
  {
2480
- presets: import_core9.CANVAS_PRESETS.map((p) => ({
3082
+ presets: import_core10.CANVAS_PRESETS.map((p) => ({
2481
3083
  id: p.id,
2482
3084
  label: p.label,
2483
3085
  category: p.category,
@@ -2534,7 +3136,7 @@ function registerGalleryResource(server, state) {
2534
3136
  );
2535
3137
  }
2536
3138
  function registerRenderersResource(server) {
2537
- const registry4 = (0, import_core9.createDefaultRegistry)();
3139
+ const registry4 = (0, import_core10.createDefaultRegistry)();
2538
3140
  server.resource(
2539
3141
  "renderers",
2540
3142
  "genart://renderers",
@@ -2900,11 +3502,23 @@ function toolError(message) {
2900
3502
  isError: true
2901
3503
  };
2902
3504
  }
3505
+ async function initializePluginRegistry() {
3506
+ const registry4 = (0, import_core11.createPluginRegistry)({
3507
+ surface: "mcp",
3508
+ supportsInteractiveTools: false,
3509
+ supportsRendering: false
3510
+ });
3511
+ await registry4.register(import_plugin_typography.default);
3512
+ await registry4.register(import_plugin_filters.default);
3513
+ await registry4.register(import_plugin_shapes.default);
3514
+ await registry4.register(import_plugin_layout_guides.default);
3515
+ return registry4;
3516
+ }
2903
3517
  function createServer(state) {
2904
3518
  const server = new import_mcp.McpServer(
2905
3519
  {
2906
3520
  name: "@genart/mcp-server",
2907
- version: "0.0.1"
3521
+ version: "0.3.0"
2908
3522
  },
2909
3523
  {
2910
3524
  capabilities: {
@@ -2914,8 +3528,14 @@ function createServer(state) {
2914
3528
  }
2915
3529
  }
2916
3530
  );
3531
+ const registryReady = initializePluginRegistry().then((registry4) => {
3532
+ state.pluginRegistry = registry4;
3533
+ registerPluginMcpTools(server, registry4, state);
3534
+ });
3535
+ server._pluginsReady = registryReady;
2917
3536
  registerWorkspaceTools(server, state);
2918
3537
  registerSketchTools(server, state);
3538
+ registerComponentTools(server, state);
2919
3539
  registerSelectionTools(server, state);
2920
3540
  registerParameterTools(server, state);
2921
3541
  registerArrangementTools(server, state);
@@ -2923,6 +3543,7 @@ function createServer(state) {
2923
3543
  registerMergeTools(server, state);
2924
3544
  registerSnapshotTools(server, state);
2925
3545
  registerKnowledgeTools(server, state);
3546
+ registerDesignTools(server, state);
2926
3547
  registerCaptureTools(server, state);
2927
3548
  registerExportTools(server, state);
2928
3549
  registerResources(server, state);
@@ -3019,7 +3640,7 @@ function registerWorkspaceTools(server, state) {
3019
3640
  function registerSketchTools(server, state) {
3020
3641
  server.tool(
3021
3642
  "create_sketch",
3022
- "Create a new .genart sketch file from metadata, parameters, and algorithm",
3643
+ '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.',
3023
3644
  {
3024
3645
  id: import_zod2.z.string().describe("URL-safe kebab-case identifier"),
3025
3646
  title: import_zod2.z.string().describe("Human-readable title"),
@@ -3057,6 +3678,16 @@ function registerSketchTools(server, state) {
3057
3678
  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)."),
3058
3679
  seed: import_zod2.z.number().optional().describe("Initial random seed (default: random)"),
3059
3680
  skills: import_zod2.z.array(import_zod2.z.string()).optional().describe("Design skill references"),
3681
+ components: import_zod2.z.record(
3682
+ import_zod2.z.union([
3683
+ import_zod2.z.string(),
3684
+ import_zod2.z.object({
3685
+ version: import_zod2.z.string().optional(),
3686
+ code: import_zod2.z.string().optional(),
3687
+ exports: import_zod2.z.array(import_zod2.z.string()).optional()
3688
+ })
3689
+ ])
3690
+ ).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.'),
3060
3691
  addToWorkspace: import_zod2.z.string().optional().describe("Path to workspace to add sketch to after creation"),
3061
3692
  agent: import_zod2.z.string().optional().describe("Your CLI agent name (e.g. 'claude-code', 'codex-cli', 'gemini-cli', 'opencode', 'kiro')"),
3062
3693
  model: import_zod2.z.string().optional().describe("Your AI model identifier (e.g. 'claude-opus-4-6', 'gpt-4o', 'gemini-2.5-pro')")
@@ -3136,11 +3767,21 @@ function registerSketchTools(server, state) {
3136
3767
  );
3137
3768
  server.tool(
3138
3769
  "update_algorithm",
3139
- "Replace the algorithm source code of a sketch",
3770
+ "Replace the algorithm source code of a sketch. If adding/changing components, pass them in the components field alongside the algorithm.",
3140
3771
  {
3141
3772
  sketchId: import_zod2.z.string().describe("ID of the sketch to update"),
3142
3773
  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)."),
3143
3774
  validate: import_zod2.z.boolean().optional().describe("Run renderer-specific validation before saving (default: true)"),
3775
+ components: import_zod2.z.record(
3776
+ import_zod2.z.union([
3777
+ import_zod2.z.string(),
3778
+ import_zod2.z.object({
3779
+ version: import_zod2.z.string().optional(),
3780
+ code: import_zod2.z.string().optional(),
3781
+ exports: import_zod2.z.array(import_zod2.z.string()).optional()
3782
+ })
3783
+ ])
3784
+ ).optional().describe("Component dependencies to resolve alongside the algorithm update. Use list_components to see available."),
3144
3785
  agent: import_zod2.z.string().optional().describe("Your CLI agent name (e.g. 'claude-code', 'codex-cli', 'gemini-cli', 'opencode', 'kiro')"),
3145
3786
  model: import_zod2.z.string().optional().describe("Your AI model identifier (e.g. 'claude-opus-4-6', 'gpt-4o', 'gemini-2.5-pro')")
3146
3787
  },
@@ -3236,6 +3877,76 @@ function registerSketchTools(server, state) {
3236
3877
  }
3237
3878
  );
3238
3879
  }
3880
+ function registerComponentTools(server, state) {
3881
+ server.tool(
3882
+ "list_components",
3883
+ "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.",
3884
+ {
3885
+ renderer: import_zod2.z.enum(["p5", "three", "glsl", "canvas2d", "svg"]).optional().describe("Filter by renderer compatibility"),
3886
+ category: import_zod2.z.enum([
3887
+ "randomness",
3888
+ "noise",
3889
+ "math",
3890
+ "easing",
3891
+ "color",
3892
+ "vector",
3893
+ "geometry",
3894
+ "grid",
3895
+ "particle",
3896
+ "physics",
3897
+ "distribution",
3898
+ "pattern",
3899
+ "sdf",
3900
+ "transform",
3901
+ "animation",
3902
+ "string",
3903
+ "data-structure",
3904
+ "imaging"
3905
+ ]).optional().describe("Filter by component category")
3906
+ },
3907
+ async (args) => {
3908
+ try {
3909
+ const result = await listComponents(state, args);
3910
+ return jsonResult(result);
3911
+ } catch (e) {
3912
+ return toolError(e instanceof Error ? e.message : String(e));
3913
+ }
3914
+ }
3915
+ );
3916
+ server.tool(
3917
+ "add_component",
3918
+ "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.",
3919
+ {
3920
+ sketchId: import_zod2.z.string().describe("ID of the sketch to add the component to"),
3921
+ component: import_zod2.z.string().describe("Component name (e.g. 'prng', 'noise-2d', 'glsl-noise')"),
3922
+ version: import_zod2.z.string().optional().describe("Version range (default: '^1.0.0')")
3923
+ },
3924
+ async (args) => {
3925
+ try {
3926
+ const result = await addComponent(state, args);
3927
+ return jsonResult(result);
3928
+ } catch (e) {
3929
+ return toolError(e instanceof Error ? e.message : String(e));
3930
+ }
3931
+ }
3932
+ );
3933
+ server.tool(
3934
+ "remove_component",
3935
+ "Remove a component dependency from a sketch. Checks for dependent components and warns if the algorithm references the component's exports.",
3936
+ {
3937
+ sketchId: import_zod2.z.string().describe("ID of the sketch to remove the component from"),
3938
+ component: import_zod2.z.string().describe("Component name to remove")
3939
+ },
3940
+ async (args) => {
3941
+ try {
3942
+ const result = await removeComponent(state, args);
3943
+ return jsonResult(result);
3944
+ } catch (e) {
3945
+ return toolError(e instanceof Error ? e.message : String(e));
3946
+ }
3947
+ }
3948
+ );
3949
+ }
3239
3950
  function registerSelectionTools(server, state) {
3240
3951
  server.tool(
3241
3952
  "get_selection",
@@ -3621,6 +4332,247 @@ function registerExportTools(server, state) {
3621
4332
  }
3622
4333
  );
3623
4334
  }
4335
+ function registerDesignTools(server, state) {
4336
+ server.tool(
4337
+ "design_add_layer",
4338
+ "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').",
4339
+ {
4340
+ sketchId: import_zod2.z.string().optional().describe("Target sketch ID (default: selected sketch)"),
4341
+ type: import_zod2.z.string().describe("Layer type ID (e.g. 'typography:text', 'filter:grain', 'shapes:rect')"),
4342
+ name: import_zod2.z.string().optional().describe("Layer display name (default: type's display name)"),
4343
+ properties: import_zod2.z.record(import_zod2.z.unknown()).optional().describe("Initial layer properties (merged with type defaults)"),
4344
+ transform: import_zod2.z.object({
4345
+ x: import_zod2.z.number().optional(),
4346
+ y: import_zod2.z.number().optional(),
4347
+ width: import_zod2.z.number().optional(),
4348
+ height: import_zod2.z.number().optional(),
4349
+ rotation: import_zod2.z.number().optional(),
4350
+ scaleX: import_zod2.z.number().optional(),
4351
+ scaleY: import_zod2.z.number().optional(),
4352
+ anchorX: import_zod2.z.number().optional(),
4353
+ anchorY: import_zod2.z.number().optional()
4354
+ }).optional().describe("Layer transform (default: full canvas)"),
4355
+ opacity: import_zod2.z.number().optional().describe("Layer opacity 0\u20131 (default: 1)"),
4356
+ blendMode: import_zod2.z.string().optional().describe("Blend mode (default: 'normal')"),
4357
+ index: import_zod2.z.number().optional().describe("Insert position in layer stack (default: top)")
4358
+ },
4359
+ async (args) => {
4360
+ try {
4361
+ const result = await designAddLayer(state, args);
4362
+ return jsonResult(result);
4363
+ } catch (e) {
4364
+ return toolError(e instanceof Error ? e.message : String(e));
4365
+ }
4366
+ }
4367
+ );
4368
+ server.tool(
4369
+ "design_remove_layer",
4370
+ "Remove a design layer from the active sketch",
4371
+ {
4372
+ sketchId: import_zod2.z.string().optional().describe("Target sketch ID (default: selected sketch)"),
4373
+ layerId: import_zod2.z.string().describe("ID of the layer to remove")
4374
+ },
4375
+ async (args) => {
4376
+ try {
4377
+ const result = await designRemoveLayer(state, args);
4378
+ return jsonResult(result);
4379
+ } catch (e) {
4380
+ return toolError(e instanceof Error ? e.message : String(e));
4381
+ }
4382
+ }
4383
+ );
4384
+ server.tool(
4385
+ "design_list_layers",
4386
+ "List all design layers in the active sketch with their types, visibility, and key properties",
4387
+ {
4388
+ sketchId: import_zod2.z.string().optional().describe("Target sketch ID (default: selected sketch)")
4389
+ },
4390
+ async (args) => {
4391
+ try {
4392
+ const result = await designListLayers(state, args);
4393
+ return jsonResult(result);
4394
+ } catch (e) {
4395
+ return toolError(e instanceof Error ? e.message : String(e));
4396
+ }
4397
+ }
4398
+ );
4399
+ server.tool(
4400
+ "design_get_layer",
4401
+ "Get full details of a single design layer including all properties and transform",
4402
+ {
4403
+ sketchId: import_zod2.z.string().optional().describe("Target sketch ID (default: selected sketch)"),
4404
+ layerId: import_zod2.z.string().describe("ID of the layer to inspect")
4405
+ },
4406
+ async (args) => {
4407
+ try {
4408
+ const result = await designGetLayer(state, args);
4409
+ return jsonResult(result);
4410
+ } catch (e) {
4411
+ return toolError(e instanceof Error ? e.message : String(e));
4412
+ }
4413
+ }
4414
+ );
4415
+ server.tool(
4416
+ "design_update_layer",
4417
+ "Update properties on a design layer (e.g. text content, filter intensity, shape fill color)",
4418
+ {
4419
+ sketchId: import_zod2.z.string().optional().describe("Target sketch ID (default: selected sketch)"),
4420
+ layerId: import_zod2.z.string().describe("ID of the layer to update"),
4421
+ name: import_zod2.z.string().optional().describe("New display name"),
4422
+ properties: import_zod2.z.record(import_zod2.z.unknown()).optional().describe("Property key-value pairs to set")
4423
+ },
4424
+ async (args) => {
4425
+ try {
4426
+ const result = await designUpdateLayer(state, args);
4427
+ return jsonResult(result);
4428
+ } catch (e) {
4429
+ return toolError(e instanceof Error ? e.message : String(e));
4430
+ }
4431
+ }
4432
+ );
4433
+ server.tool(
4434
+ "design_set_transform",
4435
+ "Set the position, size, rotation, and scale of a design layer",
4436
+ {
4437
+ sketchId: import_zod2.z.string().optional().describe("Target sketch ID (default: selected sketch)"),
4438
+ layerId: import_zod2.z.string().describe("ID of the layer to transform"),
4439
+ x: import_zod2.z.number().optional().describe("X position"),
4440
+ y: import_zod2.z.number().optional().describe("Y position"),
4441
+ width: import_zod2.z.number().optional().describe("Width"),
4442
+ height: import_zod2.z.number().optional().describe("Height"),
4443
+ rotation: import_zod2.z.number().optional().describe("Rotation in degrees"),
4444
+ scaleX: import_zod2.z.number().optional().describe("Horizontal scale"),
4445
+ scaleY: import_zod2.z.number().optional().describe("Vertical scale"),
4446
+ anchorX: import_zod2.z.number().optional().describe("Anchor X (0\u20131)"),
4447
+ anchorY: import_zod2.z.number().optional().describe("Anchor Y (0\u20131)")
4448
+ },
4449
+ async (args) => {
4450
+ try {
4451
+ const result = await designSetTransform(state, args);
4452
+ return jsonResult(result);
4453
+ } catch (e) {
4454
+ return toolError(e instanceof Error ? e.message : String(e));
4455
+ }
4456
+ }
4457
+ );
4458
+ server.tool(
4459
+ "design_set_blend",
4460
+ "Set blend mode and/or opacity on a design layer",
4461
+ {
4462
+ sketchId: import_zod2.z.string().optional().describe("Target sketch ID (default: selected sketch)"),
4463
+ layerId: import_zod2.z.string().describe("ID of the layer"),
4464
+ blendMode: import_zod2.z.enum([
4465
+ "normal",
4466
+ "multiply",
4467
+ "screen",
4468
+ "overlay",
4469
+ "darken",
4470
+ "lighten",
4471
+ "color-dodge",
4472
+ "color-burn",
4473
+ "hard-light",
4474
+ "soft-light",
4475
+ "difference",
4476
+ "exclusion",
4477
+ "hue",
4478
+ "saturation",
4479
+ "color",
4480
+ "luminosity"
4481
+ ]).optional().describe("CSS blend mode"),
4482
+ opacity: import_zod2.z.number().optional().describe("Layer opacity 0\u20131")
4483
+ },
4484
+ async (args) => {
4485
+ try {
4486
+ const result = await designSetBlend(state, args);
4487
+ return jsonResult(result);
4488
+ } catch (e) {
4489
+ return toolError(e instanceof Error ? e.message : String(e));
4490
+ }
4491
+ }
4492
+ );
4493
+ server.tool(
4494
+ "design_reorder_layers",
4495
+ "Move a design layer to a new position in the z-order stack",
4496
+ {
4497
+ sketchId: import_zod2.z.string().optional().describe("Target sketch ID (default: selected sketch)"),
4498
+ layerId: import_zod2.z.string().describe("ID of the layer to move"),
4499
+ newIndex: import_zod2.z.number().describe("New position (0 = bottom, n-1 = top)")
4500
+ },
4501
+ async (args) => {
4502
+ try {
4503
+ const result = await designReorderLayers(state, args);
4504
+ return jsonResult(result);
4505
+ } catch (e) {
4506
+ return toolError(e instanceof Error ? e.message : String(e));
4507
+ }
4508
+ }
4509
+ );
4510
+ server.tool(
4511
+ "design_duplicate_layer",
4512
+ "Clone a design layer with a new ID, inserted directly above the source",
4513
+ {
4514
+ sketchId: import_zod2.z.string().optional().describe("Target sketch ID (default: selected sketch)"),
4515
+ layerId: import_zod2.z.string().describe("ID of the layer to duplicate")
4516
+ },
4517
+ async (args) => {
4518
+ try {
4519
+ const result = await designDuplicateLayer(state, args);
4520
+ return jsonResult(result);
4521
+ } catch (e) {
4522
+ return toolError(e instanceof Error ? e.message : String(e));
4523
+ }
4524
+ }
4525
+ );
4526
+ server.tool(
4527
+ "design_toggle_visibility",
4528
+ "Show or hide a design layer",
4529
+ {
4530
+ sketchId: import_zod2.z.string().optional().describe("Target sketch ID (default: selected sketch)"),
4531
+ layerId: import_zod2.z.string().describe("ID of the layer"),
4532
+ visible: import_zod2.z.boolean().optional().describe("Set visibility (default: toggle)")
4533
+ },
4534
+ async (args) => {
4535
+ try {
4536
+ const result = await designToggleVisibility(state, args);
4537
+ return jsonResult(result);
4538
+ } catch (e) {
4539
+ return toolError(e instanceof Error ? e.message : String(e));
4540
+ }
4541
+ }
4542
+ );
4543
+ server.tool(
4544
+ "design_lock_layer",
4545
+ "Lock or unlock a design layer to prevent accidental edits",
4546
+ {
4547
+ sketchId: import_zod2.z.string().optional().describe("Target sketch ID (default: selected sketch)"),
4548
+ layerId: import_zod2.z.string().describe("ID of the layer"),
4549
+ locked: import_zod2.z.boolean().optional().describe("Set lock state (default: toggle)")
4550
+ },
4551
+ async (args) => {
4552
+ try {
4553
+ const result = await designLockLayer(state, args);
4554
+ return jsonResult(result);
4555
+ } catch (e) {
4556
+ return toolError(e instanceof Error ? e.message : String(e));
4557
+ }
4558
+ }
4559
+ );
4560
+ server.tool(
4561
+ "design_capture_composite",
4562
+ "Get info about the design layer composite for a sketch. For full visual capture use capture_screenshot.",
4563
+ {
4564
+ sketchId: import_zod2.z.string().optional().describe("Target sketch ID (default: selected sketch)")
4565
+ },
4566
+ async (args) => {
4567
+ try {
4568
+ const result = await designCaptureComposite(state, args);
4569
+ return jsonResult(result);
4570
+ } catch (e) {
4571
+ return toolError(e instanceof Error ? e.message : String(e));
4572
+ }
4573
+ }
4574
+ );
4575
+ }
3624
4576
  function registerKnowledgeTools(server, _state) {
3625
4577
  server.tool(
3626
4578
  "list_skills",
@@ -3673,7 +4625,7 @@ function registerKnowledgeTools(server, _state) {
3673
4625
 
3674
4626
  // src/state.ts
3675
4627
  var import_events = require("events");
3676
- var import_promises7 = require("fs/promises");
4628
+ var import_promises8 = require("fs/promises");
3677
4629
  var import_path9 = require("path");
3678
4630
 
3679
4631
  // src/sidecar.ts
@@ -3689,8 +4641,8 @@ function notifyMutation(type, payload) {
3689
4641
  }
3690
4642
 
3691
4643
  // src/state.ts
3692
- var import_core10 = require("@genart-dev/core");
3693
- var import_promises8 = require("fs/promises");
4644
+ var import_core12 = require("@genart-dev/core");
4645
+ var import_promises9 = require("fs/promises");
3694
4646
  var EditorState = class extends import_events.EventEmitter {
3695
4647
  /** Absolute path to the active .genart-workspace file, or null. */
3696
4648
  workspacePath = null;
@@ -3712,6 +4664,10 @@ var EditorState = class extends import_events.EventEmitter {
3712
4664
  * instead of writing to disk. Set by mcp-host for HTTP-based sessions.
3713
4665
  */
3714
4666
  remoteMode = false;
4667
+ /** Plugin registry for design mode. Set during server initialization. */
4668
+ pluginRegistry = null;
4669
+ /** Layer stacks keyed by sketch ID. Created lazily when design tools are used. */
4670
+ layerStacks = /* @__PURE__ */ new Map();
3715
4671
  constructor(options) {
3716
4672
  super();
3717
4673
  if (options?.basePath) {
@@ -3769,13 +4725,14 @@ var EditorState = class extends import_events.EventEmitter {
3769
4725
  if (this.basePath && !absPath.startsWith(this.basePath)) {
3770
4726
  throw new Error(`Path escapes sandbox: ${absPath}`);
3771
4727
  }
3772
- const raw = await (0, import_promises7.readFile)(absPath, "utf-8");
4728
+ const raw = await (0, import_promises8.readFile)(absPath, "utf-8");
3773
4729
  const json = JSON.parse(raw);
3774
- const ws = (0, import_core10.parseWorkspace)(json);
4730
+ const ws = (0, import_core12.parseWorkspace)(json);
3775
4731
  this.workspacePath = absPath;
3776
4732
  this.workspace = ws;
3777
4733
  this.sketches.clear();
3778
4734
  this.selection.clear();
4735
+ this.layerStacks.clear();
3779
4736
  for (const ref of ws.sketches) {
3780
4737
  const sketchPath = this.resolveSketchPath(ref.file);
3781
4738
  await this.loadSketch(sketchPath);
@@ -3787,9 +4744,9 @@ var EditorState = class extends import_events.EventEmitter {
3787
4744
  if (this.basePath && !absPath.startsWith(this.basePath)) {
3788
4745
  throw new Error(`Path escapes sandbox: ${absPath}`);
3789
4746
  }
3790
- const raw = await (0, import_promises7.readFile)(absPath, "utf-8");
4747
+ const raw = await (0, import_promises8.readFile)(absPath, "utf-8");
3791
4748
  const json = JSON.parse(raw);
3792
- const definition = (0, import_core10.parseGenart)(json);
4749
+ const definition = (0, import_core12.parseGenart)(json);
3793
4750
  this.sketches.set(definition.id, { definition, path: absPath });
3794
4751
  this.emitMutation("sketch:loaded", { id: definition.id, path: absPath });
3795
4752
  return definition;
@@ -3817,6 +4774,7 @@ var EditorState = class extends import_events.EventEmitter {
3817
4774
  removeSketch(id) {
3818
4775
  this.sketches.delete(id);
3819
4776
  this.selection.delete(id);
4777
+ this.layerStacks.delete(id);
3820
4778
  this.emitMutation("sketch:removed", { id });
3821
4779
  }
3822
4780
  /** Save the active workspace to disk. */
@@ -3824,18 +4782,18 @@ var EditorState = class extends import_events.EventEmitter {
3824
4782
  if (!this.workspace || !this.workspacePath) {
3825
4783
  throw new Error("No workspace is currently open");
3826
4784
  }
3827
- const json = (0, import_core10.serializeWorkspace)(this.workspace);
4785
+ const json = (0, import_core12.serializeWorkspace)(this.workspace);
3828
4786
  if (!this.remoteMode) {
3829
- await (0, import_promises8.writeFile)(this.workspacePath, json, "utf-8");
4787
+ await (0, import_promises9.writeFile)(this.workspacePath, json, "utf-8");
3830
4788
  }
3831
4789
  this.emitMutation("workspace:saved", { path: this.workspacePath });
3832
4790
  }
3833
4791
  /** Save a sketch to disk. */
3834
4792
  async saveSketch(id) {
3835
4793
  const loaded = this.requireSketch(id);
3836
- const json = (0, import_core10.serializeGenart)(loaded.definition);
4794
+ const json = (0, import_core12.serializeGenart)(loaded.definition);
3837
4795
  if (!this.remoteMode) {
3838
- await (0, import_promises8.writeFile)(loaded.path, json, "utf-8");
4796
+ await (0, import_promises9.writeFile)(loaded.path, json, "utf-8");
3839
4797
  }
3840
4798
  this.emitMutation("sketch:saved", { id, path: loaded.path });
3841
4799
  }
@@ -3860,6 +4818,81 @@ var EditorState = class extends import_events.EventEmitter {
3860
4818
  selection: Array.from(this.selection)
3861
4819
  };
3862
4820
  }
4821
+ /**
4822
+ * Get or create a LayerStackAccessor for a sketch.
4823
+ * Initializes from the sketch's persisted design layers.
4824
+ */
4825
+ getLayerStack(sketchId) {
4826
+ let stack = this.layerStacks.get(sketchId);
4827
+ if (stack) return stack;
4828
+ const loaded = this.requireSketch(sketchId);
4829
+ const initialLayers = loaded.definition.layers ?? [];
4830
+ stack = (0, import_core12.createLayerStack)(initialLayers, (changeType) => {
4831
+ this.syncLayersToDefinition(sketchId);
4832
+ const mutationType = `design:${changeType}`;
4833
+ this.emitMutation(mutationType, { sketchId, changeType });
4834
+ });
4835
+ this.layerStacks.set(sketchId, stack);
4836
+ return stack;
4837
+ }
4838
+ /**
4839
+ * Sync the layer stack's current state back to the sketch definition.
4840
+ * Called automatically on every layer mutation.
4841
+ */
4842
+ syncLayersToDefinition(sketchId) {
4843
+ const loaded = this.sketches.get(sketchId);
4844
+ const stack = this.layerStacks.get(sketchId);
4845
+ if (!loaded || !stack) return;
4846
+ const layers = stack.getAll();
4847
+ loaded.definition = {
4848
+ ...loaded.definition,
4849
+ layers: layers.length > 0 ? layers : void 0
4850
+ };
4851
+ }
4852
+ /**
4853
+ * Create an McpToolContext for a plugin's MCP tool handler.
4854
+ * Provides access to the layer stack, sketch state, and change notifications.
4855
+ */
4856
+ createMcpToolContext(sketchId) {
4857
+ const loaded = this.requireSketch(sketchId);
4858
+ const layerStack = this.getLayerStack(sketchId);
4859
+ const def = loaded.definition;
4860
+ const sketchState = {
4861
+ seed: def.state.seed,
4862
+ params: def.state.params,
4863
+ colorPalette: def.state.colorPalette,
4864
+ canvasWidth: def.canvas.width,
4865
+ canvasHeight: def.canvas.height,
4866
+ rendererId: def.renderer.type
4867
+ };
4868
+ return {
4869
+ layers: layerStack,
4870
+ sketchState,
4871
+ canvasWidth: def.canvas.width,
4872
+ canvasHeight: def.canvas.height,
4873
+ async resolveAsset(_assetId) {
4874
+ return null;
4875
+ },
4876
+ async captureComposite(_format) {
4877
+ throw new Error("captureComposite is not available in headless MCP mode");
4878
+ },
4879
+ emitChange(_changeType) {
4880
+ }
4881
+ };
4882
+ }
4883
+ /**
4884
+ * Get the currently selected sketch ID for design operations.
4885
+ * Returns the single selected sketch, or throws if none/multiple selected.
4886
+ */
4887
+ requireSelectedSketchId() {
4888
+ if (this.selection.size === 0) {
4889
+ throw new Error("No sketch is selected. Use select_sketch or open_sketch first.");
4890
+ }
4891
+ if (this.selection.size > 1) {
4892
+ throw new Error("Multiple sketches are selected. Design operations require a single sketch.");
4893
+ }
4894
+ return this.selection.values().next().value;
4895
+ }
3863
4896
  /** Emit a mutation event for external listeners (WebSocket broadcast, sidecar IPC). */
3864
4897
  emitMutation(type, payload) {
3865
4898
  this.emit("mutation", { type, payload });