@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/index.cjs +670 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +672 -2
- package/dist/index.js.map +1 -1
- package/dist/lib.cjs +675 -6
- package/dist/lib.cjs.map +1 -1
- package/dist/lib.d.cts +26 -2
- package/dist/lib.d.ts +26 -2
- package/dist/lib.js +672 -2
- package/dist/lib.js.map +1 -1
- package/package.json +6 -2
package/dist/lib.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
// src/server.ts
|
|
2
2
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
3
|
import { z as z2 } from "zod";
|
|
4
|
+
import { createPluginRegistry } from "@genart-dev/core";
|
|
5
|
+
import typographyPlugin from "@genart-dev/plugin-typography";
|
|
6
|
+
import filtersPlugin from "@genart-dev/plugin-filters";
|
|
7
|
+
import shapesPlugin from "@genart-dev/plugin-shapes";
|
|
8
|
+
import layoutGuidesPlugin from "@genart-dev/plugin-layout-guides";
|
|
4
9
|
|
|
5
10
|
// src/tools/workspace.ts
|
|
6
11
|
import { readFile, writeFile, stat } from "fs/promises";
|
|
@@ -2675,6 +2680,330 @@ async function exportZip(sketch, input) {
|
|
|
2675
2680
|
};
|
|
2676
2681
|
}
|
|
2677
2682
|
|
|
2683
|
+
// src/tools/design.ts
|
|
2684
|
+
function requireSketchId(state, args) {
|
|
2685
|
+
return args.sketchId ?? state.requireSelectedSketchId();
|
|
2686
|
+
}
|
|
2687
|
+
var BLEND_MODES = [
|
|
2688
|
+
"normal",
|
|
2689
|
+
"multiply",
|
|
2690
|
+
"screen",
|
|
2691
|
+
"overlay",
|
|
2692
|
+
"darken",
|
|
2693
|
+
"lighten",
|
|
2694
|
+
"color-dodge",
|
|
2695
|
+
"color-burn",
|
|
2696
|
+
"hard-light",
|
|
2697
|
+
"soft-light",
|
|
2698
|
+
"difference",
|
|
2699
|
+
"exclusion",
|
|
2700
|
+
"hue",
|
|
2701
|
+
"saturation",
|
|
2702
|
+
"color",
|
|
2703
|
+
"luminosity"
|
|
2704
|
+
];
|
|
2705
|
+
function generateLayerId() {
|
|
2706
|
+
return `layer-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
2707
|
+
}
|
|
2708
|
+
async function designAddLayer(state, args) {
|
|
2709
|
+
const sketchId = requireSketchId(state, args);
|
|
2710
|
+
const loaded = state.requireSketch(sketchId);
|
|
2711
|
+
const stack = state.getLayerStack(sketchId);
|
|
2712
|
+
const registry4 = state.pluginRegistry;
|
|
2713
|
+
const layerTypeDef = registry4?.resolveLayerType(args.type);
|
|
2714
|
+
if (!layerTypeDef) {
|
|
2715
|
+
throw new Error(
|
|
2716
|
+
`Unknown layer type: '${args.type}'. Use design_list_layers types from registered plugins.`
|
|
2717
|
+
);
|
|
2718
|
+
}
|
|
2719
|
+
const defaults = layerTypeDef.createDefault();
|
|
2720
|
+
const id = generateLayerId();
|
|
2721
|
+
const { width, height } = loaded.definition.canvas;
|
|
2722
|
+
const layer = {
|
|
2723
|
+
id,
|
|
2724
|
+
type: args.type,
|
|
2725
|
+
name: args.name ?? layerTypeDef.displayName,
|
|
2726
|
+
visible: true,
|
|
2727
|
+
locked: false,
|
|
2728
|
+
opacity: args.opacity ?? 1,
|
|
2729
|
+
blendMode: args.blendMode ?? "normal",
|
|
2730
|
+
transform: {
|
|
2731
|
+
x: 0,
|
|
2732
|
+
y: 0,
|
|
2733
|
+
width,
|
|
2734
|
+
height,
|
|
2735
|
+
rotation: 0,
|
|
2736
|
+
scaleX: 1,
|
|
2737
|
+
scaleY: 1,
|
|
2738
|
+
anchorX: 0.5,
|
|
2739
|
+
anchorY: 0.5,
|
|
2740
|
+
...args.transform
|
|
2741
|
+
},
|
|
2742
|
+
properties: { ...defaults, ...args.properties }
|
|
2743
|
+
};
|
|
2744
|
+
stack.add(layer, args.index);
|
|
2745
|
+
await state.saveSketch(sketchId);
|
|
2746
|
+
return {
|
|
2747
|
+
layerId: id,
|
|
2748
|
+
type: args.type,
|
|
2749
|
+
name: layer.name,
|
|
2750
|
+
index: args.index ?? stack.count - 1,
|
|
2751
|
+
sketchId
|
|
2752
|
+
};
|
|
2753
|
+
}
|
|
2754
|
+
async function designRemoveLayer(state, args) {
|
|
2755
|
+
const sketchId = requireSketchId(state, args);
|
|
2756
|
+
const stack = state.getLayerStack(sketchId);
|
|
2757
|
+
const removed = stack.remove(args.layerId);
|
|
2758
|
+
if (!removed) {
|
|
2759
|
+
throw new Error(`Layer '${args.layerId}' not found in sketch '${sketchId}'.`);
|
|
2760
|
+
}
|
|
2761
|
+
await state.saveSketch(sketchId);
|
|
2762
|
+
return { removed: true, layerId: args.layerId, sketchId };
|
|
2763
|
+
}
|
|
2764
|
+
async function designListLayers(state, args) {
|
|
2765
|
+
const sketchId = requireSketchId(state, args);
|
|
2766
|
+
const stack = state.getLayerStack(sketchId);
|
|
2767
|
+
const layers = stack.getAll();
|
|
2768
|
+
return {
|
|
2769
|
+
sketchId,
|
|
2770
|
+
count: layers.length,
|
|
2771
|
+
layers: layers.map((l, i) => ({
|
|
2772
|
+
index: i,
|
|
2773
|
+
id: l.id,
|
|
2774
|
+
type: l.type,
|
|
2775
|
+
name: l.name,
|
|
2776
|
+
visible: l.visible,
|
|
2777
|
+
locked: l.locked,
|
|
2778
|
+
opacity: l.opacity,
|
|
2779
|
+
blendMode: l.blendMode
|
|
2780
|
+
}))
|
|
2781
|
+
};
|
|
2782
|
+
}
|
|
2783
|
+
async function designGetLayer(state, args) {
|
|
2784
|
+
const sketchId = requireSketchId(state, args);
|
|
2785
|
+
const stack = state.getLayerStack(sketchId);
|
|
2786
|
+
const layer = stack.get(args.layerId);
|
|
2787
|
+
if (!layer) {
|
|
2788
|
+
throw new Error(`Layer '${args.layerId}' not found in sketch '${sketchId}'.`);
|
|
2789
|
+
}
|
|
2790
|
+
return {
|
|
2791
|
+
sketchId,
|
|
2792
|
+
layer: {
|
|
2793
|
+
id: layer.id,
|
|
2794
|
+
type: layer.type,
|
|
2795
|
+
name: layer.name,
|
|
2796
|
+
visible: layer.visible,
|
|
2797
|
+
locked: layer.locked,
|
|
2798
|
+
opacity: layer.opacity,
|
|
2799
|
+
blendMode: layer.blendMode,
|
|
2800
|
+
transform: layer.transform,
|
|
2801
|
+
properties: layer.properties
|
|
2802
|
+
}
|
|
2803
|
+
};
|
|
2804
|
+
}
|
|
2805
|
+
async function designUpdateLayer(state, args) {
|
|
2806
|
+
const sketchId = requireSketchId(state, args);
|
|
2807
|
+
const stack = state.getLayerStack(sketchId);
|
|
2808
|
+
const layer = stack.get(args.layerId);
|
|
2809
|
+
if (!layer) {
|
|
2810
|
+
throw new Error(`Layer '${args.layerId}' not found in sketch '${sketchId}'.`);
|
|
2811
|
+
}
|
|
2812
|
+
const updates = {};
|
|
2813
|
+
if (args.properties) {
|
|
2814
|
+
Object.assign(updates, args.properties);
|
|
2815
|
+
}
|
|
2816
|
+
if (Object.keys(updates).length > 0) {
|
|
2817
|
+
stack.updateProperties(args.layerId, updates);
|
|
2818
|
+
}
|
|
2819
|
+
if (args.name !== void 0) {
|
|
2820
|
+
const current = stack.get(args.layerId);
|
|
2821
|
+
stack.updateProperties(args.layerId, { ...current.properties });
|
|
2822
|
+
const mutableLayer = stack.get(args.layerId);
|
|
2823
|
+
mutableLayer.name = args.name;
|
|
2824
|
+
}
|
|
2825
|
+
await state.saveSketch(sketchId);
|
|
2826
|
+
return { updated: true, layerId: args.layerId, sketchId };
|
|
2827
|
+
}
|
|
2828
|
+
async function designSetTransform(state, args) {
|
|
2829
|
+
const sketchId = requireSketchId(state, args);
|
|
2830
|
+
const stack = state.getLayerStack(sketchId);
|
|
2831
|
+
const layer = stack.get(args.layerId);
|
|
2832
|
+
if (!layer) {
|
|
2833
|
+
throw new Error(`Layer '${args.layerId}' not found in sketch '${sketchId}'.`);
|
|
2834
|
+
}
|
|
2835
|
+
const partial = {};
|
|
2836
|
+
if (args.x !== void 0) partial.x = args.x;
|
|
2837
|
+
if (args.y !== void 0) partial.y = args.y;
|
|
2838
|
+
if (args.width !== void 0) partial.width = args.width;
|
|
2839
|
+
if (args.height !== void 0) partial.height = args.height;
|
|
2840
|
+
if (args.rotation !== void 0) partial.rotation = args.rotation;
|
|
2841
|
+
if (args.scaleX !== void 0) partial.scaleX = args.scaleX;
|
|
2842
|
+
if (args.scaleY !== void 0) partial.scaleY = args.scaleY;
|
|
2843
|
+
if (args.anchorX !== void 0) partial.anchorX = args.anchorX;
|
|
2844
|
+
if (args.anchorY !== void 0) partial.anchorY = args.anchorY;
|
|
2845
|
+
stack.updateTransform(args.layerId, partial);
|
|
2846
|
+
await state.saveSketch(sketchId);
|
|
2847
|
+
return {
|
|
2848
|
+
updated: true,
|
|
2849
|
+
layerId: args.layerId,
|
|
2850
|
+
transform: stack.get(args.layerId).transform,
|
|
2851
|
+
sketchId
|
|
2852
|
+
};
|
|
2853
|
+
}
|
|
2854
|
+
async function designSetBlend(state, args) {
|
|
2855
|
+
const sketchId = requireSketchId(state, args);
|
|
2856
|
+
const stack = state.getLayerStack(sketchId);
|
|
2857
|
+
const layer = stack.get(args.layerId);
|
|
2858
|
+
if (!layer) {
|
|
2859
|
+
throw new Error(`Layer '${args.layerId}' not found in sketch '${sketchId}'.`);
|
|
2860
|
+
}
|
|
2861
|
+
if (args.blendMode && !BLEND_MODES.includes(args.blendMode)) {
|
|
2862
|
+
throw new Error(
|
|
2863
|
+
`Invalid blend mode '${args.blendMode}'. Must be one of: ${BLEND_MODES.join(", ")}`
|
|
2864
|
+
);
|
|
2865
|
+
}
|
|
2866
|
+
stack.updateBlend(
|
|
2867
|
+
args.layerId,
|
|
2868
|
+
args.blendMode,
|
|
2869
|
+
args.opacity
|
|
2870
|
+
);
|
|
2871
|
+
await state.saveSketch(sketchId);
|
|
2872
|
+
const updated = stack.get(args.layerId);
|
|
2873
|
+
return {
|
|
2874
|
+
updated: true,
|
|
2875
|
+
layerId: args.layerId,
|
|
2876
|
+
blendMode: updated.blendMode,
|
|
2877
|
+
opacity: updated.opacity,
|
|
2878
|
+
sketchId
|
|
2879
|
+
};
|
|
2880
|
+
}
|
|
2881
|
+
async function designReorderLayers(state, args) {
|
|
2882
|
+
const sketchId = requireSketchId(state, args);
|
|
2883
|
+
const stack = state.getLayerStack(sketchId);
|
|
2884
|
+
const layer = stack.get(args.layerId);
|
|
2885
|
+
if (!layer) {
|
|
2886
|
+
throw new Error(`Layer '${args.layerId}' not found in sketch '${sketchId}'.`);
|
|
2887
|
+
}
|
|
2888
|
+
stack.reorder(args.layerId, args.newIndex);
|
|
2889
|
+
await state.saveSketch(sketchId);
|
|
2890
|
+
return {
|
|
2891
|
+
reordered: true,
|
|
2892
|
+
layerId: args.layerId,
|
|
2893
|
+
newIndex: args.newIndex,
|
|
2894
|
+
sketchId
|
|
2895
|
+
};
|
|
2896
|
+
}
|
|
2897
|
+
async function designDuplicateLayer(state, args) {
|
|
2898
|
+
const sketchId = requireSketchId(state, args);
|
|
2899
|
+
const stack = state.getLayerStack(sketchId);
|
|
2900
|
+
const layer = stack.get(args.layerId);
|
|
2901
|
+
if (!layer) {
|
|
2902
|
+
throw new Error(`Layer '${args.layerId}' not found in sketch '${sketchId}'.`);
|
|
2903
|
+
}
|
|
2904
|
+
const newId = stack.duplicate(args.layerId);
|
|
2905
|
+
await state.saveSketch(sketchId);
|
|
2906
|
+
return {
|
|
2907
|
+
duplicated: true,
|
|
2908
|
+
sourceLayerId: args.layerId,
|
|
2909
|
+
newLayerId: newId,
|
|
2910
|
+
sketchId
|
|
2911
|
+
};
|
|
2912
|
+
}
|
|
2913
|
+
async function designToggleVisibility(state, args) {
|
|
2914
|
+
const sketchId = requireSketchId(state, args);
|
|
2915
|
+
const stack = state.getLayerStack(sketchId);
|
|
2916
|
+
const layer = stack.get(args.layerId);
|
|
2917
|
+
if (!layer) {
|
|
2918
|
+
throw new Error(`Layer '${args.layerId}' not found in sketch '${sketchId}'.`);
|
|
2919
|
+
}
|
|
2920
|
+
const newVisible = args.visible ?? !layer.visible;
|
|
2921
|
+
const mutableLayer = layer;
|
|
2922
|
+
mutableLayer.visible = newVisible;
|
|
2923
|
+
stack.updateProperties(args.layerId, { ...layer.properties });
|
|
2924
|
+
await state.saveSketch(sketchId);
|
|
2925
|
+
return {
|
|
2926
|
+
layerId: args.layerId,
|
|
2927
|
+
visible: newVisible,
|
|
2928
|
+
sketchId
|
|
2929
|
+
};
|
|
2930
|
+
}
|
|
2931
|
+
async function designLockLayer(state, args) {
|
|
2932
|
+
const sketchId = requireSketchId(state, args);
|
|
2933
|
+
const stack = state.getLayerStack(sketchId);
|
|
2934
|
+
const layer = stack.get(args.layerId);
|
|
2935
|
+
if (!layer) {
|
|
2936
|
+
throw new Error(`Layer '${args.layerId}' not found in sketch '${sketchId}'.`);
|
|
2937
|
+
}
|
|
2938
|
+
const newLocked = args.locked ?? !layer.locked;
|
|
2939
|
+
const mutableLayer = layer;
|
|
2940
|
+
mutableLayer.locked = newLocked;
|
|
2941
|
+
stack.updateProperties(args.layerId, { ...layer.properties });
|
|
2942
|
+
await state.saveSketch(sketchId);
|
|
2943
|
+
return {
|
|
2944
|
+
layerId: args.layerId,
|
|
2945
|
+
locked: newLocked,
|
|
2946
|
+
sketchId
|
|
2947
|
+
};
|
|
2948
|
+
}
|
|
2949
|
+
async function designCaptureComposite(state, args) {
|
|
2950
|
+
const sketchId = requireSketchId(state, args);
|
|
2951
|
+
const stack = state.getLayerStack(sketchId);
|
|
2952
|
+
const layers = stack.getAll();
|
|
2953
|
+
return {
|
|
2954
|
+
sketchId,
|
|
2955
|
+
layerCount: layers.length,
|
|
2956
|
+
visibleCount: layers.filter((l) => l.visible).length,
|
|
2957
|
+
message: "Composite capture requires a rendering surface. Use capture_screenshot to get a rasterized preview of the sketch, then use design_list_layers to see the design layer stack."
|
|
2958
|
+
};
|
|
2959
|
+
}
|
|
2960
|
+
|
|
2961
|
+
// src/tools/design-plugins.ts
|
|
2962
|
+
function registerPluginMcpTools(server, registry4, state) {
|
|
2963
|
+
for (const tool of registry4.getMcpTools()) {
|
|
2964
|
+
const inputSchema = tool.definition.inputSchema;
|
|
2965
|
+
server.tool(
|
|
2966
|
+
tool.name,
|
|
2967
|
+
tool.definition.description,
|
|
2968
|
+
// Pass raw JSON schema — MCP SDK accepts this alongside Zod
|
|
2969
|
+
inputSchema,
|
|
2970
|
+
async (args) => {
|
|
2971
|
+
try {
|
|
2972
|
+
const sketchId = args.sketchId ?? state.requireSelectedSketchId();
|
|
2973
|
+
const context = state.createMcpToolContext(sketchId);
|
|
2974
|
+
const result = await tool.definition.handler(args, context);
|
|
2975
|
+
await state.saveSketch(sketchId);
|
|
2976
|
+
return {
|
|
2977
|
+
content: result.content.map((c) => {
|
|
2978
|
+
if (c.type === "text") {
|
|
2979
|
+
return { type: "text", text: c.text };
|
|
2980
|
+
}
|
|
2981
|
+
return {
|
|
2982
|
+
type: "image",
|
|
2983
|
+
data: c.data,
|
|
2984
|
+
mimeType: c.mimeType
|
|
2985
|
+
};
|
|
2986
|
+
}),
|
|
2987
|
+
isError: result.isError
|
|
2988
|
+
};
|
|
2989
|
+
} catch (e) {
|
|
2990
|
+
return {
|
|
2991
|
+
content: [
|
|
2992
|
+
{
|
|
2993
|
+
type: "text",
|
|
2994
|
+
text: JSON.stringify({
|
|
2995
|
+
error: e instanceof Error ? e.message : String(e)
|
|
2996
|
+
})
|
|
2997
|
+
}
|
|
2998
|
+
],
|
|
2999
|
+
isError: true
|
|
3000
|
+
};
|
|
3001
|
+
}
|
|
3002
|
+
}
|
|
3003
|
+
);
|
|
3004
|
+
}
|
|
3005
|
+
}
|
|
3006
|
+
|
|
2678
3007
|
// src/resources/index.ts
|
|
2679
3008
|
import {
|
|
2680
3009
|
CANVAS_PRESETS,
|
|
@@ -3162,11 +3491,23 @@ function toolError(message) {
|
|
|
3162
3491
|
isError: true
|
|
3163
3492
|
};
|
|
3164
3493
|
}
|
|
3494
|
+
async function initializePluginRegistry() {
|
|
3495
|
+
const registry4 = createPluginRegistry({
|
|
3496
|
+
surface: "mcp",
|
|
3497
|
+
supportsInteractiveTools: false,
|
|
3498
|
+
supportsRendering: false
|
|
3499
|
+
});
|
|
3500
|
+
await registry4.register(typographyPlugin);
|
|
3501
|
+
await registry4.register(filtersPlugin);
|
|
3502
|
+
await registry4.register(shapesPlugin);
|
|
3503
|
+
await registry4.register(layoutGuidesPlugin);
|
|
3504
|
+
return registry4;
|
|
3505
|
+
}
|
|
3165
3506
|
function createServer(state) {
|
|
3166
3507
|
const server = new McpServer(
|
|
3167
3508
|
{
|
|
3168
3509
|
name: "@genart/mcp-server",
|
|
3169
|
-
version: "0.0
|
|
3510
|
+
version: "0.3.0"
|
|
3170
3511
|
},
|
|
3171
3512
|
{
|
|
3172
3513
|
capabilities: {
|
|
@@ -3176,6 +3517,11 @@ function createServer(state) {
|
|
|
3176
3517
|
}
|
|
3177
3518
|
}
|
|
3178
3519
|
);
|
|
3520
|
+
const registryReady = initializePluginRegistry().then((registry4) => {
|
|
3521
|
+
state.pluginRegistry = registry4;
|
|
3522
|
+
registerPluginMcpTools(server, registry4, state);
|
|
3523
|
+
});
|
|
3524
|
+
server._pluginsReady = registryReady;
|
|
3179
3525
|
registerWorkspaceTools(server, state);
|
|
3180
3526
|
registerSketchTools(server, state);
|
|
3181
3527
|
registerComponentTools(server, state);
|
|
@@ -3186,6 +3532,7 @@ function createServer(state) {
|
|
|
3186
3532
|
registerMergeTools(server, state);
|
|
3187
3533
|
registerSnapshotTools(server, state);
|
|
3188
3534
|
registerKnowledgeTools(server, state);
|
|
3535
|
+
registerDesignTools(server, state);
|
|
3189
3536
|
registerCaptureTools(server, state);
|
|
3190
3537
|
registerExportTools(server, state);
|
|
3191
3538
|
registerResources(server, state);
|
|
@@ -3974,6 +4321,247 @@ function registerExportTools(server, state) {
|
|
|
3974
4321
|
}
|
|
3975
4322
|
);
|
|
3976
4323
|
}
|
|
4324
|
+
function registerDesignTools(server, state) {
|
|
4325
|
+
server.tool(
|
|
4326
|
+
"design_add_layer",
|
|
4327
|
+
"Add a new design layer of a given type to the active sketch. Layer types come from registered plugins (e.g. 'typography:text', 'filter:grain', 'shapes:rect', 'guides:thirds').",
|
|
4328
|
+
{
|
|
4329
|
+
sketchId: z2.string().optional().describe("Target sketch ID (default: selected sketch)"),
|
|
4330
|
+
type: z2.string().describe("Layer type ID (e.g. 'typography:text', 'filter:grain', 'shapes:rect')"),
|
|
4331
|
+
name: z2.string().optional().describe("Layer display name (default: type's display name)"),
|
|
4332
|
+
properties: z2.record(z2.unknown()).optional().describe("Initial layer properties (merged with type defaults)"),
|
|
4333
|
+
transform: z2.object({
|
|
4334
|
+
x: z2.number().optional(),
|
|
4335
|
+
y: z2.number().optional(),
|
|
4336
|
+
width: z2.number().optional(),
|
|
4337
|
+
height: z2.number().optional(),
|
|
4338
|
+
rotation: z2.number().optional(),
|
|
4339
|
+
scaleX: z2.number().optional(),
|
|
4340
|
+
scaleY: z2.number().optional(),
|
|
4341
|
+
anchorX: z2.number().optional(),
|
|
4342
|
+
anchorY: z2.number().optional()
|
|
4343
|
+
}).optional().describe("Layer transform (default: full canvas)"),
|
|
4344
|
+
opacity: z2.number().optional().describe("Layer opacity 0\u20131 (default: 1)"),
|
|
4345
|
+
blendMode: z2.string().optional().describe("Blend mode (default: 'normal')"),
|
|
4346
|
+
index: z2.number().optional().describe("Insert position in layer stack (default: top)")
|
|
4347
|
+
},
|
|
4348
|
+
async (args) => {
|
|
4349
|
+
try {
|
|
4350
|
+
const result = await designAddLayer(state, args);
|
|
4351
|
+
return jsonResult(result);
|
|
4352
|
+
} catch (e) {
|
|
4353
|
+
return toolError(e instanceof Error ? e.message : String(e));
|
|
4354
|
+
}
|
|
4355
|
+
}
|
|
4356
|
+
);
|
|
4357
|
+
server.tool(
|
|
4358
|
+
"design_remove_layer",
|
|
4359
|
+
"Remove a design layer from the active sketch",
|
|
4360
|
+
{
|
|
4361
|
+
sketchId: z2.string().optional().describe("Target sketch ID (default: selected sketch)"),
|
|
4362
|
+
layerId: z2.string().describe("ID of the layer to remove")
|
|
4363
|
+
},
|
|
4364
|
+
async (args) => {
|
|
4365
|
+
try {
|
|
4366
|
+
const result = await designRemoveLayer(state, args);
|
|
4367
|
+
return jsonResult(result);
|
|
4368
|
+
} catch (e) {
|
|
4369
|
+
return toolError(e instanceof Error ? e.message : String(e));
|
|
4370
|
+
}
|
|
4371
|
+
}
|
|
4372
|
+
);
|
|
4373
|
+
server.tool(
|
|
4374
|
+
"design_list_layers",
|
|
4375
|
+
"List all design layers in the active sketch with their types, visibility, and key properties",
|
|
4376
|
+
{
|
|
4377
|
+
sketchId: z2.string().optional().describe("Target sketch ID (default: selected sketch)")
|
|
4378
|
+
},
|
|
4379
|
+
async (args) => {
|
|
4380
|
+
try {
|
|
4381
|
+
const result = await designListLayers(state, args);
|
|
4382
|
+
return jsonResult(result);
|
|
4383
|
+
} catch (e) {
|
|
4384
|
+
return toolError(e instanceof Error ? e.message : String(e));
|
|
4385
|
+
}
|
|
4386
|
+
}
|
|
4387
|
+
);
|
|
4388
|
+
server.tool(
|
|
4389
|
+
"design_get_layer",
|
|
4390
|
+
"Get full details of a single design layer including all properties and transform",
|
|
4391
|
+
{
|
|
4392
|
+
sketchId: z2.string().optional().describe("Target sketch ID (default: selected sketch)"),
|
|
4393
|
+
layerId: z2.string().describe("ID of the layer to inspect")
|
|
4394
|
+
},
|
|
4395
|
+
async (args) => {
|
|
4396
|
+
try {
|
|
4397
|
+
const result = await designGetLayer(state, args);
|
|
4398
|
+
return jsonResult(result);
|
|
4399
|
+
} catch (e) {
|
|
4400
|
+
return toolError(e instanceof Error ? e.message : String(e));
|
|
4401
|
+
}
|
|
4402
|
+
}
|
|
4403
|
+
);
|
|
4404
|
+
server.tool(
|
|
4405
|
+
"design_update_layer",
|
|
4406
|
+
"Update properties on a design layer (e.g. text content, filter intensity, shape fill color)",
|
|
4407
|
+
{
|
|
4408
|
+
sketchId: z2.string().optional().describe("Target sketch ID (default: selected sketch)"),
|
|
4409
|
+
layerId: z2.string().describe("ID of the layer to update"),
|
|
4410
|
+
name: z2.string().optional().describe("New display name"),
|
|
4411
|
+
properties: z2.record(z2.unknown()).optional().describe("Property key-value pairs to set")
|
|
4412
|
+
},
|
|
4413
|
+
async (args) => {
|
|
4414
|
+
try {
|
|
4415
|
+
const result = await designUpdateLayer(state, args);
|
|
4416
|
+
return jsonResult(result);
|
|
4417
|
+
} catch (e) {
|
|
4418
|
+
return toolError(e instanceof Error ? e.message : String(e));
|
|
4419
|
+
}
|
|
4420
|
+
}
|
|
4421
|
+
);
|
|
4422
|
+
server.tool(
|
|
4423
|
+
"design_set_transform",
|
|
4424
|
+
"Set the position, size, rotation, and scale of a design layer",
|
|
4425
|
+
{
|
|
4426
|
+
sketchId: z2.string().optional().describe("Target sketch ID (default: selected sketch)"),
|
|
4427
|
+
layerId: z2.string().describe("ID of the layer to transform"),
|
|
4428
|
+
x: z2.number().optional().describe("X position"),
|
|
4429
|
+
y: z2.number().optional().describe("Y position"),
|
|
4430
|
+
width: z2.number().optional().describe("Width"),
|
|
4431
|
+
height: z2.number().optional().describe("Height"),
|
|
4432
|
+
rotation: z2.number().optional().describe("Rotation in degrees"),
|
|
4433
|
+
scaleX: z2.number().optional().describe("Horizontal scale"),
|
|
4434
|
+
scaleY: z2.number().optional().describe("Vertical scale"),
|
|
4435
|
+
anchorX: z2.number().optional().describe("Anchor X (0\u20131)"),
|
|
4436
|
+
anchorY: z2.number().optional().describe("Anchor Y (0\u20131)")
|
|
4437
|
+
},
|
|
4438
|
+
async (args) => {
|
|
4439
|
+
try {
|
|
4440
|
+
const result = await designSetTransform(state, args);
|
|
4441
|
+
return jsonResult(result);
|
|
4442
|
+
} catch (e) {
|
|
4443
|
+
return toolError(e instanceof Error ? e.message : String(e));
|
|
4444
|
+
}
|
|
4445
|
+
}
|
|
4446
|
+
);
|
|
4447
|
+
server.tool(
|
|
4448
|
+
"design_set_blend",
|
|
4449
|
+
"Set blend mode and/or opacity on a design layer",
|
|
4450
|
+
{
|
|
4451
|
+
sketchId: z2.string().optional().describe("Target sketch ID (default: selected sketch)"),
|
|
4452
|
+
layerId: z2.string().describe("ID of the layer"),
|
|
4453
|
+
blendMode: z2.enum([
|
|
4454
|
+
"normal",
|
|
4455
|
+
"multiply",
|
|
4456
|
+
"screen",
|
|
4457
|
+
"overlay",
|
|
4458
|
+
"darken",
|
|
4459
|
+
"lighten",
|
|
4460
|
+
"color-dodge",
|
|
4461
|
+
"color-burn",
|
|
4462
|
+
"hard-light",
|
|
4463
|
+
"soft-light",
|
|
4464
|
+
"difference",
|
|
4465
|
+
"exclusion",
|
|
4466
|
+
"hue",
|
|
4467
|
+
"saturation",
|
|
4468
|
+
"color",
|
|
4469
|
+
"luminosity"
|
|
4470
|
+
]).optional().describe("CSS blend mode"),
|
|
4471
|
+
opacity: z2.number().optional().describe("Layer opacity 0\u20131")
|
|
4472
|
+
},
|
|
4473
|
+
async (args) => {
|
|
4474
|
+
try {
|
|
4475
|
+
const result = await designSetBlend(state, args);
|
|
4476
|
+
return jsonResult(result);
|
|
4477
|
+
} catch (e) {
|
|
4478
|
+
return toolError(e instanceof Error ? e.message : String(e));
|
|
4479
|
+
}
|
|
4480
|
+
}
|
|
4481
|
+
);
|
|
4482
|
+
server.tool(
|
|
4483
|
+
"design_reorder_layers",
|
|
4484
|
+
"Move a design layer to a new position in the z-order stack",
|
|
4485
|
+
{
|
|
4486
|
+
sketchId: z2.string().optional().describe("Target sketch ID (default: selected sketch)"),
|
|
4487
|
+
layerId: z2.string().describe("ID of the layer to move"),
|
|
4488
|
+
newIndex: z2.number().describe("New position (0 = bottom, n-1 = top)")
|
|
4489
|
+
},
|
|
4490
|
+
async (args) => {
|
|
4491
|
+
try {
|
|
4492
|
+
const result = await designReorderLayers(state, args);
|
|
4493
|
+
return jsonResult(result);
|
|
4494
|
+
} catch (e) {
|
|
4495
|
+
return toolError(e instanceof Error ? e.message : String(e));
|
|
4496
|
+
}
|
|
4497
|
+
}
|
|
4498
|
+
);
|
|
4499
|
+
server.tool(
|
|
4500
|
+
"design_duplicate_layer",
|
|
4501
|
+
"Clone a design layer with a new ID, inserted directly above the source",
|
|
4502
|
+
{
|
|
4503
|
+
sketchId: z2.string().optional().describe("Target sketch ID (default: selected sketch)"),
|
|
4504
|
+
layerId: z2.string().describe("ID of the layer to duplicate")
|
|
4505
|
+
},
|
|
4506
|
+
async (args) => {
|
|
4507
|
+
try {
|
|
4508
|
+
const result = await designDuplicateLayer(state, args);
|
|
4509
|
+
return jsonResult(result);
|
|
4510
|
+
} catch (e) {
|
|
4511
|
+
return toolError(e instanceof Error ? e.message : String(e));
|
|
4512
|
+
}
|
|
4513
|
+
}
|
|
4514
|
+
);
|
|
4515
|
+
server.tool(
|
|
4516
|
+
"design_toggle_visibility",
|
|
4517
|
+
"Show or hide a design layer",
|
|
4518
|
+
{
|
|
4519
|
+
sketchId: z2.string().optional().describe("Target sketch ID (default: selected sketch)"),
|
|
4520
|
+
layerId: z2.string().describe("ID of the layer"),
|
|
4521
|
+
visible: z2.boolean().optional().describe("Set visibility (default: toggle)")
|
|
4522
|
+
},
|
|
4523
|
+
async (args) => {
|
|
4524
|
+
try {
|
|
4525
|
+
const result = await designToggleVisibility(state, args);
|
|
4526
|
+
return jsonResult(result);
|
|
4527
|
+
} catch (e) {
|
|
4528
|
+
return toolError(e instanceof Error ? e.message : String(e));
|
|
4529
|
+
}
|
|
4530
|
+
}
|
|
4531
|
+
);
|
|
4532
|
+
server.tool(
|
|
4533
|
+
"design_lock_layer",
|
|
4534
|
+
"Lock or unlock a design layer to prevent accidental edits",
|
|
4535
|
+
{
|
|
4536
|
+
sketchId: z2.string().optional().describe("Target sketch ID (default: selected sketch)"),
|
|
4537
|
+
layerId: z2.string().describe("ID of the layer"),
|
|
4538
|
+
locked: z2.boolean().optional().describe("Set lock state (default: toggle)")
|
|
4539
|
+
},
|
|
4540
|
+
async (args) => {
|
|
4541
|
+
try {
|
|
4542
|
+
const result = await designLockLayer(state, args);
|
|
4543
|
+
return jsonResult(result);
|
|
4544
|
+
} catch (e) {
|
|
4545
|
+
return toolError(e instanceof Error ? e.message : String(e));
|
|
4546
|
+
}
|
|
4547
|
+
}
|
|
4548
|
+
);
|
|
4549
|
+
server.tool(
|
|
4550
|
+
"design_capture_composite",
|
|
4551
|
+
"Get info about the design layer composite for a sketch. For full visual capture use capture_screenshot.",
|
|
4552
|
+
{
|
|
4553
|
+
sketchId: z2.string().optional().describe("Target sketch ID (default: selected sketch)")
|
|
4554
|
+
},
|
|
4555
|
+
async (args) => {
|
|
4556
|
+
try {
|
|
4557
|
+
const result = await designCaptureComposite(state, args);
|
|
4558
|
+
return jsonResult(result);
|
|
4559
|
+
} catch (e) {
|
|
4560
|
+
return toolError(e instanceof Error ? e.message : String(e));
|
|
4561
|
+
}
|
|
4562
|
+
}
|
|
4563
|
+
);
|
|
4564
|
+
}
|
|
3977
4565
|
function registerKnowledgeTools(server, _state) {
|
|
3978
4566
|
server.tool(
|
|
3979
4567
|
"list_skills",
|
|
@@ -4046,7 +4634,8 @@ import {
|
|
|
4046
4634
|
parseGenart as parseGenart4,
|
|
4047
4635
|
parseWorkspace as parseWorkspace2,
|
|
4048
4636
|
serializeGenart as serializeGenart5,
|
|
4049
|
-
serializeWorkspace as serializeWorkspace3
|
|
4637
|
+
serializeWorkspace as serializeWorkspace3,
|
|
4638
|
+
createLayerStack
|
|
4050
4639
|
} from "@genart-dev/core";
|
|
4051
4640
|
import { writeFile as writeFile7 } from "fs/promises";
|
|
4052
4641
|
var EditorState = class extends EventEmitter {
|
|
@@ -4070,6 +4659,10 @@ var EditorState = class extends EventEmitter {
|
|
|
4070
4659
|
* instead of writing to disk. Set by mcp-host for HTTP-based sessions.
|
|
4071
4660
|
*/
|
|
4072
4661
|
remoteMode = false;
|
|
4662
|
+
/** Plugin registry for design mode. Set during server initialization. */
|
|
4663
|
+
pluginRegistry = null;
|
|
4664
|
+
/** Layer stacks keyed by sketch ID. Created lazily when design tools are used. */
|
|
4665
|
+
layerStacks = /* @__PURE__ */ new Map();
|
|
4073
4666
|
constructor(options) {
|
|
4074
4667
|
super();
|
|
4075
4668
|
if (options?.basePath) {
|
|
@@ -4134,6 +4727,7 @@ var EditorState = class extends EventEmitter {
|
|
|
4134
4727
|
this.workspace = ws;
|
|
4135
4728
|
this.sketches.clear();
|
|
4136
4729
|
this.selection.clear();
|
|
4730
|
+
this.layerStacks.clear();
|
|
4137
4731
|
for (const ref of ws.sketches) {
|
|
4138
4732
|
const sketchPath = this.resolveSketchPath(ref.file);
|
|
4139
4733
|
await this.loadSketch(sketchPath);
|
|
@@ -4175,6 +4769,7 @@ var EditorState = class extends EventEmitter {
|
|
|
4175
4769
|
removeSketch(id) {
|
|
4176
4770
|
this.sketches.delete(id);
|
|
4177
4771
|
this.selection.delete(id);
|
|
4772
|
+
this.layerStacks.delete(id);
|
|
4178
4773
|
this.emitMutation("sketch:removed", { id });
|
|
4179
4774
|
}
|
|
4180
4775
|
/** Save the active workspace to disk. */
|
|
@@ -4218,6 +4813,81 @@ var EditorState = class extends EventEmitter {
|
|
|
4218
4813
|
selection: Array.from(this.selection)
|
|
4219
4814
|
};
|
|
4220
4815
|
}
|
|
4816
|
+
/**
|
|
4817
|
+
* Get or create a LayerStackAccessor for a sketch.
|
|
4818
|
+
* Initializes from the sketch's persisted design layers.
|
|
4819
|
+
*/
|
|
4820
|
+
getLayerStack(sketchId) {
|
|
4821
|
+
let stack = this.layerStacks.get(sketchId);
|
|
4822
|
+
if (stack) return stack;
|
|
4823
|
+
const loaded = this.requireSketch(sketchId);
|
|
4824
|
+
const initialLayers = loaded.definition.layers ?? [];
|
|
4825
|
+
stack = createLayerStack(initialLayers, (changeType) => {
|
|
4826
|
+
this.syncLayersToDefinition(sketchId);
|
|
4827
|
+
const mutationType = `design:${changeType}`;
|
|
4828
|
+
this.emitMutation(mutationType, { sketchId, changeType });
|
|
4829
|
+
});
|
|
4830
|
+
this.layerStacks.set(sketchId, stack);
|
|
4831
|
+
return stack;
|
|
4832
|
+
}
|
|
4833
|
+
/**
|
|
4834
|
+
* Sync the layer stack's current state back to the sketch definition.
|
|
4835
|
+
* Called automatically on every layer mutation.
|
|
4836
|
+
*/
|
|
4837
|
+
syncLayersToDefinition(sketchId) {
|
|
4838
|
+
const loaded = this.sketches.get(sketchId);
|
|
4839
|
+
const stack = this.layerStacks.get(sketchId);
|
|
4840
|
+
if (!loaded || !stack) return;
|
|
4841
|
+
const layers = stack.getAll();
|
|
4842
|
+
loaded.definition = {
|
|
4843
|
+
...loaded.definition,
|
|
4844
|
+
layers: layers.length > 0 ? layers : void 0
|
|
4845
|
+
};
|
|
4846
|
+
}
|
|
4847
|
+
/**
|
|
4848
|
+
* Create an McpToolContext for a plugin's MCP tool handler.
|
|
4849
|
+
* Provides access to the layer stack, sketch state, and change notifications.
|
|
4850
|
+
*/
|
|
4851
|
+
createMcpToolContext(sketchId) {
|
|
4852
|
+
const loaded = this.requireSketch(sketchId);
|
|
4853
|
+
const layerStack = this.getLayerStack(sketchId);
|
|
4854
|
+
const def = loaded.definition;
|
|
4855
|
+
const sketchState = {
|
|
4856
|
+
seed: def.state.seed,
|
|
4857
|
+
params: def.state.params,
|
|
4858
|
+
colorPalette: def.state.colorPalette,
|
|
4859
|
+
canvasWidth: def.canvas.width,
|
|
4860
|
+
canvasHeight: def.canvas.height,
|
|
4861
|
+
rendererId: def.renderer.type
|
|
4862
|
+
};
|
|
4863
|
+
return {
|
|
4864
|
+
layers: layerStack,
|
|
4865
|
+
sketchState,
|
|
4866
|
+
canvasWidth: def.canvas.width,
|
|
4867
|
+
canvasHeight: def.canvas.height,
|
|
4868
|
+
async resolveAsset(_assetId) {
|
|
4869
|
+
return null;
|
|
4870
|
+
},
|
|
4871
|
+
async captureComposite(_format) {
|
|
4872
|
+
throw new Error("captureComposite is not available in headless MCP mode");
|
|
4873
|
+
},
|
|
4874
|
+
emitChange(_changeType) {
|
|
4875
|
+
}
|
|
4876
|
+
};
|
|
4877
|
+
}
|
|
4878
|
+
/**
|
|
4879
|
+
* Get the currently selected sketch ID for design operations.
|
|
4880
|
+
* Returns the single selected sketch, or throws if none/multiple selected.
|
|
4881
|
+
*/
|
|
4882
|
+
requireSelectedSketchId() {
|
|
4883
|
+
if (this.selection.size === 0) {
|
|
4884
|
+
throw new Error("No sketch is selected. Use select_sketch or open_sketch first.");
|
|
4885
|
+
}
|
|
4886
|
+
if (this.selection.size > 1) {
|
|
4887
|
+
throw new Error("Multiple sketches are selected. Design operations require a single sketch.");
|
|
4888
|
+
}
|
|
4889
|
+
return this.selection.values().next().value;
|
|
4890
|
+
}
|
|
4221
4891
|
/** Emit a mutation event for external listeners (WebSocket broadcast, sidecar IPC). */
|
|
4222
4892
|
emitMutation(type, payload) {
|
|
4223
4893
|
this.emit("mutation", { type, payload });
|