@genart-dev/mcp-server 0.2.0 → 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");
@@ -2690,6 +2695,330 @@ async function exportZip(sketch, input) {
2690
2695
  };
2691
2696
  }
2692
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
+
2693
3022
  // src/resources/index.ts
2694
3023
  var import_core10 = require("@genart-dev/core");
2695
3024
  function registerResources(server, state) {
@@ -3173,11 +3502,23 @@ function toolError(message) {
3173
3502
  isError: true
3174
3503
  };
3175
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
+ }
3176
3517
  function createServer(state) {
3177
3518
  const server = new import_mcp.McpServer(
3178
3519
  {
3179
3520
  name: "@genart/mcp-server",
3180
- version: "0.0.1"
3521
+ version: "0.3.0"
3181
3522
  },
3182
3523
  {
3183
3524
  capabilities: {
@@ -3187,6 +3528,11 @@ function createServer(state) {
3187
3528
  }
3188
3529
  }
3189
3530
  );
3531
+ const registryReady = initializePluginRegistry().then((registry4) => {
3532
+ state.pluginRegistry = registry4;
3533
+ registerPluginMcpTools(server, registry4, state);
3534
+ });
3535
+ server._pluginsReady = registryReady;
3190
3536
  registerWorkspaceTools(server, state);
3191
3537
  registerSketchTools(server, state);
3192
3538
  registerComponentTools(server, state);
@@ -3197,6 +3543,7 @@ function createServer(state) {
3197
3543
  registerMergeTools(server, state);
3198
3544
  registerSnapshotTools(server, state);
3199
3545
  registerKnowledgeTools(server, state);
3546
+ registerDesignTools(server, state);
3200
3547
  registerCaptureTools(server, state);
3201
3548
  registerExportTools(server, state);
3202
3549
  registerResources(server, state);
@@ -3985,6 +4332,247 @@ function registerExportTools(server, state) {
3985
4332
  }
3986
4333
  );
3987
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
+ }
3988
4576
  function registerKnowledgeTools(server, _state) {
3989
4577
  server.tool(
3990
4578
  "list_skills",
@@ -4053,7 +4641,7 @@ function notifyMutation(type, payload) {
4053
4641
  }
4054
4642
 
4055
4643
  // src/state.ts
4056
- var import_core11 = require("@genart-dev/core");
4644
+ var import_core12 = require("@genart-dev/core");
4057
4645
  var import_promises9 = require("fs/promises");
4058
4646
  var EditorState = class extends import_events.EventEmitter {
4059
4647
  /** Absolute path to the active .genart-workspace file, or null. */
@@ -4076,6 +4664,10 @@ var EditorState = class extends import_events.EventEmitter {
4076
4664
  * instead of writing to disk. Set by mcp-host for HTTP-based sessions.
4077
4665
  */
4078
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();
4079
4671
  constructor(options) {
4080
4672
  super();
4081
4673
  if (options?.basePath) {
@@ -4135,11 +4727,12 @@ var EditorState = class extends import_events.EventEmitter {
4135
4727
  }
4136
4728
  const raw = await (0, import_promises8.readFile)(absPath, "utf-8");
4137
4729
  const json = JSON.parse(raw);
4138
- const ws = (0, import_core11.parseWorkspace)(json);
4730
+ const ws = (0, import_core12.parseWorkspace)(json);
4139
4731
  this.workspacePath = absPath;
4140
4732
  this.workspace = ws;
4141
4733
  this.sketches.clear();
4142
4734
  this.selection.clear();
4735
+ this.layerStacks.clear();
4143
4736
  for (const ref of ws.sketches) {
4144
4737
  const sketchPath = this.resolveSketchPath(ref.file);
4145
4738
  await this.loadSketch(sketchPath);
@@ -4153,7 +4746,7 @@ var EditorState = class extends import_events.EventEmitter {
4153
4746
  }
4154
4747
  const raw = await (0, import_promises8.readFile)(absPath, "utf-8");
4155
4748
  const json = JSON.parse(raw);
4156
- const definition = (0, import_core11.parseGenart)(json);
4749
+ const definition = (0, import_core12.parseGenart)(json);
4157
4750
  this.sketches.set(definition.id, { definition, path: absPath });
4158
4751
  this.emitMutation("sketch:loaded", { id: definition.id, path: absPath });
4159
4752
  return definition;
@@ -4181,6 +4774,7 @@ var EditorState = class extends import_events.EventEmitter {
4181
4774
  removeSketch(id) {
4182
4775
  this.sketches.delete(id);
4183
4776
  this.selection.delete(id);
4777
+ this.layerStacks.delete(id);
4184
4778
  this.emitMutation("sketch:removed", { id });
4185
4779
  }
4186
4780
  /** Save the active workspace to disk. */
@@ -4188,7 +4782,7 @@ var EditorState = class extends import_events.EventEmitter {
4188
4782
  if (!this.workspace || !this.workspacePath) {
4189
4783
  throw new Error("No workspace is currently open");
4190
4784
  }
4191
- const json = (0, import_core11.serializeWorkspace)(this.workspace);
4785
+ const json = (0, import_core12.serializeWorkspace)(this.workspace);
4192
4786
  if (!this.remoteMode) {
4193
4787
  await (0, import_promises9.writeFile)(this.workspacePath, json, "utf-8");
4194
4788
  }
@@ -4197,7 +4791,7 @@ var EditorState = class extends import_events.EventEmitter {
4197
4791
  /** Save a sketch to disk. */
4198
4792
  async saveSketch(id) {
4199
4793
  const loaded = this.requireSketch(id);
4200
- const json = (0, import_core11.serializeGenart)(loaded.definition);
4794
+ const json = (0, import_core12.serializeGenart)(loaded.definition);
4201
4795
  if (!this.remoteMode) {
4202
4796
  await (0, import_promises9.writeFile)(loaded.path, json, "utf-8");
4203
4797
  }
@@ -4224,6 +4818,81 @@ var EditorState = class extends import_events.EventEmitter {
4224
4818
  selection: Array.from(this.selection)
4225
4819
  };
4226
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
+ }
4227
4896
  /** Emit a mutation event for external listeners (WebSocket broadcast, sidecar IPC). */
4228
4897
  emitMutation(type, payload) {
4229
4898
  this.emit("mutation", { type, payload });