@ufira/vibma 0.2.1 → 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/mcp.cjs +810 -1430
- package/dist/mcp.cjs.map +1 -1
- package/dist/mcp.js +809 -1431
- package/dist/mcp.js.map +1 -1
- package/dist/tools/endpoint.cjs +119 -0
- package/dist/tools/endpoint.cjs.map +1 -0
- package/dist/tools/endpoint.d.cts +94 -0
- package/dist/tools/endpoint.d.ts +94 -0
- package/dist/tools/endpoint.js +92 -0
- package/dist/tools/endpoint.js.map +1 -0
- package/dist/tools/registry.cjs +73 -0
- package/dist/tools/registry.cjs.map +1 -0
- package/dist/tools/registry.d.cts +14 -0
- package/dist/tools/registry.d.ts +14 -0
- package/dist/tools/registry.js +47 -0
- package/dist/tools/registry.js.map +1 -0
- package/dist/tools/schemas.cjs +101 -0
- package/dist/tools/schemas.cjs.map +1 -0
- package/dist/tools/schemas.d.cts +52 -0
- package/dist/tools/schemas.d.ts +52 -0
- package/dist/tools/schemas.js +70 -0
- package/dist/tools/schemas.js.map +1 -0
- package/dist/tools/types.cjs +52 -0
- package/dist/tools/types.cjs.map +1 -0
- package/dist/tools/types.d.cts +53 -0
- package/dist/tools/types.d.ts +53 -0
- package/dist/tools/types.js +27 -0
- package/dist/tools/types.js.map +1 -0
- package/dist/utils/coercion.cjs +56 -0
- package/dist/utils/coercion.cjs.map +1 -0
- package/dist/utils/coercion.d.cts +10 -0
- package/dist/utils/coercion.d.ts +10 -0
- package/dist/utils/coercion.js +30 -0
- package/dist/utils/coercion.js.map +1 -0
- package/dist/utils/color.cjs +38 -0
- package/dist/utils/color.cjs.map +1 -0
- package/dist/utils/color.d.cts +4 -0
- package/dist/utils/color.d.ts +4 -0
- package/dist/utils/color.js +14 -0
- package/dist/utils/color.js.map +1 -0
- package/dist/utils/wcag.cjs +123 -0
- package/dist/utils/wcag.cjs.map +1 -0
- package/dist/utils/wcag.d.cts +80 -0
- package/dist/utils/wcag.d.ts +80 -0
- package/dist/utils/wcag.js +92 -0
- package/dist/utils/wcag.js.map +1 -0
- package/package.json +43 -15
- package/LICENSE +0 -22
- package/README.md +0 -54
package/dist/mcp.cjs
CHANGED
|
@@ -5,9 +5,6 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
5
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
6
|
var __getProtoOf = Object.getPrototypeOf;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __esm = (fn, res) => function __init() {
|
|
9
|
-
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
10
|
-
};
|
|
11
8
|
var __copyProps = (to, from, except, desc) => {
|
|
12
9
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
10
|
for (let key of __getOwnPropNames(from))
|
|
@@ -25,30 +22,15 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
25
22
|
mod
|
|
26
23
|
));
|
|
27
24
|
|
|
28
|
-
// src/utils/color.ts
|
|
29
|
-
var init_color = __esm({
|
|
30
|
-
"src/utils/color.ts"() {
|
|
31
|
-
}
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
// src/utils/serialize-node.ts
|
|
35
|
-
var init_serialize_node = __esm({
|
|
36
|
-
"src/utils/serialize-node.ts"() {
|
|
37
|
-
init_color();
|
|
38
|
-
}
|
|
39
|
-
});
|
|
40
|
-
|
|
41
25
|
// src/mcp.ts
|
|
42
26
|
var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
43
27
|
var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
44
|
-
var
|
|
28
|
+
var import_zod18 = require("zod");
|
|
45
29
|
var import_ws = __toESM(require("ws"), 1);
|
|
46
30
|
var import_uuid = require("uuid");
|
|
47
31
|
var import_fs = require("fs");
|
|
48
32
|
var import_path = require("path");
|
|
49
|
-
|
|
50
|
-
// src/tools/document.ts
|
|
51
|
-
var import_zod = require("zod");
|
|
33
|
+
var import_url = require("url");
|
|
52
34
|
|
|
53
35
|
// src/tools/types.ts
|
|
54
36
|
var MAX_RESPONSE_CHARS = 5e4;
|
|
@@ -73,89 +55,80 @@ function mcpError(prefix, error) {
|
|
|
73
55
|
return { content: [{ type: "text", text: `${prefix}: ${msg}` }] };
|
|
74
56
|
}
|
|
75
57
|
|
|
76
|
-
// src/tools/
|
|
77
|
-
function
|
|
78
|
-
|
|
79
|
-
"
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
return mcpError("Error getting document info", e);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
);
|
|
90
|
-
server2.tool(
|
|
91
|
-
"get_current_page",
|
|
92
|
-
"Get the current page info and its top-level children. Always safe \u2014 never touches unloaded pages.",
|
|
93
|
-
{},
|
|
94
|
-
async () => {
|
|
58
|
+
// src/tools/registry.ts
|
|
59
|
+
function registerTools(server2, sendCommand, caps2, tools16) {
|
|
60
|
+
for (const tool of tools16) {
|
|
61
|
+
if (tool.tier === "create" && !caps2.create) continue;
|
|
62
|
+
if (tool.tier === "edit" && !caps2.edit) continue;
|
|
63
|
+
const schema = typeof tool.schema === "function" ? tool.schema(caps2) : tool.schema;
|
|
64
|
+
const command = tool.command ?? tool.name;
|
|
65
|
+
const timeout = tool.timeout;
|
|
66
|
+
const format = tool.formatResponse ?? mcpJson;
|
|
67
|
+
server2.registerTool(tool.name, { description: tool.description, inputSchema: schema }, async (params) => {
|
|
95
68
|
try {
|
|
96
|
-
|
|
69
|
+
if (tool.validate) tool.validate(params);
|
|
70
|
+
const result = await sendCommand(command, params, timeout);
|
|
71
|
+
return format(result);
|
|
97
72
|
} catch (e) {
|
|
98
|
-
return mcpError(
|
|
73
|
+
return mcpError(`${tool.name} error`, e);
|
|
99
74
|
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// src/tools/defs/connection.ts
|
|
80
|
+
var tools = [
|
|
81
|
+
{
|
|
82
|
+
name: "ping",
|
|
83
|
+
description: "Verify end-to-end connection to Figma. Call this right after join_channel. Returns { status: 'pong', documentName, currentPage } if the full chain (MCP \u2192 relay \u2192 plugin \u2192 Figma) is working. If this times out, the Figma plugin is not connected \u2014 ask the user to check the plugin window for the correct port and channel name.",
|
|
84
|
+
schema: {},
|
|
85
|
+
tier: "read",
|
|
86
|
+
timeout: 5e3
|
|
87
|
+
}
|
|
88
|
+
];
|
|
89
|
+
|
|
90
|
+
// src/tools/defs/document.ts
|
|
91
|
+
var import_zod = require("zod");
|
|
92
|
+
var tools2 = [
|
|
93
|
+
{
|
|
94
|
+
name: "get_document_info",
|
|
95
|
+
description: "Get the document name, current page, and list of all pages.",
|
|
96
|
+
schema: {},
|
|
97
|
+
tier: "read"
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
name: "get_current_page",
|
|
101
|
+
description: "Get the current page info and its top-level children.",
|
|
102
|
+
schema: {},
|
|
103
|
+
tier: "read"
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
name: "set_current_page",
|
|
107
|
+
description: "Switch to a different page. Provide either pageId or pageName.",
|
|
108
|
+
schema: {
|
|
118
109
|
pageId: import_zod.z.string().optional().describe("The page ID to switch to"),
|
|
119
110
|
pageName: import_zod.z.string().optional().describe("The page name (case-insensitive, partial match)")
|
|
120
111
|
},
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
"
|
|
131
|
-
"
|
|
132
|
-
|
|
133
|
-
async ({ name }) => {
|
|
134
|
-
try {
|
|
135
|
-
return mcpJson(await sendCommand("create_page", { name }));
|
|
136
|
-
} catch (e) {
|
|
137
|
-
return mcpError("Error creating page", e);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
);
|
|
141
|
-
server2.tool(
|
|
142
|
-
"rename_page",
|
|
143
|
-
"Rename a page. Defaults to current page if no pageId given.",
|
|
144
|
-
{
|
|
112
|
+
tier: "read"
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
name: "create_page",
|
|
116
|
+
description: "Create a new page in the document",
|
|
117
|
+
schema: { name: import_zod.z.string().optional().describe("Name for the new page (default: 'New Page')") },
|
|
118
|
+
tier: "create"
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
name: "rename_page",
|
|
122
|
+
description: "Rename a page. Defaults to current page if no pageId given.",
|
|
123
|
+
schema: {
|
|
145
124
|
newName: import_zod.z.string().describe("New name for the page"),
|
|
146
125
|
pageId: import_zod.z.string().optional().describe("Page ID (default: current page)")
|
|
147
126
|
},
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
} catch (e) {
|
|
152
|
-
return mcpError("Error renaming page", e);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
);
|
|
156
|
-
}
|
|
127
|
+
tier: "edit"
|
|
128
|
+
}
|
|
129
|
+
];
|
|
157
130
|
|
|
158
|
-
// src/tools/selection.ts
|
|
131
|
+
// src/tools/defs/selection.ts
|
|
159
132
|
var import_zod3 = require("zod");
|
|
160
133
|
|
|
161
134
|
// src/utils/coercion.ts
|
|
@@ -183,114 +156,41 @@ var flexNum = (inner) => import_zod2.z.preprocess((v) => {
|
|
|
183
156
|
return v;
|
|
184
157
|
}, inner);
|
|
185
158
|
|
|
186
|
-
// src/tools/selection.ts
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
"get_selection",
|
|
190
|
-
"Get
|
|
191
|
-
{},
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
}
|
|
199
|
-
);
|
|
200
|
-
server2.tool(
|
|
201
|
-
"read_my_design",
|
|
202
|
-
"Read the nodes the user has selected in Figma (or set via set_selection). Returns nothing if no selection exists \u2014 ask the user to select something, or use get_node_info with specific node IDs. Use depth to control child traversal.",
|
|
203
|
-
{ depth: import_zod3.z.coerce.number().optional().describe("Levels of children to recurse. 0=selection only, -1 or omit for unlimited.") },
|
|
204
|
-
async ({ depth: depth2 }) => {
|
|
205
|
-
try {
|
|
206
|
-
return mcpJson(await sendCommand("read_my_design", { depth: depth2 }));
|
|
207
|
-
} catch (e) {
|
|
208
|
-
return mcpError("Error reading design", e);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
);
|
|
212
|
-
server2.tool(
|
|
213
|
-
"set_selection",
|
|
214
|
-
"Set selection to nodes and scroll viewport to show them. Also works as focus (single node).",
|
|
215
|
-
{
|
|
159
|
+
// src/tools/defs/selection.ts
|
|
160
|
+
var tools3 = [
|
|
161
|
+
{
|
|
162
|
+
name: "get_selection",
|
|
163
|
+
description: "Get the current selection. Without depth, returns stubs (id/name/type). With depth, returns full serialized node trees.",
|
|
164
|
+
schema: { depth: import_zod3.z.coerce.number().optional().describe("Child recursion depth. Omit for stubs only, 0=selected nodes' properties, -1=unlimited.") },
|
|
165
|
+
tier: "read"
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
name: "set_selection",
|
|
169
|
+
description: "Set selection to nodes and scroll viewport to show them. Also works as focus (single node).",
|
|
170
|
+
schema: {
|
|
216
171
|
nodeIds: flexJson(import_zod3.z.array(import_zod3.z.string())).describe('Array of node IDs to select. Example: ["1:2","1:3"]')
|
|
217
172
|
},
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
} catch (e) {
|
|
222
|
-
return mcpError("Error setting selection", e);
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
);
|
|
226
|
-
server2.tool(
|
|
227
|
-
"zoom_into_view",
|
|
228
|
-
"Zoom the viewport to fit specific nodes (like pressing Shift+1)",
|
|
229
|
-
{
|
|
230
|
-
nodeIds: flexJson(import_zod3.z.array(import_zod3.z.string())).describe("Array of node IDs to zoom into")
|
|
231
|
-
},
|
|
232
|
-
async ({ nodeIds: nodeIds2 }) => {
|
|
233
|
-
try {
|
|
234
|
-
return mcpJson(await sendCommand("zoom_into_view", { nodeIds: nodeIds2 }));
|
|
235
|
-
} catch (e) {
|
|
236
|
-
return mcpError("Error zooming", e);
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
);
|
|
240
|
-
server2.tool(
|
|
241
|
-
"set_viewport",
|
|
242
|
-
"Set viewport center position and/or zoom level",
|
|
243
|
-
{
|
|
244
|
-
center: flexJson(import_zod3.z.object({ x: import_zod3.z.coerce.number(), y: import_zod3.z.coerce.number() })).optional().describe("Viewport center point. Omit to keep current center."),
|
|
245
|
-
zoom: import_zod3.z.coerce.number().optional().describe("Zoom level (1 = 100%). Omit to keep current zoom.")
|
|
246
|
-
},
|
|
247
|
-
async (params) => {
|
|
248
|
-
try {
|
|
249
|
-
return mcpJson(await sendCommand("set_viewport", params));
|
|
250
|
-
} catch (e) {
|
|
251
|
-
return mcpError("Error setting viewport", e);
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
);
|
|
255
|
-
}
|
|
173
|
+
tier: "read"
|
|
174
|
+
}
|
|
175
|
+
];
|
|
256
176
|
|
|
257
|
-
// src/tools/node-info.ts
|
|
177
|
+
// src/tools/defs/node-info.ts
|
|
258
178
|
var import_zod4 = require("zod");
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
"
|
|
263
|
-
|
|
264
|
-
{
|
|
179
|
+
var tools4 = [
|
|
180
|
+
{
|
|
181
|
+
name: "get_node_info",
|
|
182
|
+
description: "Get detailed information about one or more nodes. Always pass an array of IDs. Use `fields` to select only the properties you need (reduces context size).",
|
|
183
|
+
schema: {
|
|
265
184
|
nodeIds: flexJson(import_zod4.z.array(import_zod4.z.string())).describe('Array of node IDs. Example: ["1:2","1:3"]'),
|
|
266
185
|
depth: import_zod4.z.coerce.number().optional().describe("Child recursion depth (default: unlimited). 0=stubs only."),
|
|
267
|
-
fields: flexJson(import_zod4.z.array(import_zod4.z.string())).optional().describe('Whitelist of property names to include.
|
|
186
|
+
fields: flexJson(import_zod4.z.array(import_zod4.z.string())).optional().describe('Whitelist of property names to include. Example: ["absoluteBoundingBox","layoutMode","fills"]. Omit to return all properties.')
|
|
268
187
|
},
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
);
|
|
278
|
-
server2.tool(
|
|
279
|
-
"get_node_css",
|
|
280
|
-
"Get CSS properties for a node (useful for dev handoff)",
|
|
281
|
-
{ nodeId: import_zod4.z.string().describe("The node ID to get CSS for") },
|
|
282
|
-
async ({ nodeId: nodeId2 }) => {
|
|
283
|
-
try {
|
|
284
|
-
return mcpJson(await sendCommand("get_node_css", { nodeId: nodeId2 }));
|
|
285
|
-
} catch (e) {
|
|
286
|
-
return mcpError("Error getting CSS", e);
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
);
|
|
290
|
-
server2.tool(
|
|
291
|
-
"search_nodes",
|
|
292
|
-
"Search for nodes by layer name and/or type. Searches current page only \u2014 use set_current_page to switch pages first. Matches layer names (text nodes are often auto-named from their content). Returns paginated results.",
|
|
293
|
-
{
|
|
188
|
+
tier: "read"
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
name: "search_nodes",
|
|
192
|
+
description: "Search nodes on the current page by name and/or type. Use set_current_page first to search other pages. Paginated (default 50).",
|
|
193
|
+
schema: {
|
|
294
194
|
query: import_zod4.z.string().optional().describe("Name search (case-insensitive substring). Omit to match all names."),
|
|
295
195
|
types: flexJson(import_zod4.z.array(import_zod4.z.string())).optional().describe('Filter by types. Example: ["FRAME","TEXT"]. Omit to match all types.'),
|
|
296
196
|
scopeNodeId: import_zod4.z.string().optional().describe("Node ID to search within (defaults to current page)"),
|
|
@@ -298,36 +198,27 @@ function registerMcpTools3(server2, sendCommand) {
|
|
|
298
198
|
limit: import_zod4.z.coerce.number().optional().describe("Max results (default 50)"),
|
|
299
199
|
offset: import_zod4.z.coerce.number().optional().describe("Skip N results for pagination (default 0)")
|
|
300
200
|
},
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
}
|
|
308
|
-
);
|
|
309
|
-
server2.tool(
|
|
310
|
-
"export_node_as_image",
|
|
311
|
-
"Export a node as an image from Figma",
|
|
312
|
-
{
|
|
201
|
+
tier: "read"
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
name: "export_node_as_image",
|
|
205
|
+
description: "Export a node as an image from Figma",
|
|
206
|
+
schema: {
|
|
313
207
|
nodeId: import_zod4.z.string().describe("The node ID to export"),
|
|
314
208
|
format: import_zod4.z.enum(["PNG", "JPG", "SVG", "PDF"]).optional().describe("Export format (default: PNG)"),
|
|
315
209
|
scale: import_zod4.z.coerce.number().positive().optional().describe("Export scale (default: 1)")
|
|
316
210
|
},
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
} catch (e) {
|
|
324
|
-
return mcpError("Error exporting image", e);
|
|
325
|
-
}
|
|
211
|
+
tier: "read",
|
|
212
|
+
formatResponse: (result) => {
|
|
213
|
+
const r = result;
|
|
214
|
+
return {
|
|
215
|
+
content: [{ type: "image", data: r.imageData, mimeType: r.mimeType || "image/png" }]
|
|
216
|
+
};
|
|
326
217
|
}
|
|
327
|
-
|
|
328
|
-
|
|
218
|
+
}
|
|
219
|
+
];
|
|
329
220
|
|
|
330
|
-
// src/tools/create-shape.ts
|
|
221
|
+
// src/tools/defs/create-shape.ts
|
|
331
222
|
var import_zod6 = require("zod");
|
|
332
223
|
|
|
333
224
|
// src/tools/schemas.ts
|
|
@@ -359,7 +250,7 @@ var colorRgba = import_zod5.z.preprocess((v) => {
|
|
|
359
250
|
g: import_zod5.z.coerce.number().min(0).max(1),
|
|
360
251
|
b: import_zod5.z.coerce.number().min(0).max(1),
|
|
361
252
|
a: import_zod5.z.coerce.number().min(0).max(1).optional()
|
|
362
|
-
}));
|
|
253
|
+
})).describe('Hex "#FF0000" or {r,g,b,a?} with values 0-1.');
|
|
363
254
|
var effectEntry = import_zod5.z.object({
|
|
364
255
|
type: import_zod5.z.enum(["DROP_SHADOW", "INNER_SHADOW", "LAYER_BLUR", "BACKGROUND_BLUR"]),
|
|
365
256
|
color: flexJson(colorRgba).optional(),
|
|
@@ -370,34 +261,7 @@ var effectEntry = import_zod5.z.object({
|
|
|
370
261
|
blendMode: import_zod5.z.string().optional()
|
|
371
262
|
});
|
|
372
263
|
|
|
373
|
-
// src/tools/
|
|
374
|
-
init_serialize_node();
|
|
375
|
-
|
|
376
|
-
// src/tools/create-shape.ts
|
|
377
|
-
var rectItem = import_zod6.z.object({
|
|
378
|
-
name: import_zod6.z.string().optional().describe("Name (default: 'Rectangle')"),
|
|
379
|
-
x: xPos,
|
|
380
|
-
y: yPos,
|
|
381
|
-
width: import_zod6.z.coerce.number().optional().describe("Width (default: 100)"),
|
|
382
|
-
height: import_zod6.z.coerce.number().optional().describe("Height (default: 100)"),
|
|
383
|
-
parentId
|
|
384
|
-
});
|
|
385
|
-
var ellipseItem = import_zod6.z.object({
|
|
386
|
-
name: import_zod6.z.string().optional().describe("Layer name (default: 'Ellipse')"),
|
|
387
|
-
x: xPos,
|
|
388
|
-
y: yPos,
|
|
389
|
-
width: import_zod6.z.coerce.number().optional().describe("Width (default: 100)"),
|
|
390
|
-
height: import_zod6.z.coerce.number().optional().describe("Height (default: 100)"),
|
|
391
|
-
parentId
|
|
392
|
-
});
|
|
393
|
-
var lineItem = import_zod6.z.object({
|
|
394
|
-
name: import_zod6.z.string().optional().describe("Layer name (default: 'Line')"),
|
|
395
|
-
x: xPos,
|
|
396
|
-
y: yPos,
|
|
397
|
-
length: import_zod6.z.coerce.number().optional().describe("Length (default: 100)"),
|
|
398
|
-
rotation: import_zod6.z.coerce.number().optional().describe("Rotation in degrees (default: 0)"),
|
|
399
|
-
parentId
|
|
400
|
-
});
|
|
264
|
+
// src/tools/defs/create-shape.ts
|
|
401
265
|
var sectionItem = import_zod6.z.object({
|
|
402
266
|
name: import_zod6.z.string().optional().describe("Name (default: 'Section')"),
|
|
403
267
|
x: xPos,
|
|
@@ -413,87 +277,22 @@ var svgItem = import_zod6.z.object({
|
|
|
413
277
|
y: yPos,
|
|
414
278
|
parentId
|
|
415
279
|
});
|
|
416
|
-
var
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
return mcpError("Error creating rectangles", e);
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
);
|
|
434
|
-
server2.tool(
|
|
435
|
-
"create_ellipse",
|
|
436
|
-
"Create ellipses (leaf nodes \u2014 cannot have children). For circular containers, use create_frame with cornerRadius instead. Batch: pass multiple items.",
|
|
437
|
-
{ items: flexJson(import_zod6.z.array(ellipseItem)).describe("Array of ellipses to create"), depth },
|
|
438
|
-
async (params) => {
|
|
439
|
-
try {
|
|
440
|
-
return mcpJson(await sendCommand("create_ellipse", params));
|
|
441
|
-
} catch (e) {
|
|
442
|
-
return mcpError("Error creating ellipses", e);
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
);
|
|
446
|
-
server2.tool(
|
|
447
|
-
"create_line",
|
|
448
|
-
"Create lines (leaf nodes \u2014 cannot have children). For dividers inside layouts, use create_frame with a thin height and fill color instead. Batch: pass multiple items.",
|
|
449
|
-
{ items: flexJson(import_zod6.z.array(lineItem)).describe("Array of lines to create"), depth },
|
|
450
|
-
async (params) => {
|
|
451
|
-
try {
|
|
452
|
-
return mcpJson(await sendCommand("create_line", params));
|
|
453
|
-
} catch (e) {
|
|
454
|
-
return mcpError("Error creating lines", e);
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
);
|
|
458
|
-
server2.tool(
|
|
459
|
-
"create_section",
|
|
460
|
-
"Create section nodes to organize content on the canvas.",
|
|
461
|
-
{ items: flexJson(import_zod6.z.array(sectionItem)).describe("Array of sections to create"), depth },
|
|
462
|
-
async (params) => {
|
|
463
|
-
try {
|
|
464
|
-
return mcpJson(await sendCommand("create_section", params));
|
|
465
|
-
} catch (e) {
|
|
466
|
-
return mcpError("Error creating sections", e);
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
);
|
|
470
|
-
server2.tool(
|
|
471
|
-
"create_node_from_svg",
|
|
472
|
-
"Create nodes from SVG strings.",
|
|
473
|
-
{ items: flexJson(import_zod6.z.array(svgItem)).describe("Array of SVG items to create"), depth },
|
|
474
|
-
async (params) => {
|
|
475
|
-
try {
|
|
476
|
-
return mcpJson(await sendCommand("create_node_from_svg", params));
|
|
477
|
-
} catch (e) {
|
|
478
|
-
return mcpError("Error creating SVG nodes", e);
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
);
|
|
482
|
-
server2.tool(
|
|
483
|
-
"create_boolean_operation",
|
|
484
|
-
"Create a boolean operation (union, intersect, subtract, exclude) from multiple nodes.",
|
|
485
|
-
{ items: flexJson(import_zod6.z.array(boolOpItem)).describe("Array of boolean operations to create"), depth },
|
|
486
|
-
async (params) => {
|
|
487
|
-
try {
|
|
488
|
-
return mcpJson(await sendCommand("create_boolean_operation", params));
|
|
489
|
-
} catch (e) {
|
|
490
|
-
return mcpError("Error creating boolean operations", e);
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
);
|
|
494
|
-
}
|
|
280
|
+
var tools5 = [
|
|
281
|
+
{
|
|
282
|
+
name: "create_section",
|
|
283
|
+
description: "Create section nodes to organize content on the canvas.",
|
|
284
|
+
schema: { items: flexJson(import_zod6.z.array(sectionItem)).describe("Array of sections to create"), depth },
|
|
285
|
+
tier: "create"
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
name: "create_node_from_svg",
|
|
289
|
+
description: "Create nodes from SVG strings.",
|
|
290
|
+
schema: { items: flexJson(import_zod6.z.array(svgItem)).describe("Array of SVG items to create"), depth },
|
|
291
|
+
tier: "create"
|
|
292
|
+
}
|
|
293
|
+
];
|
|
495
294
|
|
|
496
|
-
// src/tools/create-frame.ts
|
|
295
|
+
// src/tools/defs/create-frame.ts
|
|
497
296
|
var import_zod7 = require("zod");
|
|
498
297
|
var frameItem = import_zod7.z.object({
|
|
499
298
|
name: import_zod7.z.string().optional().describe("Frame name (default: 'Frame')"),
|
|
@@ -502,8 +301,8 @@ var frameItem = import_zod7.z.object({
|
|
|
502
301
|
width: import_zod7.z.coerce.number().optional().describe("Width (default: 100)"),
|
|
503
302
|
height: import_zod7.z.coerce.number().optional().describe("Height (default: 100)"),
|
|
504
303
|
parentId,
|
|
505
|
-
fillColor: flexJson(colorRgba).optional().describe(
|
|
506
|
-
strokeColor: flexJson(colorRgba).optional().describe(
|
|
304
|
+
fillColor: flexJson(colorRgba).optional().describe("Fill color. Default: no fill."),
|
|
305
|
+
strokeColor: flexJson(colorRgba).optional().describe("Stroke color. Default: none."),
|
|
507
306
|
strokeWeight: import_zod7.z.coerce.number().positive().optional().describe("Stroke weight (default: 1)"),
|
|
508
307
|
cornerRadius: import_zod7.z.coerce.number().min(0).optional().describe("Corner radius (default: 0)"),
|
|
509
308
|
layoutMode: import_zod7.z.enum(["NONE", "HORIZONTAL", "VERTICAL"]).optional().describe("Auto-layout direction (default: NONE)"),
|
|
@@ -517,7 +316,6 @@ var frameItem = import_zod7.z.object({
|
|
|
517
316
|
layoutSizingHorizontal: import_zod7.z.enum(["FIXED", "HUG", "FILL"]).optional(),
|
|
518
317
|
layoutSizingVertical: import_zod7.z.enum(["FIXED", "HUG", "FILL"]).optional(),
|
|
519
318
|
itemSpacing: import_zod7.z.coerce.number().optional().describe("Spacing between children (default: 0)"),
|
|
520
|
-
// Style/variable references
|
|
521
319
|
fillStyleName: import_zod7.z.string().optional().describe("Apply a fill paint style by name (case-insensitive). Omit to skip."),
|
|
522
320
|
strokeStyleName: import_zod7.z.string().optional().describe("Apply a stroke paint style by name. Omit to skip."),
|
|
523
321
|
fillVariableId: import_zod7.z.string().optional().describe("Bind a color variable to the fill. Creates a solid fill and binds the variable to fills/0/color."),
|
|
@@ -529,1110 +327,681 @@ var autoLayoutItem = import_zod7.z.object({
|
|
|
529
327
|
layoutMode: import_zod7.z.enum(["HORIZONTAL", "VERTICAL"]).optional().describe("Direction (default: VERTICAL)"),
|
|
530
328
|
itemSpacing: import_zod7.z.coerce.number().optional().describe("Spacing between children (default: 0)"),
|
|
531
329
|
paddingTop: import_zod7.z.coerce.number().optional().describe("Top padding (default: 0)"),
|
|
532
|
-
paddingRight: import_zod7.z.coerce.number().optional().describe("Right padding (default: 0)"),
|
|
533
|
-
paddingBottom: import_zod7.z.coerce.number().optional().describe("Bottom padding (default: 0)"),
|
|
534
|
-
paddingLeft: import_zod7.z.coerce.number().optional().describe("Left padding (default: 0)"),
|
|
535
|
-
primaryAxisAlignItems: import_zod7.z.enum(["MIN", "MAX", "CENTER", "SPACE_BETWEEN"]).optional(),
|
|
536
|
-
counterAxisAlignItems: import_zod7.z.enum(["MIN", "MAX", "CENTER", "BASELINE"]).optional(),
|
|
537
|
-
layoutSizingHorizontal: import_zod7.z.enum(["FIXED", "HUG", "FILL"]).optional(),
|
|
538
|
-
layoutSizingVertical: import_zod7.z.enum(["FIXED", "HUG", "FILL"]).optional(),
|
|
539
|
-
layoutWrap: import_zod7.z.enum(["NO_WRAP", "WRAP"]).optional()
|
|
540
|
-
});
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
"create_frame",
|
|
544
|
-
"Create frames in Figma. Batch supported. Use fillStyleName/fillVariableId and strokeStyleName/strokeVariableId instead of hardcoded colors \u2014 hardcoded values skip design tokens and will trigger lint warnings.",
|
|
545
|
-
{ items: flexJson(import_zod7.z.array(frameItem)).describe("Array of frames to create"), depth },
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
"create_auto_layout",
|
|
556
|
-
"Wrap existing nodes in an auto-layout frame. One call replaces create_frame + update_frame + insert_child \xD7 N.",
|
|
557
|
-
{ items: flexJson(import_zod7.z.array(autoLayoutItem)).describe("Array of auto-layout wraps to perform"), depth },
|
|
558
|
-
async (params) => {
|
|
559
|
-
try {
|
|
560
|
-
return mcpJson(await sendCommand("create_auto_layout", params));
|
|
561
|
-
} catch (e) {
|
|
562
|
-
return mcpError("Error creating auto layout", e);
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
|
-
);
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
// src/tools/create-text.ts
|
|
569
|
-
var import_zod8 = require("zod");
|
|
570
|
-
var textItem = import_zod8.z.object({
|
|
571
|
-
text: import_zod8.z.string().describe("Text content"),
|
|
572
|
-
name: import_zod8.z.string().optional().describe("Layer name (default: text content)"),
|
|
573
|
-
x: xPos,
|
|
574
|
-
y: yPos,
|
|
575
|
-
fontFamily: import_zod8.z.string().optional().describe("Font family (default: Inter). Use get_available_fonts to list installed fonts."),
|
|
576
|
-
fontStyle: import_zod8.z.string().optional().describe("Font style, e.g. 'Regular', 'Bold', 'Italic' (default: derived from fontWeight). Overrides fontWeight when set."),
|
|
577
|
-
fontSize: import_zod8.z.coerce.number().optional().describe("Font size (default: 14)"),
|
|
578
|
-
fontWeight: import_zod8.z.coerce.number().optional().describe("Font weight: 100-900 (default: 400). Ignored when fontStyle is set."),
|
|
579
|
-
fontColor: flexJson(colorRgba).optional().describe('Font color. Hex "#000000" or {r,g,b,a?} 0-1. Default: black.'),
|
|
580
|
-
fontColorVariableId: import_zod8.z.string().optional().describe("Bind a color variable to the text fill instead of hardcoded fontColor."),
|
|
581
|
-
fontColorStyleName: import_zod8.z.string().optional().describe("Apply a paint style to the text fill by name (case-insensitive). Overrides fontColor."),
|
|
582
|
-
parentId,
|
|
583
|
-
textStyleId: import_zod8.z.string().optional().describe("Text style ID to apply (overrides fontSize/fontWeight). Omit to skip."),
|
|
584
|
-
textStyleName: import_zod8.z.string().optional().describe("Text style name (case-insensitive match). Omit to skip."),
|
|
585
|
-
textAlignHorizontal: import_zod8.z.enum(["LEFT", "CENTER", "RIGHT", "JUSTIFIED"]).optional().describe("Horizontal text alignment (default: LEFT)"),
|
|
586
|
-
textAlignVertical: import_zod8.z.enum(["TOP", "CENTER", "BOTTOM"]).optional().describe("Vertical text alignment (default: TOP)"),
|
|
587
|
-
layoutSizingHorizontal: import_zod8.z.enum(["FIXED", "HUG", "FILL"]).optional().describe("Horizontal sizing. FILL auto-sets textAutoResize to HEIGHT."),
|
|
588
|
-
layoutSizingVertical: import_zod8.z.enum(["FIXED", "HUG", "FILL"]).optional().describe("Vertical sizing (default: HUG)"),
|
|
589
|
-
textAutoResize: import_zod8.z.enum(["NONE", "WIDTH_AND_HEIGHT", "HEIGHT", "TRUNCATE"]).optional().describe("Text auto-resize behavior (default: WIDTH_AND_HEIGHT when FILL)")
|
|
590
|
-
});
|
|
591
|
-
function registerMcpTools6(server2, sendCommand) {
|
|
592
|
-
server2.tool(
|
|
593
|
-
"create_text",
|
|
594
|
-
"Create text nodes. Max 10 per batch. Prefer textStyleName for typography and fontColorStyleName or fontColorVariableId for color \u2014 hardcoded values skip design tokens. Supports custom fonts via fontFamily.",
|
|
595
|
-
{ items: flexJson(import_zod8.z.array(textItem).max(10)).describe("Array of text nodes to create (max 10)"), depth },
|
|
596
|
-
async (params) => {
|
|
597
|
-
try {
|
|
598
|
-
return mcpJson(await sendCommand("create_text", params));
|
|
599
|
-
} catch (e) {
|
|
600
|
-
return mcpError("Error creating text", e);
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
);
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
// src/tools/modify-node.ts
|
|
607
|
-
var import_zod9 = require("zod");
|
|
608
|
-
var moveItem = import_zod9.z.object({
|
|
609
|
-
nodeId,
|
|
610
|
-
x: import_zod9.z.coerce.number().describe("New X"),
|
|
611
|
-
y: import_zod9.z.coerce.number().describe("New Y")
|
|
612
|
-
});
|
|
613
|
-
var resizeItem = import_zod9.z.object({
|
|
614
|
-
nodeId,
|
|
615
|
-
width: import_zod9.z.coerce.number().positive().describe("New width"),
|
|
616
|
-
height: import_zod9.z.coerce.number().positive().describe("New height")
|
|
617
|
-
});
|
|
618
|
-
var deleteItem = import_zod9.z.object({
|
|
619
|
-
nodeId: import_zod9.z.string().describe("Node ID to delete")
|
|
620
|
-
});
|
|
621
|
-
var cloneItem = import_zod9.z.object({
|
|
622
|
-
nodeId: import_zod9.z.string().describe("Node ID to clone"),
|
|
623
|
-
parentId: import_zod9.z.string().optional().describe("Parent for the clone (e.g. a page ID). Defaults to same parent as original."),
|
|
624
|
-
x: import_zod9.z.coerce.number().optional().describe("New X for clone. Omit to keep original position."),
|
|
625
|
-
y: import_zod9.z.coerce.number().optional().describe("New Y for clone. Omit to keep original position.")
|
|
626
|
-
});
|
|
627
|
-
var insertItem = import_zod9.z.object({
|
|
628
|
-
parentId: import_zod9.z.string().describe("Parent node ID"),
|
|
629
|
-
childId: import_zod9.z.string().describe("Child node ID to move"),
|
|
630
|
-
index: import_zod9.z.coerce.number().optional().describe("Index to insert at (0=first). Omit to append.")
|
|
631
|
-
});
|
|
632
|
-
function registerMcpTools7(server2, sendCommand) {
|
|
633
|
-
server2.tool(
|
|
634
|
-
"move_node",
|
|
635
|
-
"Move nodes to new positions. Batch: pass multiple items.",
|
|
636
|
-
{ items: flexJson(import_zod9.z.array(moveItem)).describe("Array of {nodeId, x, y}"), depth },
|
|
637
|
-
async (params) => {
|
|
638
|
-
try {
|
|
639
|
-
return mcpJson(await sendCommand("move_node", params));
|
|
640
|
-
} catch (e) {
|
|
641
|
-
return mcpError("Error moving nodes", e);
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
);
|
|
645
|
-
server2.tool(
|
|
646
|
-
"resize_node",
|
|
647
|
-
"Resize nodes. Batch: pass multiple items.",
|
|
648
|
-
{ items: flexJson(import_zod9.z.array(resizeItem)).describe("Array of {nodeId, width, height}"), depth },
|
|
649
|
-
async (params) => {
|
|
650
|
-
try {
|
|
651
|
-
return mcpJson(await sendCommand("resize_node", params));
|
|
652
|
-
} catch (e) {
|
|
653
|
-
return mcpError("Error resizing nodes", e);
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
);
|
|
657
|
-
server2.tool(
|
|
658
|
-
"delete_node",
|
|
659
|
-
"Delete nodes. Batch: pass multiple items.",
|
|
660
|
-
{ items: flexJson(import_zod9.z.array(deleteItem)).describe("Array of {nodeId}") },
|
|
661
|
-
async (params) => {
|
|
662
|
-
try {
|
|
663
|
-
return mcpJson(await sendCommand("delete_node", params));
|
|
664
|
-
} catch (e) {
|
|
665
|
-
return mcpError("Error deleting nodes", e);
|
|
666
|
-
}
|
|
667
|
-
}
|
|
668
|
-
);
|
|
669
|
-
server2.tool(
|
|
670
|
-
"clone_node",
|
|
671
|
-
"Clone nodes. Batch: pass multiple items.",
|
|
672
|
-
{ items: flexJson(import_zod9.z.array(cloneItem)).describe("Array of {nodeId, x?, y?}"), depth },
|
|
673
|
-
async (params) => {
|
|
674
|
-
try {
|
|
675
|
-
return mcpJson(await sendCommand("clone_node", params));
|
|
676
|
-
} catch (e) {
|
|
677
|
-
return mcpError("Error cloning nodes", e);
|
|
678
|
-
}
|
|
679
|
-
}
|
|
680
|
-
);
|
|
681
|
-
server2.tool(
|
|
682
|
-
"insert_child",
|
|
683
|
-
"Move nodes into a parent at a specific index (reorder/reparent). Batch: pass multiple items.",
|
|
684
|
-
{ items: flexJson(import_zod9.z.array(insertItem)).describe("Array of {parentId, childId, index?}"), depth },
|
|
685
|
-
async (params) => {
|
|
686
|
-
try {
|
|
687
|
-
return mcpJson(await sendCommand("insert_child", params));
|
|
688
|
-
} catch (e) {
|
|
689
|
-
return mcpError("Error inserting children", e);
|
|
690
|
-
}
|
|
691
|
-
}
|
|
692
|
-
);
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
// src/tools/fill-stroke.ts
|
|
696
|
-
var import_zod10 = require("zod");
|
|
697
|
-
var fillItem = import_zod10.z.object({
|
|
698
|
-
nodeId,
|
|
699
|
-
color: flexJson(colorRgba).optional().describe('Fill color. Hex "#FF0000" or {r,g,b,a?} 0-1. Ignored when styleName is set.'),
|
|
700
|
-
styleName: import_zod10.z.string().optional().describe("Apply fill paint style by name instead of color. Omit to use color.")
|
|
701
|
-
});
|
|
702
|
-
var strokeItem = import_zod10.z.object({
|
|
703
|
-
nodeId,
|
|
704
|
-
color: flexJson(colorRgba).optional().describe('Stroke color. Hex "#FF0000" or {r,g,b,a?} 0-1. Ignored when styleName is set.'),
|
|
705
|
-
strokeWeight: import_zod10.z.coerce.number().positive().optional().describe("Stroke weight (default: 1)"),
|
|
706
|
-
styleName: import_zod10.z.string().optional().describe("Apply stroke paint style by name instead of color. Omit to use color.")
|
|
707
|
-
});
|
|
708
|
-
var cornerItem = import_zod10.z.object({
|
|
709
|
-
nodeId,
|
|
710
|
-
radius: import_zod10.z.coerce.number().min(0).describe("Corner radius"),
|
|
711
|
-
corners: flexJson(import_zod10.z.array(flexBool(import_zod10.z.boolean())).length(4)).optional().describe("Which corners to round [topLeft, topRight, bottomRight, bottomLeft]. Default: all corners [true,true,true,true].")
|
|
712
|
-
});
|
|
713
|
-
var opacityItem = import_zod10.z.object({
|
|
714
|
-
nodeId,
|
|
715
|
-
opacity: import_zod10.z.coerce.number().min(0).max(1).describe("Opacity (0-1)")
|
|
716
|
-
});
|
|
717
|
-
function registerMcpTools8(server2, sendCommand) {
|
|
718
|
-
server2.tool(
|
|
719
|
-
"set_fill_color",
|
|
720
|
-
"Set fill color on nodes. Prefer styleName (design token) over hardcoded color \u2014 hardcoded values trigger lint warnings. Batch: pass multiple items.",
|
|
721
|
-
{ items: flexJson(import_zod10.z.array(fillItem)).describe("Array of {nodeId, color?, styleName?}"), depth },
|
|
722
|
-
async (params) => {
|
|
723
|
-
try {
|
|
724
|
-
return mcpJson(await sendCommand("set_fill_color", params));
|
|
725
|
-
} catch (e) {
|
|
726
|
-
return mcpError("Error setting fill", e);
|
|
727
|
-
}
|
|
728
|
-
}
|
|
729
|
-
);
|
|
730
|
-
server2.tool(
|
|
731
|
-
"set_stroke_color",
|
|
732
|
-
"Set stroke color on nodes. Prefer styleName (design token) over hardcoded color \u2014 hardcoded values trigger lint warnings. Batch: pass multiple items.",
|
|
733
|
-
{ items: flexJson(import_zod10.z.array(strokeItem)).describe("Array of {nodeId, color?, strokeWeight?, styleName?}"), depth },
|
|
734
|
-
async (params) => {
|
|
735
|
-
try {
|
|
736
|
-
return mcpJson(await sendCommand("set_stroke_color", params));
|
|
737
|
-
} catch (e) {
|
|
738
|
-
return mcpError("Error setting stroke", e);
|
|
739
|
-
}
|
|
740
|
-
}
|
|
741
|
-
);
|
|
742
|
-
server2.tool(
|
|
743
|
-
"set_corner_radius",
|
|
744
|
-
"Set corner radius on nodes. Batch: pass multiple items.",
|
|
745
|
-
{ items: flexJson(import_zod10.z.array(cornerItem)).describe("Array of {nodeId, radius, corners?}"), depth },
|
|
746
|
-
async (params) => {
|
|
747
|
-
try {
|
|
748
|
-
return mcpJson(await sendCommand("set_corner_radius", params));
|
|
749
|
-
} catch (e) {
|
|
750
|
-
return mcpError("Error setting corner radius", e);
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
|
-
);
|
|
754
|
-
server2.tool(
|
|
755
|
-
"set_opacity",
|
|
756
|
-
"Set opacity on nodes. Batch: pass multiple items.",
|
|
757
|
-
{ items: flexJson(import_zod10.z.array(opacityItem)).describe("Array of {nodeId, opacity}"), depth },
|
|
758
|
-
async (params) => {
|
|
759
|
-
try {
|
|
760
|
-
return mcpJson(await sendCommand("set_opacity", params));
|
|
761
|
-
} catch (e) {
|
|
762
|
-
return mcpError("Error setting opacity", e);
|
|
763
|
-
}
|
|
764
|
-
}
|
|
765
|
-
);
|
|
766
|
-
}
|
|
767
|
-
|
|
768
|
-
// src/tools/update-frame.ts
|
|
769
|
-
var import_zod11 = require("zod");
|
|
770
|
-
var updateFrameItem = import_zod11.z.object({
|
|
771
|
-
nodeId,
|
|
772
|
-
layoutMode: import_zod11.z.enum(["NONE", "HORIZONTAL", "VERTICAL"]).optional().describe("Auto-layout direction"),
|
|
773
|
-
layoutWrap: import_zod11.z.enum(["NO_WRAP", "WRAP"]).optional().describe("Wrap (default: NO_WRAP)"),
|
|
774
|
-
paddingTop: import_zod11.z.coerce.number().optional().describe("Top padding"),
|
|
775
|
-
paddingRight: import_zod11.z.coerce.number().optional().describe("Right padding"),
|
|
776
|
-
paddingBottom: import_zod11.z.coerce.number().optional().describe("Bottom padding"),
|
|
777
|
-
paddingLeft: import_zod11.z.coerce.number().optional().describe("Left padding"),
|
|
778
|
-
primaryAxisAlignItems: import_zod11.z.enum(["MIN", "MAX", "CENTER", "SPACE_BETWEEN"]).optional().describe("Primary axis alignment"),
|
|
779
|
-
counterAxisAlignItems: import_zod11.z.enum(["MIN", "MAX", "CENTER", "BASELINE"]).optional().describe("Counter axis alignment"),
|
|
780
|
-
layoutSizingHorizontal: import_zod11.z.enum(["FIXED", "HUG", "FILL"]).optional().describe("Horizontal sizing (works on any node in auto-layout)"),
|
|
781
|
-
layoutSizingVertical: import_zod11.z.enum(["FIXED", "HUG", "FILL"]).optional().describe("Vertical sizing (works on any node in auto-layout)"),
|
|
782
|
-
itemSpacing: import_zod11.z.coerce.number().optional().describe("Spacing between children"),
|
|
783
|
-
counterAxisSpacing: import_zod11.z.coerce.number().optional().describe("Spacing between wrapped rows/columns (WRAP only)")
|
|
784
|
-
});
|
|
785
|
-
function registerMcpTools9(server2, sendCommand) {
|
|
786
|
-
server2.tool(
|
|
787
|
-
"update_frame",
|
|
788
|
-
"Update layout properties on frames. Combines layout mode, padding, alignment, sizing, and spacing in one call. Batch: pass multiple items.",
|
|
789
|
-
{ items: flexJson(import_zod11.z.array(updateFrameItem)).describe("Array of {nodeId, ...layout properties}"), depth },
|
|
790
|
-
async (params) => {
|
|
791
|
-
try {
|
|
792
|
-
return mcpJson(await sendCommand("update_frame", params));
|
|
793
|
-
} catch (e) {
|
|
794
|
-
return mcpError("Error updating frame", e);
|
|
795
|
-
}
|
|
796
|
-
}
|
|
797
|
-
);
|
|
798
|
-
}
|
|
799
|
-
|
|
800
|
-
// src/tools/effects.ts
|
|
801
|
-
var import_zod12 = require("zod");
|
|
802
|
-
var effectItem = import_zod12.z.object({
|
|
803
|
-
nodeId,
|
|
804
|
-
effects: flexJson(import_zod12.z.array(effectEntry)).optional().describe("Array of effect objects. Ignored when effectStyleName is set."),
|
|
805
|
-
effectStyleName: import_zod12.z.string().optional().describe("Apply an effect style by name (case-insensitive). Omit to use raw effects.")
|
|
806
|
-
});
|
|
807
|
-
var constraintItem = import_zod12.z.object({
|
|
808
|
-
nodeId,
|
|
809
|
-
horizontal: import_zod12.z.enum(["MIN", "CENTER", "MAX", "STRETCH", "SCALE"]),
|
|
810
|
-
vertical: import_zod12.z.enum(["MIN", "CENTER", "MAX", "STRETCH", "SCALE"])
|
|
811
|
-
});
|
|
812
|
-
var exportSettingEntry = import_zod12.z.object({
|
|
813
|
-
format: import_zod12.z.enum(["PNG", "JPG", "SVG", "PDF"]),
|
|
814
|
-
suffix: import_zod12.z.string().optional(),
|
|
815
|
-
contentsOnly: flexBool(import_zod12.z.boolean()).optional(),
|
|
816
|
-
constraint: flexJson(import_zod12.z.object({
|
|
817
|
-
type: import_zod12.z.enum(["SCALE", "WIDTH", "HEIGHT"]),
|
|
818
|
-
value: import_zod12.z.coerce.number()
|
|
819
|
-
})).optional()
|
|
820
|
-
});
|
|
821
|
-
var exportSettingsItem = import_zod12.z.object({
|
|
822
|
-
nodeId,
|
|
823
|
-
settings: flexJson(import_zod12.z.array(exportSettingEntry)).describe("Export settings array")
|
|
824
|
-
});
|
|
825
|
-
var nodePropertiesItem = import_zod12.z.object({
|
|
826
|
-
nodeId,
|
|
827
|
-
properties: flexJson(import_zod12.z.record(import_zod12.z.string(), import_zod12.z.unknown())).describe("Key-value properties to set")
|
|
828
|
-
});
|
|
829
|
-
function registerMcpTools10(server2, sendCommand) {
|
|
830
|
-
server2.tool(
|
|
831
|
-
"set_effects",
|
|
832
|
-
"Set effects (shadows, blurs) on nodes. Use effectStyleName to apply by name, or provide raw effects. Batch: pass multiple items.",
|
|
833
|
-
{ items: flexJson(import_zod12.z.array(effectItem)).describe("Array of {nodeId, effects}"), depth },
|
|
834
|
-
async (params) => {
|
|
835
|
-
try {
|
|
836
|
-
return mcpJson(await sendCommand("set_effects", params));
|
|
837
|
-
} catch (e) {
|
|
838
|
-
return mcpError("Error setting effects", e);
|
|
839
|
-
}
|
|
840
|
-
}
|
|
841
|
-
);
|
|
842
|
-
server2.tool(
|
|
843
|
-
"set_constraints",
|
|
844
|
-
"Set constraints on nodes. Batch: pass multiple items.",
|
|
845
|
-
{ items: flexJson(import_zod12.z.array(constraintItem)).describe("Array of {nodeId, horizontal, vertical}"), depth },
|
|
846
|
-
async (params) => {
|
|
847
|
-
try {
|
|
848
|
-
return mcpJson(await sendCommand("set_constraints", params));
|
|
849
|
-
} catch (e) {
|
|
850
|
-
return mcpError("Error setting constraints", e);
|
|
851
|
-
}
|
|
852
|
-
}
|
|
853
|
-
);
|
|
854
|
-
server2.tool(
|
|
855
|
-
"set_export_settings",
|
|
856
|
-
"Set export settings on nodes. Batch: pass multiple items.",
|
|
857
|
-
{ items: flexJson(import_zod12.z.array(exportSettingsItem)).describe("Array of {nodeId, settings}"), depth },
|
|
858
|
-
async (params) => {
|
|
859
|
-
try {
|
|
860
|
-
return mcpJson(await sendCommand("set_export_settings", params));
|
|
861
|
-
} catch (e) {
|
|
862
|
-
return mcpError("Error setting export settings", e);
|
|
863
|
-
}
|
|
864
|
-
}
|
|
865
|
-
);
|
|
866
|
-
server2.tool(
|
|
867
|
-
"set_node_properties",
|
|
868
|
-
"Set arbitrary properties on nodes. Batch: pass multiple items.",
|
|
869
|
-
{ items: flexJson(import_zod12.z.array(nodePropertiesItem)).describe("Array of {nodeId, properties}"), depth },
|
|
870
|
-
async (params) => {
|
|
871
|
-
try {
|
|
872
|
-
return mcpJson(await sendCommand("set_node_properties", params));
|
|
873
|
-
} catch (e) {
|
|
874
|
-
return mcpError("Error setting node properties", e);
|
|
875
|
-
}
|
|
876
|
-
}
|
|
877
|
-
);
|
|
878
|
-
}
|
|
879
|
-
|
|
880
|
-
// src/tools/text.ts
|
|
881
|
-
var import_zod13 = require("zod");
|
|
882
|
-
var textContentItem = import_zod13.z.object({
|
|
883
|
-
nodeId: import_zod13.z.string().describe("Text node ID"),
|
|
884
|
-
text: import_zod13.z.string().describe("New text content")
|
|
885
|
-
});
|
|
886
|
-
var textPropsItem = import_zod13.z.object({
|
|
887
|
-
nodeId: import_zod13.z.string().describe("Text node ID"),
|
|
888
|
-
fontSize: import_zod13.z.coerce.number().optional().describe("Font size"),
|
|
889
|
-
fontWeight: import_zod13.z.coerce.number().optional().describe("Font weight: 100-900"),
|
|
890
|
-
fontColor: flexJson(colorRgba).optional().describe('Font color. Hex "#000" or {r,g,b,a?} 0-1.'),
|
|
891
|
-
textStyleId: import_zod13.z.string().optional().describe("Text style ID to apply (overrides font props)"),
|
|
892
|
-
textStyleName: import_zod13.z.string().optional().describe("Text style name (case-insensitive match)"),
|
|
893
|
-
textAlignHorizontal: import_zod13.z.enum(["LEFT", "CENTER", "RIGHT", "JUSTIFIED"]).optional().describe("Horizontal text alignment"),
|
|
894
|
-
textAlignVertical: import_zod13.z.enum(["TOP", "CENTER", "BOTTOM"]).optional().describe("Vertical text alignment"),
|
|
895
|
-
textAutoResize: import_zod13.z.enum(["NONE", "WIDTH_AND_HEIGHT", "HEIGHT", "TRUNCATE"]).optional(),
|
|
896
|
-
layoutSizingHorizontal: import_zod13.z.enum(["FIXED", "HUG", "FILL"]).optional(),
|
|
897
|
-
layoutSizingVertical: import_zod13.z.enum(["FIXED", "HUG", "FILL"]).optional()
|
|
898
|
-
});
|
|
899
|
-
var scanTextItem = import_zod13.z.object({
|
|
900
|
-
nodeId,
|
|
901
|
-
limit: import_zod13.z.coerce.number().optional().describe("Max text nodes to return (default: 50)"),
|
|
902
|
-
includePath: flexBool(import_zod13.z.boolean()).optional().describe("Include ancestor path strings (default: true). Set false to reduce payload."),
|
|
903
|
-
includeGeometry: flexBool(import_zod13.z.boolean()).optional().describe("Include absoluteX/absoluteY/width/height (default: true). Set false to reduce payload.")
|
|
904
|
-
});
|
|
905
|
-
function registerMcpTools11(server2, sendCommand) {
|
|
906
|
-
server2.tool(
|
|
907
|
-
"set_text_content",
|
|
908
|
-
"Set text content on text nodes. Batch: pass multiple items to replace text in multiple nodes at once.",
|
|
909
|
-
{ items: flexJson(import_zod13.z.array(textContentItem)).describe("Array of {nodeId, text}"), depth },
|
|
910
|
-
async (params) => {
|
|
911
|
-
try {
|
|
912
|
-
return mcpJson(await sendCommand("set_text_content", params));
|
|
913
|
-
} catch (e) {
|
|
914
|
-
return mcpError("Error setting text content", e);
|
|
915
|
-
}
|
|
916
|
-
}
|
|
917
|
-
);
|
|
918
|
-
server2.tool(
|
|
919
|
-
"set_text_properties",
|
|
920
|
-
"Set font properties on existing text nodes (fontSize, fontWeight, fontColor, textStyle). Batch: pass multiple items.",
|
|
921
|
-
{ items: flexJson(import_zod13.z.array(textPropsItem)).describe("Array of {nodeId, fontSize?, fontWeight?, fontColor?, ...}"), depth },
|
|
922
|
-
async (params) => {
|
|
923
|
-
try {
|
|
924
|
-
return mcpJson(await sendCommand("set_text_properties", params));
|
|
925
|
-
} catch (e) {
|
|
926
|
-
return mcpError("Error setting text properties", e);
|
|
927
|
-
}
|
|
928
|
-
}
|
|
929
|
-
);
|
|
930
|
-
server2.tool(
|
|
931
|
-
"scan_text_nodes",
|
|
932
|
-
"Scan all text nodes within a node tree. Batch: pass multiple items.",
|
|
933
|
-
{ items: flexJson(import_zod13.z.array(scanTextItem)).describe("Array of {nodeId}") },
|
|
934
|
-
async (params) => {
|
|
935
|
-
try {
|
|
936
|
-
return mcpJson(await sendCommand("scan_text_nodes", params));
|
|
937
|
-
} catch (e) {
|
|
938
|
-
return mcpError("Error scanning text nodes", e);
|
|
939
|
-
}
|
|
940
|
-
}
|
|
941
|
-
);
|
|
942
|
-
}
|
|
943
|
-
|
|
944
|
-
// src/tools/fonts.ts
|
|
945
|
-
var import_zod14 = require("zod");
|
|
946
|
-
function registerMcpTools12(server2, sendCommand) {
|
|
947
|
-
server2.tool(
|
|
948
|
-
"get_available_fonts",
|
|
949
|
-
"Get available fonts in Figma. Optionally filter by query string.",
|
|
950
|
-
{ query: import_zod14.z.string().optional().describe("Filter fonts by name (case-insensitive). Omit to list all fonts.") },
|
|
951
|
-
async ({ query }) => {
|
|
952
|
-
try {
|
|
953
|
-
return mcpJson(await sendCommand("get_available_fonts", { query }));
|
|
954
|
-
} catch (e) {
|
|
955
|
-
return mcpError("Error getting fonts", e);
|
|
956
|
-
}
|
|
957
|
-
}
|
|
958
|
-
);
|
|
959
|
-
}
|
|
960
|
-
|
|
961
|
-
// src/tools/components.ts
|
|
962
|
-
var import_zod15 = require("zod");
|
|
963
|
-
var componentItem = import_zod15.z.object({
|
|
964
|
-
name: import_zod15.z.string().describe("Component name"),
|
|
965
|
-
x: xPos,
|
|
966
|
-
y: yPos,
|
|
967
|
-
width: import_zod15.z.coerce.number().optional().describe("Width (default: 100)"),
|
|
968
|
-
height: import_zod15.z.coerce.number().optional().describe("Height (default: 100)"),
|
|
969
|
-
parentId,
|
|
970
|
-
fillColor: flexJson(colorRgba).optional().describe('Fill color. Hex "#FF0000" or {r,g,b,a?} 0-1. Omit for no fill.'),
|
|
971
|
-
fillStyleName: import_zod15.z.string().optional().describe("Apply a fill paint style by name (case-insensitive)."),
|
|
972
|
-
fillVariableId: import_zod15.z.string().optional().describe("Bind a color variable to the fill."),
|
|
973
|
-
strokeColor: flexJson(colorRgba).optional().describe('Stroke color. Hex "#FF0000" or {r,g,b,a?} 0-1. Omit for no stroke.'),
|
|
974
|
-
strokeStyleName: import_zod15.z.string().optional().describe("Apply a stroke paint style by name."),
|
|
975
|
-
strokeVariableId: import_zod15.z.string().optional().describe("Bind a color variable to the stroke."),
|
|
976
|
-
strokeWeight: import_zod15.z.coerce.number().positive().optional().describe("Stroke weight (default: 1)"),
|
|
977
|
-
cornerRadius: import_zod15.z.coerce.number().optional().describe("Corner radius (default: 0)"),
|
|
978
|
-
layoutMode: import_zod15.z.enum(["NONE", "HORIZONTAL", "VERTICAL"]).optional().describe("Layout direction (default: NONE)"),
|
|
979
|
-
layoutWrap: import_zod15.z.enum(["NO_WRAP", "WRAP"]).optional().describe("Wrap behavior (default: NO_WRAP)"),
|
|
980
|
-
paddingTop: import_zod15.z.coerce.number().optional().describe("Top padding (default: 0)"),
|
|
981
|
-
paddingRight: import_zod15.z.coerce.number().optional().describe("Right padding (default: 0)"),
|
|
982
|
-
paddingBottom: import_zod15.z.coerce.number().optional().describe("Bottom padding (default: 0)"),
|
|
983
|
-
paddingLeft: import_zod15.z.coerce.number().optional().describe("Left padding (default: 0)"),
|
|
984
|
-
primaryAxisAlignItems: import_zod15.z.enum(["MIN", "MAX", "CENTER", "SPACE_BETWEEN"]).optional().describe("Primary axis alignment (default: MIN)"),
|
|
985
|
-
counterAxisAlignItems: import_zod15.z.enum(["MIN", "MAX", "CENTER", "BASELINE"]).optional().describe("Counter axis alignment (default: MIN)"),
|
|
986
|
-
layoutSizingHorizontal: import_zod15.z.enum(["FIXED", "HUG", "FILL"]).optional().describe("Horizontal sizing (default: FIXED)"),
|
|
987
|
-
layoutSizingVertical: import_zod15.z.enum(["FIXED", "HUG", "FILL"]).optional().describe("Vertical sizing (default: FIXED)"),
|
|
988
|
-
itemSpacing: import_zod15.z.coerce.number().optional().describe("Spacing between children (default: 0)")
|
|
989
|
-
});
|
|
990
|
-
var fromNodeItem = import_zod15.z.object({
|
|
991
|
-
nodeId
|
|
992
|
-
});
|
|
993
|
-
var combineItem = import_zod15.z.object({
|
|
994
|
-
componentIds: flexJson(import_zod15.z.array(import_zod15.z.string())).describe("Component IDs to combine (min 2)"),
|
|
995
|
-
name: import_zod15.z.string().optional().describe("Name for the component set. Omit to auto-generate.")
|
|
996
|
-
});
|
|
997
|
-
var propItem = import_zod15.z.object({
|
|
998
|
-
componentId: import_zod15.z.string().describe("Component node ID"),
|
|
999
|
-
propertyName: import_zod15.z.string().describe("Property name"),
|
|
1000
|
-
type: import_zod15.z.enum(["BOOLEAN", "TEXT", "INSTANCE_SWAP", "VARIANT"]).describe("Property type"),
|
|
1001
|
-
defaultValue: flexBool(import_zod15.z.union([import_zod15.z.string(), import_zod15.z.boolean()])).describe("Default value (string for TEXT/VARIANT, boolean for BOOLEAN)"),
|
|
1002
|
-
preferredValues: flexJson(import_zod15.z.array(import_zod15.z.object({
|
|
1003
|
-
type: import_zod15.z.enum(["COMPONENT", "COMPONENT_SET"]),
|
|
1004
|
-
key: import_zod15.z.string()
|
|
1005
|
-
})).optional()).describe("Preferred values for INSTANCE_SWAP type. Omit for none.")
|
|
1006
|
-
});
|
|
1007
|
-
var instanceItem = import_zod15.z.object({
|
|
1008
|
-
componentId: import_zod15.z.string().describe("Component or component set ID"),
|
|
1009
|
-
variantProperties: flexJson(import_zod15.z.record(import_zod15.z.string(), import_zod15.z.string())).optional().describe('Pick variant by properties, e.g. {"Style":"Secondary","Size":"Large"}. Ignored for plain COMPONENT IDs.'),
|
|
1010
|
-
x: import_zod15.z.coerce.number().optional().describe("X position. Omit to keep default."),
|
|
1011
|
-
y: import_zod15.z.coerce.number().optional().describe("Y position. Omit to keep default."),
|
|
1012
|
-
parentId
|
|
1013
|
-
});
|
|
1014
|
-
function registerMcpTools13(server2, sendCommand) {
|
|
1015
|
-
server2.tool(
|
|
1016
|
-
"create_component",
|
|
1017
|
-
"Create components in Figma. Same layout params as create_frame. Name with 'Property=Value' pattern (e.g. 'Size=Small') if you plan to combine_as_variants later. Use fillStyleName/fillVariableId over hardcoded colors. After adding text children, use add_component_property to expose text as editable properties. Batch: pass multiple items.",
|
|
1018
|
-
{ items: flexJson(import_zod15.z.array(componentItem)).describe("Array of components to create"), depth },
|
|
1019
|
-
async (params) => {
|
|
1020
|
-
try {
|
|
1021
|
-
return mcpJson(await sendCommand("create_component", params));
|
|
1022
|
-
} catch (e) {
|
|
1023
|
-
return mcpError("Error creating component", e);
|
|
1024
|
-
}
|
|
1025
|
-
}
|
|
1026
|
-
);
|
|
1027
|
-
server2.tool(
|
|
1028
|
-
"create_component_from_node",
|
|
1029
|
-
"Convert existing nodes into components. Batch: pass multiple items.",
|
|
1030
|
-
{ items: flexJson(import_zod15.z.array(fromNodeItem)).describe("Array of {nodeId}"), depth },
|
|
1031
|
-
async (params) => {
|
|
1032
|
-
try {
|
|
1033
|
-
return mcpJson(await sendCommand("create_component_from_node", params));
|
|
1034
|
-
} catch (e) {
|
|
1035
|
-
return mcpError("Error creating component from node", e);
|
|
1036
|
-
}
|
|
1037
|
-
}
|
|
1038
|
-
);
|
|
1039
|
-
server2.tool(
|
|
1040
|
-
"combine_as_variants",
|
|
1041
|
-
"Combine components into variant sets. Name components with 'Property=Value' pattern (e.g. 'Style=Primary', 'Size=Large') BEFORE combining \u2014 Figma derives variant properties from component names. Avoid slashes in names. The resulting set is placed in the components' shared parent (or page root if parents differ). Batch: pass multiple items.",
|
|
1042
|
-
{ items: flexJson(import_zod15.z.array(combineItem)).describe("Array of {componentIds, name?}"), depth },
|
|
1043
|
-
async (params) => {
|
|
1044
|
-
try {
|
|
1045
|
-
return mcpJson(await sendCommand("combine_as_variants", params));
|
|
1046
|
-
} catch (e) {
|
|
1047
|
-
return mcpError("Error combining variants", e);
|
|
1048
|
-
}
|
|
1049
|
-
}
|
|
1050
|
-
);
|
|
1051
|
-
server2.tool(
|
|
1052
|
-
"add_component_property",
|
|
1053
|
-
"Add properties to components. Batch: pass multiple items.",
|
|
1054
|
-
{ items: flexJson(import_zod15.z.array(propItem)).describe("Array of {componentId, propertyName, type, defaultValue, preferredValues?}") },
|
|
1055
|
-
async (params) => {
|
|
1056
|
-
try {
|
|
1057
|
-
return mcpJson(await sendCommand("add_component_property", params));
|
|
1058
|
-
} catch (e) {
|
|
1059
|
-
return mcpError("Error adding component property", e);
|
|
1060
|
-
}
|
|
1061
|
-
}
|
|
1062
|
-
);
|
|
1063
|
-
server2.tool(
|
|
1064
|
-
"create_instance_from_local",
|
|
1065
|
-
'Create instances of local components. For COMPONENT_SET, use variantProperties to pick a specific variant (e.g. {"Style":"Secondary"}). Batch: pass multiple items.',
|
|
1066
|
-
{ items: flexJson(import_zod15.z.array(instanceItem)).describe("Array of {componentId, x?, y?, parentId?}") },
|
|
1067
|
-
async (params) => {
|
|
1068
|
-
try {
|
|
1069
|
-
return mcpJson(await sendCommand("create_instance_from_local", params));
|
|
1070
|
-
} catch (e) {
|
|
1071
|
-
return mcpError("Error creating instance", e);
|
|
1072
|
-
}
|
|
1073
|
-
}
|
|
1074
|
-
);
|
|
1075
|
-
server2.tool(
|
|
1076
|
-
"search_components",
|
|
1077
|
-
"Search local components and component sets across all pages. Returns component id, name, and which page it lives on.",
|
|
1078
|
-
{
|
|
1079
|
-
query: import_zod15.z.string().optional().describe("Filter by name (case-insensitive substring). Omit to list all."),
|
|
1080
|
-
setsOnly: flexBool(import_zod15.z.boolean()).optional().describe("If true, return only COMPONENT_SET nodes"),
|
|
1081
|
-
limit: import_zod15.z.coerce.number().optional().describe("Max results (default 100)"),
|
|
1082
|
-
offset: import_zod15.z.coerce.number().optional().describe("Skip N results (default 0)")
|
|
1083
|
-
},
|
|
1084
|
-
async (params) => {
|
|
1085
|
-
try {
|
|
1086
|
-
return mcpJson(await sendCommand("search_components", params));
|
|
1087
|
-
} catch (e) {
|
|
1088
|
-
return mcpError("Error searching components", e);
|
|
1089
|
-
}
|
|
1090
|
-
}
|
|
1091
|
-
);
|
|
1092
|
-
server2.tool(
|
|
1093
|
-
"get_component_by_id",
|
|
1094
|
-
"Get detailed component info including property definitions and variants.",
|
|
1095
|
-
{
|
|
1096
|
-
componentId: import_zod15.z.string().describe("Component node ID"),
|
|
1097
|
-
includeChildren: flexBool(import_zod15.z.boolean()).optional().describe("For COMPONENT_SETs: include variant children (default false)")
|
|
1098
|
-
},
|
|
1099
|
-
async (params) => {
|
|
1100
|
-
try {
|
|
1101
|
-
return mcpJson(await sendCommand("get_component_by_id", params));
|
|
1102
|
-
} catch (e) {
|
|
1103
|
-
return mcpError("Error getting component", e);
|
|
1104
|
-
}
|
|
1105
|
-
}
|
|
1106
|
-
);
|
|
1107
|
-
server2.tool(
|
|
1108
|
-
"get_instance_overrides",
|
|
1109
|
-
"Get override properties from a component instance.",
|
|
1110
|
-
{ nodeId: import_zod15.z.string().optional().describe("Instance node ID (uses selection if omitted)") },
|
|
1111
|
-
async ({ nodeId: nodeId2 }) => {
|
|
1112
|
-
try {
|
|
1113
|
-
return mcpJson(await sendCommand("get_instance_overrides", { instanceNodeId: nodeId2 || null }));
|
|
1114
|
-
} catch (e) {
|
|
1115
|
-
return mcpError("Error getting overrides", e);
|
|
1116
|
-
}
|
|
1117
|
-
}
|
|
1118
|
-
);
|
|
1119
|
-
server2.tool(
|
|
1120
|
-
"set_instance_properties",
|
|
1121
|
-
"Set component property values on instances (e.g. text, boolean, instance swap). Use get_component_by_id to discover property keys. Batch: pass multiple items.",
|
|
1122
|
-
{ items: flexJson(import_zod15.z.array(import_zod15.z.object({
|
|
1123
|
-
nodeId,
|
|
1124
|
-
properties: flexJson(import_zod15.z.record(import_zod15.z.string(), import_zod15.z.union([import_zod15.z.string(), import_zod15.z.boolean()]))).describe('Property key\u2192value map, e.g. {"Label#1:0":"Click Me"}')
|
|
1125
|
-
}))).describe("Array of {nodeId, properties}"), depth },
|
|
1126
|
-
async (params) => {
|
|
1127
|
-
try {
|
|
1128
|
-
return mcpJson(await sendCommand("set_instance_properties", params));
|
|
1129
|
-
} catch (e) {
|
|
1130
|
-
return mcpError("Error setting instance properties", e);
|
|
1131
|
-
}
|
|
1132
|
-
}
|
|
1133
|
-
);
|
|
1134
|
-
}
|
|
1135
|
-
|
|
1136
|
-
// src/tools/styles.ts
|
|
1137
|
-
var import_zod16 = require("zod");
|
|
1138
|
-
var paintStyleItem = import_zod16.z.object({
|
|
1139
|
-
name: import_zod16.z.string().describe("Style name"),
|
|
1140
|
-
color: flexJson(colorRgba).describe('Color. Hex "#FF0000" or {r,g,b,a?} 0-1.')
|
|
1141
|
-
});
|
|
1142
|
-
var textStyleItem = import_zod16.z.object({
|
|
1143
|
-
name: import_zod16.z.string().describe("Style name"),
|
|
1144
|
-
fontFamily: import_zod16.z.string().describe("Font family"),
|
|
1145
|
-
fontStyle: import_zod16.z.string().optional().describe("Font style (default: Regular)"),
|
|
1146
|
-
fontSize: import_zod16.z.coerce.number().describe("Font size"),
|
|
1147
|
-
lineHeight: flexNum(import_zod16.z.union([
|
|
1148
|
-
import_zod16.z.number(),
|
|
1149
|
-
import_zod16.z.object({ value: import_zod16.z.coerce.number(), unit: import_zod16.z.enum(["PIXELS", "PERCENT", "AUTO"]) })
|
|
1150
|
-
])).optional().describe("Line height \u2014 number (px) or {value, unit}. Default: auto."),
|
|
1151
|
-
letterSpacing: flexNum(import_zod16.z.union([
|
|
1152
|
-
import_zod16.z.number(),
|
|
1153
|
-
import_zod16.z.object({ value: import_zod16.z.coerce.number(), unit: import_zod16.z.enum(["PIXELS", "PERCENT"]) })
|
|
1154
|
-
])).optional().describe("Letter spacing \u2014 number (px) or {value, unit}. Default: 0."),
|
|
1155
|
-
textCase: import_zod16.z.enum(["ORIGINAL", "UPPER", "LOWER", "TITLE"]).optional(),
|
|
1156
|
-
textDecoration: import_zod16.z.enum(["NONE", "UNDERLINE", "STRIKETHROUGH"]).optional()
|
|
1157
|
-
});
|
|
1158
|
-
var effectStyleItem = import_zod16.z.object({
|
|
1159
|
-
name: import_zod16.z.string().describe("Style name"),
|
|
1160
|
-
effects: flexJson(import_zod16.z.array(effectEntry)).describe("Array of effects")
|
|
1161
|
-
});
|
|
1162
|
-
var updatePaintStyleItem = import_zod16.z.object({
|
|
1163
|
-
id: import_zod16.z.string().describe("Style ID or name (case-insensitive match)"),
|
|
1164
|
-
name: import_zod16.z.string().optional().describe("New name"),
|
|
1165
|
-
color: flexJson(colorRgba).optional().describe('New color. Hex "#FF0000" or {r,g,b,a?} 0-1.')
|
|
1166
|
-
});
|
|
1167
|
-
var updateTextStyleItem = import_zod16.z.object({
|
|
1168
|
-
id: import_zod16.z.string().describe("Style ID or name (case-insensitive match)"),
|
|
1169
|
-
name: import_zod16.z.string().optional().describe("New name"),
|
|
1170
|
-
fontFamily: import_zod16.z.string().optional().describe("Font family"),
|
|
1171
|
-
fontStyle: import_zod16.z.string().optional().describe("Font style (e.g. Regular, Bold)"),
|
|
1172
|
-
fontSize: import_zod16.z.coerce.number().optional().describe("Font size"),
|
|
1173
|
-
lineHeight: flexNum(import_zod16.z.union([
|
|
1174
|
-
import_zod16.z.number(),
|
|
1175
|
-
import_zod16.z.object({ value: import_zod16.z.coerce.number(), unit: import_zod16.z.enum(["PIXELS", "PERCENT", "AUTO"]) })
|
|
1176
|
-
])).optional().describe("Line height \u2014 number (px) or {value, unit}. Default: auto."),
|
|
1177
|
-
letterSpacing: flexNum(import_zod16.z.union([
|
|
1178
|
-
import_zod16.z.number(),
|
|
1179
|
-
import_zod16.z.object({ value: import_zod16.z.coerce.number(), unit: import_zod16.z.enum(["PIXELS", "PERCENT"]) })
|
|
1180
|
-
])).optional().describe("Letter spacing \u2014 number (px) or {value, unit}. Default: 0."),
|
|
1181
|
-
textCase: import_zod16.z.enum(["ORIGINAL", "UPPER", "LOWER", "TITLE"]).optional(),
|
|
1182
|
-
textDecoration: import_zod16.z.enum(["NONE", "UNDERLINE", "STRIKETHROUGH"]).optional()
|
|
1183
|
-
});
|
|
1184
|
-
var applyStyleItem = import_zod16.z.object({
|
|
1185
|
-
nodeId,
|
|
1186
|
-
styleId: import_zod16.z.string().optional().describe("Style ID. Provide either styleId or styleName."),
|
|
1187
|
-
styleName: import_zod16.z.string().optional().describe("Style name (case-insensitive substring match). Provide either styleId or styleName."),
|
|
1188
|
-
styleType: import_zod16.z.preprocess((v) => typeof v === "string" ? v.toLowerCase() : v, import_zod16.z.enum(["fill", "stroke", "text", "effect"])).describe("Type of style: fill, stroke, text, or effect (case-insensitive)")
|
|
1189
|
-
});
|
|
1190
|
-
function registerMcpTools14(server2, sendCommand) {
|
|
1191
|
-
server2.tool(
|
|
1192
|
-
"get_styles",
|
|
1193
|
-
"List local styles (paint, text, effect, grid). Returns IDs and names only.",
|
|
1194
|
-
{},
|
|
1195
|
-
async () => {
|
|
1196
|
-
try {
|
|
1197
|
-
return mcpJson(await sendCommand("get_styles"));
|
|
1198
|
-
} catch (e) {
|
|
1199
|
-
return mcpError("Error getting styles", e);
|
|
1200
|
-
}
|
|
1201
|
-
}
|
|
1202
|
-
);
|
|
1203
|
-
server2.tool(
|
|
1204
|
-
"get_style_by_id",
|
|
1205
|
-
"Get detailed style info by ID. Returns full paint/font/effect/grid details.",
|
|
1206
|
-
{ styleId: import_zod16.z.string().describe("Style ID") },
|
|
1207
|
-
async ({ styleId }) => {
|
|
1208
|
-
try {
|
|
1209
|
-
return mcpJson(await sendCommand("get_style_by_id", { styleId }));
|
|
1210
|
-
} catch (e) {
|
|
1211
|
-
return mcpError("Error getting style", e);
|
|
1212
|
-
}
|
|
1213
|
-
}
|
|
1214
|
-
);
|
|
1215
|
-
server2.tool(
|
|
1216
|
-
"remove_style",
|
|
1217
|
-
"Delete a style by ID.",
|
|
1218
|
-
{ styleId: import_zod16.z.string().describe("Style ID to remove") },
|
|
1219
|
-
async ({ styleId }) => {
|
|
1220
|
-
try {
|
|
1221
|
-
return mcpJson(await sendCommand("remove_style", { styleId }));
|
|
1222
|
-
} catch (e) {
|
|
1223
|
-
return mcpError("Error removing style", e);
|
|
1224
|
-
}
|
|
1225
|
-
}
|
|
1226
|
-
);
|
|
1227
|
-
server2.tool(
|
|
1228
|
-
"create_paint_style",
|
|
1229
|
-
"Create color/paint styles. Batch: pass multiple items.",
|
|
1230
|
-
{ items: flexJson(import_zod16.z.array(paintStyleItem)).describe("Array of {name, color}") },
|
|
1231
|
-
async (params) => {
|
|
1232
|
-
try {
|
|
1233
|
-
return mcpJson(await sendCommand("create_paint_style", params));
|
|
1234
|
-
} catch (e) {
|
|
1235
|
-
return mcpError("Error creating paint style", e);
|
|
1236
|
-
}
|
|
1237
|
-
}
|
|
1238
|
-
);
|
|
1239
|
-
server2.tool(
|
|
1240
|
-
"create_text_style",
|
|
1241
|
-
"Create text styles. Batch: pass multiple items.",
|
|
1242
|
-
{ items: flexJson(import_zod16.z.array(textStyleItem)).describe("Array of text style definitions") },
|
|
1243
|
-
async (params) => {
|
|
1244
|
-
try {
|
|
1245
|
-
return mcpJson(await sendCommand("create_text_style", params));
|
|
1246
|
-
} catch (e) {
|
|
1247
|
-
return mcpError("Error creating text style", e);
|
|
1248
|
-
}
|
|
1249
|
-
}
|
|
1250
|
-
);
|
|
1251
|
-
server2.tool(
|
|
1252
|
-
"create_effect_style",
|
|
1253
|
-
"Create effect styles (shadows, blurs). Batch: pass multiple items.",
|
|
1254
|
-
{ items: flexJson(import_zod16.z.array(effectStyleItem)).describe("Array of {name, effects}") },
|
|
1255
|
-
async (params) => {
|
|
1256
|
-
try {
|
|
1257
|
-
return mcpJson(await sendCommand("create_effect_style", params));
|
|
1258
|
-
} catch (e) {
|
|
1259
|
-
return mcpError("Error creating effect style", e);
|
|
1260
|
-
}
|
|
1261
|
-
}
|
|
1262
|
-
);
|
|
1263
|
-
server2.tool(
|
|
1264
|
-
"apply_style_to_node",
|
|
1265
|
-
"Apply a style to nodes by ID or name. Use styleName for convenience (case-insensitive). Batch: pass multiple items.",
|
|
1266
|
-
{ items: flexJson(import_zod16.z.array(applyStyleItem)).describe("Array of {nodeId, styleId?, styleName?, styleType}"), depth },
|
|
1267
|
-
async (params) => {
|
|
1268
|
-
try {
|
|
1269
|
-
return mcpJson(await sendCommand("apply_style_to_node", params));
|
|
1270
|
-
} catch (e) {
|
|
1271
|
-
return mcpError("Error applying style", e);
|
|
1272
|
-
}
|
|
1273
|
-
}
|
|
1274
|
-
);
|
|
1275
|
-
server2.tool(
|
|
1276
|
-
"update_paint_style",
|
|
1277
|
-
"Update paint style color/name by ID or name. Changes propagate to all nodes using the style. Batch: pass multiple items.",
|
|
1278
|
-
{ items: flexJson(import_zod16.z.array(updatePaintStyleItem)).describe("Array of {id, name?, color?}") },
|
|
1279
|
-
async (params) => {
|
|
1280
|
-
try {
|
|
1281
|
-
return mcpJson(await sendCommand("update_paint_style", params));
|
|
1282
|
-
} catch (e) {
|
|
1283
|
-
return mcpError("Error updating paint style", e);
|
|
1284
|
-
}
|
|
1285
|
-
}
|
|
1286
|
-
);
|
|
1287
|
-
server2.tool(
|
|
1288
|
-
"update_text_style",
|
|
1289
|
-
"Update text style properties by ID or name. Changes propagate to all nodes using the style. Batch: pass multiple items.",
|
|
1290
|
-
{ items: flexJson(import_zod16.z.array(updateTextStyleItem)).describe("Array of {id, name?, fontSize?, fontFamily?, ...}") },
|
|
1291
|
-
async (params) => {
|
|
1292
|
-
try {
|
|
1293
|
-
return mcpJson(await sendCommand("update_text_style", params));
|
|
1294
|
-
} catch (e) {
|
|
1295
|
-
return mcpError("Error updating text style", e);
|
|
1296
|
-
}
|
|
1297
|
-
}
|
|
1298
|
-
);
|
|
1299
|
-
}
|
|
330
|
+
paddingRight: import_zod7.z.coerce.number().optional().describe("Right padding (default: 0)"),
|
|
331
|
+
paddingBottom: import_zod7.z.coerce.number().optional().describe("Bottom padding (default: 0)"),
|
|
332
|
+
paddingLeft: import_zod7.z.coerce.number().optional().describe("Left padding (default: 0)"),
|
|
333
|
+
primaryAxisAlignItems: import_zod7.z.enum(["MIN", "MAX", "CENTER", "SPACE_BETWEEN"]).optional(),
|
|
334
|
+
counterAxisAlignItems: import_zod7.z.enum(["MIN", "MAX", "CENTER", "BASELINE"]).optional(),
|
|
335
|
+
layoutSizingHorizontal: import_zod7.z.enum(["FIXED", "HUG", "FILL"]).optional(),
|
|
336
|
+
layoutSizingVertical: import_zod7.z.enum(["FIXED", "HUG", "FILL"]).optional(),
|
|
337
|
+
layoutWrap: import_zod7.z.enum(["NO_WRAP", "WRAP"]).optional()
|
|
338
|
+
});
|
|
339
|
+
var tools6 = [
|
|
340
|
+
{
|
|
341
|
+
name: "create_frame",
|
|
342
|
+
description: "Create frames in Figma. Batch supported. Use fillStyleName/fillVariableId and strokeStyleName/strokeVariableId instead of hardcoded colors \u2014 hardcoded values skip design tokens and will trigger lint warnings.",
|
|
343
|
+
schema: { items: flexJson(import_zod7.z.array(frameItem)).describe("Array of frames to create"), depth },
|
|
344
|
+
tier: "create"
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
name: "create_auto_layout",
|
|
348
|
+
description: "Wrap existing nodes in an auto-layout frame. One call replaces create_frame + update_frame + insert_child \xD7 N.",
|
|
349
|
+
schema: { items: flexJson(import_zod7.z.array(autoLayoutItem)).describe("Array of auto-layout wraps to perform"), depth },
|
|
350
|
+
tier: "create"
|
|
351
|
+
}
|
|
352
|
+
];
|
|
1300
353
|
|
|
1301
|
-
// src/tools/
|
|
1302
|
-
var
|
|
1303
|
-
var
|
|
1304
|
-
|
|
354
|
+
// src/tools/defs/create-text.ts
|
|
355
|
+
var import_zod8 = require("zod");
|
|
356
|
+
var textItem = import_zod8.z.object({
|
|
357
|
+
text: import_zod8.z.string().describe("Text content"),
|
|
358
|
+
name: import_zod8.z.string().optional().describe("Layer name (default: text content)"),
|
|
359
|
+
x: xPos,
|
|
360
|
+
y: yPos,
|
|
361
|
+
fontFamily: import_zod8.z.string().optional().describe("Font family (default: Inter). Use get_available_fonts to list installed fonts."),
|
|
362
|
+
fontStyle: import_zod8.z.string().optional().describe("Font style, e.g. 'Regular', 'Bold', 'Italic' (default: derived from fontWeight). Overrides fontWeight when set."),
|
|
363
|
+
fontSize: import_zod8.z.coerce.number().optional().describe("Font size (default: 14)"),
|
|
364
|
+
fontWeight: import_zod8.z.coerce.number().optional().describe("Font weight: 100-900 (default: 400). Ignored when fontStyle is set."),
|
|
365
|
+
fontColor: flexJson(colorRgba).optional().describe("Font color. Default: black."),
|
|
366
|
+
fontColorVariableId: import_zod8.z.string().optional().describe("Bind a color variable to the text fill instead of hardcoded fontColor."),
|
|
367
|
+
fontColorStyleName: import_zod8.z.string().optional().describe("Apply a paint style to the text fill by name (case-insensitive). Overrides fontColor."),
|
|
368
|
+
parentId,
|
|
369
|
+
textStyleId: import_zod8.z.string().optional().describe("Text style ID to apply (overrides fontSize/fontWeight). Omit to skip."),
|
|
370
|
+
textStyleName: import_zod8.z.string().optional().describe("Text style name (case-insensitive match). Omit to skip."),
|
|
371
|
+
textAlignHorizontal: import_zod8.z.enum(["LEFT", "CENTER", "RIGHT", "JUSTIFIED"]).optional().describe("Horizontal text alignment (default: LEFT)"),
|
|
372
|
+
textAlignVertical: import_zod8.z.enum(["TOP", "CENTER", "BOTTOM"]).optional().describe("Vertical text alignment (default: TOP)"),
|
|
373
|
+
layoutSizingHorizontal: import_zod8.z.enum(["FIXED", "HUG", "FILL"]).optional().describe("Horizontal sizing. FILL auto-sets textAutoResize to HEIGHT."),
|
|
374
|
+
layoutSizingVertical: import_zod8.z.enum(["FIXED", "HUG", "FILL"]).optional().describe("Vertical sizing (default: HUG)"),
|
|
375
|
+
textAutoResize: import_zod8.z.enum(["NONE", "WIDTH_AND_HEIGHT", "HEIGHT", "TRUNCATE"]).optional().describe("Text auto-resize behavior (default: WIDTH_AND_HEIGHT when FILL)")
|
|
1305
376
|
});
|
|
1306
|
-
var
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
377
|
+
var tools7 = [
|
|
378
|
+
{
|
|
379
|
+
name: "create_text",
|
|
380
|
+
description: "Create text nodes. Max 10 per batch. Prefer textStyleName for typography and fontColorStyleName or fontColorVariableId for color \u2014 hardcoded values skip design tokens. Supports custom fonts via fontFamily.",
|
|
381
|
+
schema: { items: flexJson(import_zod8.z.array(textItem).max(10)).describe("Array of text nodes to create (max 10)"), depth },
|
|
382
|
+
tier: "create"
|
|
383
|
+
}
|
|
384
|
+
];
|
|
385
|
+
|
|
386
|
+
// src/tools/defs/modify-node.ts
|
|
387
|
+
var import_zod9 = require("zod");
|
|
388
|
+
var deleteItem = import_zod9.z.object({
|
|
389
|
+
nodeId: import_zod9.z.string().describe("Node ID to delete")
|
|
1310
390
|
});
|
|
1311
|
-
var
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
import_zod17.z.boolean(),
|
|
1317
|
-
colorRgba
|
|
1318
|
-
])).describe('Value: number, boolean, or color (hex "#RRGGBB" or {r,g,b,a?} 0-1)')
|
|
391
|
+
var cloneItem = import_zod9.z.object({
|
|
392
|
+
nodeId: import_zod9.z.string().describe("Node ID to clone"),
|
|
393
|
+
parentId: import_zod9.z.string().optional().describe("Parent for the clone (e.g. a page ID). Defaults to same parent as original."),
|
|
394
|
+
x: import_zod9.z.coerce.number().optional().describe("New X for clone. Omit to keep original position."),
|
|
395
|
+
y: import_zod9.z.coerce.number().optional().describe("New Y for clone. Omit to keep original position.")
|
|
1319
396
|
});
|
|
1320
|
-
var
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
397
|
+
var insertItem = import_zod9.z.object({
|
|
398
|
+
parentId: import_zod9.z.string().describe("Parent node ID"),
|
|
399
|
+
childId: import_zod9.z.string().describe("Child node ID to move"),
|
|
400
|
+
index: import_zod9.z.coerce.number().optional().describe("Index to insert at (0=first). Omit to append.")
|
|
1324
401
|
});
|
|
1325
|
-
var
|
|
1326
|
-
|
|
1327
|
-
|
|
402
|
+
var tools8 = [
|
|
403
|
+
{
|
|
404
|
+
name: "delete_node",
|
|
405
|
+
description: "Delete nodes. Batch: pass multiple items.",
|
|
406
|
+
schema: { items: flexJson(import_zod9.z.array(deleteItem)).describe("Array of {nodeId}") },
|
|
407
|
+
tier: "edit"
|
|
408
|
+
},
|
|
409
|
+
{
|
|
410
|
+
name: "clone_node",
|
|
411
|
+
description: "Clone nodes. Batch: pass multiple items.",
|
|
412
|
+
schema: { items: flexJson(import_zod9.z.array(cloneItem)).describe("Array of {nodeId, x?, y?}"), depth },
|
|
413
|
+
tier: "create"
|
|
414
|
+
},
|
|
415
|
+
{
|
|
416
|
+
name: "insert_child",
|
|
417
|
+
description: "Move nodes into a parent at a specific index (reorder/reparent). Batch: pass multiple items.",
|
|
418
|
+
schema: { items: flexJson(import_zod9.z.array(insertItem)).describe("Array of {parentId, childId, index?}"), depth },
|
|
419
|
+
tier: "edit"
|
|
420
|
+
}
|
|
421
|
+
];
|
|
422
|
+
|
|
423
|
+
// src/tools/defs/patch-nodes.ts
|
|
424
|
+
var import_zod10 = require("zod");
|
|
425
|
+
var exportSettingEntry = import_zod10.z.object({
|
|
426
|
+
format: import_zod10.z.enum(["PNG", "JPG", "SVG", "PDF"]),
|
|
427
|
+
suffix: import_zod10.z.string().optional(),
|
|
428
|
+
contentsOnly: flexBool(import_zod10.z.boolean()).optional(),
|
|
429
|
+
constraint: flexJson(import_zod10.z.object({
|
|
430
|
+
type: import_zod10.z.enum(["SCALE", "WIDTH", "HEIGHT"]),
|
|
431
|
+
value: import_zod10.z.coerce.number()
|
|
432
|
+
})).optional()
|
|
1328
433
|
});
|
|
1329
|
-
var
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
434
|
+
var patchNodeItem = import_zod10.z.object({
|
|
435
|
+
nodeId,
|
|
436
|
+
// Geometry (flat)
|
|
437
|
+
x: import_zod10.z.coerce.number().optional().describe("X position"),
|
|
438
|
+
y: import_zod10.z.coerce.number().optional().describe("Y position"),
|
|
439
|
+
width: import_zod10.z.coerce.number().positive().optional().describe("Width (must provide height too)"),
|
|
440
|
+
height: import_zod10.z.coerce.number().positive().optional().describe("Height (must provide width too)"),
|
|
441
|
+
// Appearance (nested)
|
|
442
|
+
fill: flexJson(import_zod10.z.object({
|
|
443
|
+
color: flexJson(colorRgba).optional(),
|
|
444
|
+
styleName: import_zod10.z.string().optional().describe("Paint style name (preferred over color)")
|
|
445
|
+
})).optional().describe("Fill color or style"),
|
|
446
|
+
stroke: flexJson(import_zod10.z.object({
|
|
447
|
+
color: flexJson(colorRgba).optional(),
|
|
448
|
+
weight: import_zod10.z.coerce.number().positive().optional().describe("Stroke weight"),
|
|
449
|
+
styleName: import_zod10.z.string().optional().describe("Paint style name (preferred over color)")
|
|
450
|
+
})).optional().describe("Stroke color/weight or style"),
|
|
451
|
+
cornerRadius: flexJson(import_zod10.z.object({
|
|
452
|
+
radius: import_zod10.z.coerce.number().min(0).describe("Corner radius"),
|
|
453
|
+
corners: flexJson(import_zod10.z.array(flexBool(import_zod10.z.boolean())).length(4)).optional().describe("Which corners [topLeft, topRight, bottomRight, bottomLeft]. Default: all.")
|
|
454
|
+
})).optional().describe("Corner radius"),
|
|
455
|
+
opacity: import_zod10.z.coerce.number().min(0).max(1).optional().describe("Opacity (0-1)"),
|
|
456
|
+
effects: flexJson(import_zod10.z.object({
|
|
457
|
+
effects: flexJson(import_zod10.z.array(effectEntry)).optional().describe("Effect objects"),
|
|
458
|
+
styleName: import_zod10.z.string().optional().describe("Effect style name (preferred over raw effects)")
|
|
459
|
+
})).optional().describe("Effects or effect style"),
|
|
460
|
+
constraints: flexJson(import_zod10.z.object({
|
|
461
|
+
horizontal: import_zod10.z.enum(["MIN", "CENTER", "MAX", "STRETCH", "SCALE"]),
|
|
462
|
+
vertical: import_zod10.z.enum(["MIN", "CENTER", "MAX", "STRETCH", "SCALE"])
|
|
463
|
+
})).optional().describe("Layout constraints"),
|
|
464
|
+
exportSettings: flexJson(import_zod10.z.array(exportSettingEntry)).optional().describe("Export settings"),
|
|
465
|
+
// Layout (nested)
|
|
466
|
+
layout: flexJson(import_zod10.z.object({
|
|
467
|
+
layoutMode: import_zod10.z.enum(["NONE", "HORIZONTAL", "VERTICAL"]).optional(),
|
|
468
|
+
layoutWrap: import_zod10.z.enum(["NO_WRAP", "WRAP"]).optional(),
|
|
469
|
+
paddingTop: import_zod10.z.coerce.number().optional(),
|
|
470
|
+
paddingRight: import_zod10.z.coerce.number().optional(),
|
|
471
|
+
paddingBottom: import_zod10.z.coerce.number().optional(),
|
|
472
|
+
paddingLeft: import_zod10.z.coerce.number().optional(),
|
|
473
|
+
primaryAxisAlignItems: import_zod10.z.enum(["MIN", "MAX", "CENTER", "SPACE_BETWEEN"]).optional(),
|
|
474
|
+
counterAxisAlignItems: import_zod10.z.enum(["MIN", "MAX", "CENTER", "BASELINE"]).optional(),
|
|
475
|
+
layoutSizingHorizontal: import_zod10.z.enum(["FIXED", "HUG", "FILL"]).optional(),
|
|
476
|
+
layoutSizingVertical: import_zod10.z.enum(["FIXED", "HUG", "FILL"]).optional(),
|
|
477
|
+
itemSpacing: import_zod10.z.coerce.number().optional(),
|
|
478
|
+
counterAxisSpacing: import_zod10.z.coerce.number().optional()
|
|
479
|
+
})).optional().describe("Auto-layout properties"),
|
|
480
|
+
// Text (nested)
|
|
481
|
+
text: flexJson(import_zod10.z.object({
|
|
482
|
+
fontSize: import_zod10.z.coerce.number().optional(),
|
|
483
|
+
fontWeight: import_zod10.z.coerce.number().optional(),
|
|
484
|
+
fontColor: flexJson(colorRgba).optional(),
|
|
485
|
+
textStyleId: import_zod10.z.string().optional(),
|
|
486
|
+
textStyleName: import_zod10.z.string().optional(),
|
|
487
|
+
textAlignHorizontal: import_zod10.z.enum(["LEFT", "CENTER", "RIGHT", "JUSTIFIED"]).optional(),
|
|
488
|
+
textAlignVertical: import_zod10.z.enum(["TOP", "CENTER", "BOTTOM"]).optional(),
|
|
489
|
+
textAutoResize: import_zod10.z.enum(["NONE", "WIDTH_AND_HEIGHT", "HEIGHT", "TRUNCATE"]).optional(),
|
|
490
|
+
layoutSizingHorizontal: import_zod10.z.enum(["FIXED", "HUG", "FILL"]).optional(),
|
|
491
|
+
layoutSizingVertical: import_zod10.z.enum(["FIXED", "HUG", "FILL"]).optional()
|
|
492
|
+
})).optional().describe("Text properties (font, alignment, sizing)"),
|
|
493
|
+
// Escape hatch
|
|
494
|
+
properties: flexJson(import_zod10.z.record(import_zod10.z.string(), import_zod10.z.unknown())).optional().describe("Arbitrary key-value properties to set directly on the node")
|
|
1333
495
|
});
|
|
1334
|
-
var
|
|
1335
|
-
|
|
1336
|
-
|
|
496
|
+
var tools9 = [
|
|
497
|
+
{
|
|
498
|
+
name: "patch_nodes",
|
|
499
|
+
description: "Patch properties on nodes. Combines geometry (x/y/width/height), appearance (fill, stroke, cornerRadius, opacity, effects, constraints, exportSettings), layout (auto-layout), text (font props), and arbitrary properties in one call. Prefer styleName over hardcoded colors. Batch: pass multiple items.",
|
|
500
|
+
schema: { items: flexJson(import_zod10.z.array(patchNodeItem)).describe("Array of nodes to patch"), depth },
|
|
501
|
+
tier: "edit"
|
|
502
|
+
}
|
|
503
|
+
];
|
|
504
|
+
|
|
505
|
+
// src/tools/defs/text.ts
|
|
506
|
+
var import_zod11 = require("zod");
|
|
507
|
+
var textContentItem = import_zod11.z.object({
|
|
508
|
+
nodeId: import_zod11.z.string().describe("Text node ID"),
|
|
509
|
+
text: import_zod11.z.string().describe("New text content")
|
|
1337
510
|
});
|
|
1338
|
-
var
|
|
511
|
+
var scanTextItem = import_zod11.z.object({
|
|
1339
512
|
nodeId,
|
|
1340
|
-
|
|
1341
|
-
|
|
513
|
+
limit: import_zod11.z.coerce.number().optional().describe("Max text nodes to return (default: 50)"),
|
|
514
|
+
includePath: flexBool(import_zod11.z.boolean()).optional().describe("Include ancestor path strings (default: true). Set false to reduce payload."),
|
|
515
|
+
includeGeometry: flexBool(import_zod11.z.boolean()).optional().describe("Include absoluteX/absoluteY/width/height (default: true). Set false to reduce payload.")
|
|
1342
516
|
});
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
"
|
|
1346
|
-
"
|
|
1347
|
-
{ items: flexJson(
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
"create_variable",
|
|
1358
|
-
"Create variables in a collection. Batch: pass multiple items.",
|
|
1359
|
-
{ items: flexJson(import_zod17.z.array(variableItem)).describe("Array of {collectionId, name, resolvedType}") },
|
|
1360
|
-
async ({ items }) => {
|
|
1361
|
-
try {
|
|
1362
|
-
return mcpJson(await sendCommand("create_variable", { items }));
|
|
1363
|
-
} catch (e) {
|
|
1364
|
-
return mcpError("Error creating variable", e);
|
|
1365
|
-
}
|
|
1366
|
-
}
|
|
1367
|
-
);
|
|
1368
|
-
server2.tool(
|
|
1369
|
-
"set_variable_value",
|
|
1370
|
-
"Set variable values for modes. Batch: pass multiple items.",
|
|
1371
|
-
{ items: flexJson(import_zod17.z.array(setValueItem)).describe("Array of {variableId, modeId, value}") },
|
|
1372
|
-
async ({ items }) => {
|
|
1373
|
-
try {
|
|
1374
|
-
return mcpJson(await sendCommand("set_variable_value", { items }));
|
|
1375
|
-
} catch (e) {
|
|
1376
|
-
return mcpError("Error setting variable value", e);
|
|
1377
|
-
}
|
|
1378
|
-
}
|
|
1379
|
-
);
|
|
1380
|
-
server2.tool(
|
|
1381
|
-
"get_local_variables",
|
|
1382
|
-
"List local variables. Pass includeValues:true to get all mode values in bulk (avoids N separate get_variable_by_id calls).",
|
|
1383
|
-
{
|
|
1384
|
-
type: import_zod17.z.enum(["COLOR", "FLOAT", "STRING", "BOOLEAN"]).optional().describe("Filter by type"),
|
|
1385
|
-
collectionId: import_zod17.z.string().optional().describe("Filter by collection. Omit for all collections."),
|
|
1386
|
-
includeValues: flexBool(import_zod17.z.boolean()).optional().describe("Include valuesByMode for each variable (default: false)")
|
|
1387
|
-
},
|
|
1388
|
-
async (params) => {
|
|
1389
|
-
try {
|
|
1390
|
-
return mcpJson(await sendCommand("get_local_variables", params));
|
|
1391
|
-
} catch (e) {
|
|
1392
|
-
return mcpError("Error getting variables", e);
|
|
1393
|
-
}
|
|
1394
|
-
}
|
|
1395
|
-
);
|
|
1396
|
-
server2.tool(
|
|
1397
|
-
"get_local_variable_collections",
|
|
1398
|
-
"List all local variable collections.",
|
|
1399
|
-
{},
|
|
1400
|
-
async () => {
|
|
1401
|
-
try {
|
|
1402
|
-
return mcpJson(await sendCommand("get_local_variable_collections"));
|
|
1403
|
-
} catch (e) {
|
|
1404
|
-
return mcpError("Error getting variable collections", e);
|
|
1405
|
-
}
|
|
1406
|
-
}
|
|
1407
|
-
);
|
|
1408
|
-
server2.tool(
|
|
1409
|
-
"get_variable_by_id",
|
|
1410
|
-
"Get detailed variable info including all mode values.",
|
|
1411
|
-
{ variableId: import_zod17.z.string().describe("Variable ID") },
|
|
1412
|
-
async ({ variableId }) => {
|
|
1413
|
-
try {
|
|
1414
|
-
return mcpJson(await sendCommand("get_variable_by_id", { variableId }));
|
|
1415
|
-
} catch (e) {
|
|
1416
|
-
return mcpError("Error getting variable", e);
|
|
1417
|
-
}
|
|
1418
|
-
}
|
|
1419
|
-
);
|
|
1420
|
-
server2.tool(
|
|
1421
|
-
"get_variable_collection_by_id",
|
|
1422
|
-
"Get detailed variable collection info including modes and variable IDs.",
|
|
1423
|
-
{ collectionId: import_zod17.z.string().describe("Collection ID") },
|
|
1424
|
-
async ({ collectionId }) => {
|
|
1425
|
-
try {
|
|
1426
|
-
return mcpJson(await sendCommand("get_variable_collection_by_id", { collectionId }));
|
|
1427
|
-
} catch (e) {
|
|
1428
|
-
return mcpError("Error getting variable collection", e);
|
|
1429
|
-
}
|
|
1430
|
-
}
|
|
1431
|
-
);
|
|
1432
|
-
server2.tool(
|
|
1433
|
-
"set_variable_binding",
|
|
1434
|
-
"Bind variables to node properties. Common fields: 'fills/0/color', 'strokes/0/color', 'opacity', 'topLeftRadius', 'itemSpacing'. Batch: pass multiple items.",
|
|
1435
|
-
{ items: flexJson(import_zod17.z.array(bindingItem)).describe("Array of {nodeId, field, variableId}") },
|
|
1436
|
-
async ({ items }) => {
|
|
1437
|
-
try {
|
|
1438
|
-
return mcpJson(await sendCommand("set_variable_binding", { items }));
|
|
1439
|
-
} catch (e) {
|
|
1440
|
-
return mcpError("Error binding variable", e);
|
|
1441
|
-
}
|
|
1442
|
-
}
|
|
1443
|
-
);
|
|
1444
|
-
server2.tool(
|
|
1445
|
-
"add_mode",
|
|
1446
|
-
"Add modes to variable collections. Batch: pass multiple items.",
|
|
1447
|
-
{ items: flexJson(import_zod17.z.array(addModeItem)).describe("Array of {collectionId, name}") },
|
|
1448
|
-
async ({ items }) => {
|
|
1449
|
-
try {
|
|
1450
|
-
return mcpJson(await sendCommand("add_mode", { items }));
|
|
1451
|
-
} catch (e) {
|
|
1452
|
-
return mcpError("Error adding mode", e);
|
|
1453
|
-
}
|
|
1454
|
-
}
|
|
1455
|
-
);
|
|
1456
|
-
server2.tool(
|
|
1457
|
-
"rename_mode",
|
|
1458
|
-
"Rename modes in variable collections. Batch: pass multiple items.",
|
|
1459
|
-
{ items: flexJson(import_zod17.z.array(renameModeItem)).describe("Array of {collectionId, modeId, name}") },
|
|
1460
|
-
async ({ items }) => {
|
|
1461
|
-
try {
|
|
1462
|
-
return mcpJson(await sendCommand("rename_mode", { items }));
|
|
1463
|
-
} catch (e) {
|
|
1464
|
-
return mcpError("Error renaming mode", e);
|
|
1465
|
-
}
|
|
1466
|
-
}
|
|
1467
|
-
);
|
|
1468
|
-
server2.tool(
|
|
1469
|
-
"remove_mode",
|
|
1470
|
-
"Remove modes from variable collections. Batch: pass multiple items.",
|
|
1471
|
-
{ items: flexJson(import_zod17.z.array(removeModeItem)).describe("Array of {collectionId, modeId}") },
|
|
1472
|
-
async ({ items }) => {
|
|
1473
|
-
try {
|
|
1474
|
-
return mcpJson(await sendCommand("remove_mode", { items }));
|
|
1475
|
-
} catch (e) {
|
|
1476
|
-
return mcpError("Error removing mode", e);
|
|
1477
|
-
}
|
|
1478
|
-
}
|
|
1479
|
-
);
|
|
1480
|
-
server2.tool(
|
|
1481
|
-
"set_explicit_variable_mode",
|
|
1482
|
-
"Pin a variable collection mode on a frame (e.g. show Dark mode). Batch: pass multiple items.",
|
|
1483
|
-
{ items: flexJson(import_zod17.z.array(setExplicitModeItem)).describe("Array of {nodeId, collectionId, modeId}") },
|
|
1484
|
-
async ({ items }) => {
|
|
1485
|
-
try {
|
|
1486
|
-
return mcpJson(await sendCommand("set_explicit_variable_mode", { items }));
|
|
1487
|
-
} catch (e) {
|
|
1488
|
-
return mcpError("Error setting variable mode", e);
|
|
1489
|
-
}
|
|
1490
|
-
}
|
|
1491
|
-
);
|
|
1492
|
-
server2.tool(
|
|
1493
|
-
"get_node_variables",
|
|
1494
|
-
"Get variable bindings on a node. Returns which variables are bound to fills, strokes, opacity, corner radius, etc.",
|
|
1495
|
-
{ nodeId },
|
|
1496
|
-
async ({ nodeId: nodeId2 }) => {
|
|
1497
|
-
try {
|
|
1498
|
-
return mcpJson(await sendCommand("get_node_variables", { nodeId: nodeId2 }));
|
|
1499
|
-
} catch (e) {
|
|
1500
|
-
return mcpError("Error getting node variables", e);
|
|
1501
|
-
}
|
|
1502
|
-
}
|
|
1503
|
-
);
|
|
1504
|
-
server2.tool(
|
|
1505
|
-
"delete_variable_collection",
|
|
1506
|
-
"Delete a variable collection and all its variables. This is destructive and cannot be undone.",
|
|
1507
|
-
{ collectionId: import_zod17.z.string().describe("Collection ID to delete") },
|
|
1508
|
-
async ({ collectionId }) => {
|
|
1509
|
-
try {
|
|
1510
|
-
return mcpJson(await sendCommand("delete_variable_collection", { collectionId }));
|
|
1511
|
-
} catch (e) {
|
|
1512
|
-
return mcpError("Error deleting variable collection", e);
|
|
1513
|
-
}
|
|
1514
|
-
}
|
|
1515
|
-
);
|
|
1516
|
-
}
|
|
517
|
+
var tools10 = [
|
|
518
|
+
{
|
|
519
|
+
name: "set_text_content",
|
|
520
|
+
description: "Set text content on text nodes. Batch: pass multiple items to replace text in multiple nodes at once.",
|
|
521
|
+
schema: { items: flexJson(import_zod11.z.array(textContentItem)).describe("Array of {nodeId, text}"), depth },
|
|
522
|
+
tier: "edit"
|
|
523
|
+
},
|
|
524
|
+
{
|
|
525
|
+
name: "scan_text_nodes",
|
|
526
|
+
description: "Scan all text nodes within a node tree. Batch: pass multiple items.",
|
|
527
|
+
schema: { items: flexJson(import_zod11.z.array(scanTextItem)).describe("Array of {nodeId}") },
|
|
528
|
+
tier: "read"
|
|
529
|
+
}
|
|
530
|
+
];
|
|
1517
531
|
|
|
1518
|
-
// src/tools/
|
|
1519
|
-
var
|
|
1520
|
-
|
|
1521
|
-
|
|
532
|
+
// src/tools/defs/fonts.ts
|
|
533
|
+
var import_zod12 = require("zod");
|
|
534
|
+
var tools11 = [
|
|
535
|
+
{
|
|
536
|
+
name: "get_available_fonts",
|
|
537
|
+
description: "Get available fonts in Figma. Optionally filter by query string.",
|
|
538
|
+
schema: { query: import_zod12.z.string().optional().describe("Filter fonts by name (case-insensitive). Omit to list all fonts.") },
|
|
539
|
+
tier: "read"
|
|
540
|
+
}
|
|
541
|
+
];
|
|
542
|
+
|
|
543
|
+
// src/tools/defs/lint.ts
|
|
544
|
+
var import_zod13 = require("zod");
|
|
545
|
+
var lintRules = import_zod13.z.enum([
|
|
1522
546
|
"no-autolayout",
|
|
1523
|
-
// Frames with >1 child and no auto-layout
|
|
1524
547
|
"shape-instead-of-frame",
|
|
1525
|
-
// Shapes used where FRAME should be
|
|
1526
548
|
"hardcoded-color",
|
|
1527
|
-
// Fills/strokes not using styles
|
|
1528
549
|
"no-text-style",
|
|
1529
|
-
// Text nodes without text style
|
|
1530
550
|
"fixed-in-autolayout",
|
|
1531
|
-
// Fixed-size children in auto-layout parents
|
|
1532
551
|
"default-name",
|
|
1533
|
-
// Nodes with default/unnamed names
|
|
1534
552
|
"empty-container",
|
|
1535
|
-
// Frames/components with layout but no children
|
|
1536
553
|
"stale-text-name",
|
|
1537
|
-
// Text nodes where layer name diverges from content
|
|
1538
554
|
"no-text-property",
|
|
1539
|
-
// Text in components not bound to a component property
|
|
1540
|
-
// ── WCAG 2.2 rules ──
|
|
1541
555
|
"wcag-contrast",
|
|
1542
|
-
// 1.4.3 AA text contrast (4.5:1 / 3:1 large)
|
|
1543
556
|
"wcag-contrast-enhanced",
|
|
1544
|
-
// 1.4.6 AAA text contrast (7:1 / 4.5:1 large)
|
|
1545
557
|
"wcag-non-text-contrast",
|
|
1546
|
-
// 1.4.11 AA non-text contrast (3:1)
|
|
1547
558
|
"wcag-target-size",
|
|
1548
|
-
// 2.5.8 AA target size minimum (24x24px)
|
|
1549
559
|
"wcag-text-size",
|
|
1550
|
-
// Best practice: minimum readable text (12px)
|
|
1551
560
|
"wcag-line-height",
|
|
1552
|
-
// 1.4.12 AA text spacing (line height 1.5x)
|
|
1553
561
|
"wcag",
|
|
1554
|
-
// Meta: run all wcag-* rules
|
|
1555
562
|
"all"
|
|
1556
|
-
// Run all rules (including WCAG)
|
|
1557
563
|
]);
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
"lint_node",
|
|
1561
|
-
"Run design linter on a node tree. Returns issues grouped by category with affected node IDs and fix instructions. Lint child nodes individually for large trees.",
|
|
1562
|
-
{
|
|
1563
|
-
nodeId:
|
|
1564
|
-
rules: flexJson(
|
|
1565
|
-
maxDepth:
|
|
1566
|
-
maxFindings:
|
|
564
|
+
var tools12 = [
|
|
565
|
+
{
|
|
566
|
+
name: "lint_node",
|
|
567
|
+
description: "Run design linter on a node tree. Returns issues grouped by category with affected node IDs and fix instructions. Lint child nodes individually for large trees.",
|
|
568
|
+
schema: {
|
|
569
|
+
nodeId: import_zod13.z.string().optional().describe("Node ID to lint. Omit to lint current selection."),
|
|
570
|
+
rules: flexJson(import_zod13.z.array(lintRules)).optional().describe('Rules to run. Default: ["all"]. Options: no-autolayout, shape-instead-of-frame, hardcoded-color, no-text-style, fixed-in-autolayout, default-name, empty-container, stale-text-name, no-text-property, all, wcag-contrast, wcag-contrast-enhanced, wcag-non-text-contrast, wcag-target-size, wcag-text-size, wcag-line-height, wcag'),
|
|
571
|
+
maxDepth: import_zod13.z.coerce.number().optional().describe("Max depth to recurse (default: 10)"),
|
|
572
|
+
maxFindings: import_zod13.z.coerce.number().optional().describe("Stop after N findings (default: 50)")
|
|
1567
573
|
},
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
);
|
|
1576
|
-
server2.tool(
|
|
1577
|
-
"lint_fix_autolayout",
|
|
1578
|
-
"Auto-fix: convert frames with multiple children to auto-layout. Takes node IDs from lint_node 'no-autolayout' results.",
|
|
1579
|
-
{
|
|
1580
|
-
items: flexJson(import_zod18.z.array(import_zod18.z.object({
|
|
574
|
+
tier: "read"
|
|
575
|
+
},
|
|
576
|
+
{
|
|
577
|
+
name: "lint_fix_autolayout",
|
|
578
|
+
description: "Auto-fix: convert frames with multiple children to auto-layout. Takes node IDs from lint_node 'no-autolayout' results.",
|
|
579
|
+
schema: {
|
|
580
|
+
items: flexJson(import_zod13.z.array(import_zod13.z.object({
|
|
1581
581
|
nodeId,
|
|
1582
|
-
layoutMode:
|
|
1583
|
-
itemSpacing:
|
|
582
|
+
layoutMode: import_zod13.z.enum(["HORIZONTAL", "VERTICAL"]).optional().describe("Layout direction (default: auto-detect based on child positions)"),
|
|
583
|
+
itemSpacing: import_zod13.z.coerce.number().optional().describe("Spacing between children (default: 0)")
|
|
1584
584
|
}))).describe("Array of frames to convert to auto-layout"),
|
|
1585
585
|
depth
|
|
1586
586
|
},
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
587
|
+
tier: "edit"
|
|
588
|
+
}
|
|
589
|
+
];
|
|
590
|
+
|
|
591
|
+
// src/tools/defs/styles.ts
|
|
592
|
+
var import_zod15 = require("zod");
|
|
593
|
+
|
|
594
|
+
// src/tools/endpoint.ts
|
|
595
|
+
var import_zod14 = require("zod");
|
|
596
|
+
var DEFAULT_TIERS = {
|
|
597
|
+
get: "read",
|
|
598
|
+
list: "read",
|
|
599
|
+
create: "create",
|
|
600
|
+
update: "edit",
|
|
601
|
+
delete: "edit"
|
|
602
|
+
};
|
|
603
|
+
function endpointSchema(methods, capsOrExtra, extraOrTiers, methodTiers) {
|
|
604
|
+
let caps2;
|
|
605
|
+
let extra;
|
|
606
|
+
if (capsOrExtra && "create" in capsOrExtra && "edit" in capsOrExtra && typeof capsOrExtra.create === "boolean") {
|
|
607
|
+
caps2 = capsOrExtra;
|
|
608
|
+
extra = extraOrTiers;
|
|
609
|
+
} else {
|
|
610
|
+
extra = capsOrExtra;
|
|
611
|
+
}
|
|
612
|
+
let filtered = methods;
|
|
613
|
+
if (caps2) {
|
|
614
|
+
const tiers = { ...DEFAULT_TIERS, ...methodTiers };
|
|
615
|
+
filtered = methods.filter((m) => {
|
|
616
|
+
const tier = tiers[m] ?? "edit";
|
|
617
|
+
if (tier === "read") return true;
|
|
618
|
+
if (tier === "create") return caps2.create;
|
|
619
|
+
if (tier === "edit") return caps2.edit;
|
|
620
|
+
return false;
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
const schema = {
|
|
624
|
+
method: import_zod14.z.enum(filtered)
|
|
625
|
+
};
|
|
626
|
+
if (filtered.includes("get") || filtered.includes("delete")) {
|
|
627
|
+
schema.id = import_zod14.z.string().optional().describe("Resource ID (get, delete)");
|
|
628
|
+
}
|
|
629
|
+
if (filtered.includes("get") || filtered.includes("list")) {
|
|
630
|
+
schema.fields = flexJson(import_zod14.z.array(import_zod14.z.string())).optional().describe('Property whitelist (get/list). Identity fields (id, name, type) always included. Omit for stubs on list, full detail on get. Pass ["*"] for all fields.');
|
|
631
|
+
}
|
|
632
|
+
if (filtered.includes("list")) {
|
|
633
|
+
schema.offset = import_zod14.z.coerce.number().optional().describe("Skip N items for pagination (default 0)");
|
|
634
|
+
schema.limit = import_zod14.z.coerce.number().optional().describe("Max items per page (default 100)");
|
|
635
|
+
}
|
|
636
|
+
return { ...schema, ...extra };
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// src/tools/defs/styles.ts
|
|
640
|
+
var paintStyleItem = import_zod15.z.object({
|
|
641
|
+
name: import_zod15.z.string().describe("Style name"),
|
|
642
|
+
color: flexJson(colorRgba).describe("Color.")
|
|
643
|
+
});
|
|
644
|
+
var textStyleItem = import_zod15.z.object({
|
|
645
|
+
name: import_zod15.z.string().describe("Style name"),
|
|
646
|
+
fontFamily: import_zod15.z.string().describe("Font family"),
|
|
647
|
+
fontStyle: import_zod15.z.string().optional().describe("Font style (default: Regular)"),
|
|
648
|
+
fontSize: import_zod15.z.coerce.number().describe("Font size"),
|
|
649
|
+
lineHeight: flexNum(import_zod15.z.union([
|
|
650
|
+
import_zod15.z.number(),
|
|
651
|
+
import_zod15.z.object({ value: import_zod15.z.coerce.number(), unit: import_zod15.z.enum(["PIXELS", "PERCENT", "AUTO"]) })
|
|
652
|
+
])).optional().describe("Line height \u2014 number (px) or {value, unit}. Default: auto."),
|
|
653
|
+
letterSpacing: flexNum(import_zod15.z.union([
|
|
654
|
+
import_zod15.z.number(),
|
|
655
|
+
import_zod15.z.object({ value: import_zod15.z.coerce.number(), unit: import_zod15.z.enum(["PIXELS", "PERCENT"]) })
|
|
656
|
+
])).optional().describe("Letter spacing \u2014 number (px) or {value, unit}. Default: 0."),
|
|
657
|
+
textCase: import_zod15.z.enum(["ORIGINAL", "UPPER", "LOWER", "TITLE"]).optional(),
|
|
658
|
+
textDecoration: import_zod15.z.enum(["NONE", "UNDERLINE", "STRIKETHROUGH"]).optional()
|
|
659
|
+
});
|
|
660
|
+
var effectStyleItem = import_zod15.z.object({
|
|
661
|
+
name: import_zod15.z.string().describe("Style name"),
|
|
662
|
+
effects: flexJson(import_zod15.z.array(effectEntry)).describe("Array of effects")
|
|
663
|
+
});
|
|
664
|
+
var patchBase = {
|
|
665
|
+
id: import_zod15.z.string().describe("Style ID or name (case-insensitive match)"),
|
|
666
|
+
name: import_zod15.z.string().optional().describe("Rename the style")
|
|
667
|
+
};
|
|
668
|
+
var patchPaintItem = import_zod15.z.object({
|
|
669
|
+
...patchBase,
|
|
670
|
+
color: flexJson(colorRgba).optional().describe("New color.")
|
|
671
|
+
});
|
|
672
|
+
var patchTextItem = import_zod15.z.object({
|
|
673
|
+
...patchBase,
|
|
674
|
+
fontFamily: import_zod15.z.string().optional().describe("Font family"),
|
|
675
|
+
fontStyle: import_zod15.z.string().optional().describe("Font style, e.g. Regular, Bold"),
|
|
676
|
+
fontSize: import_zod15.z.coerce.number().optional().describe("Font size"),
|
|
677
|
+
lineHeight: flexNum(import_zod15.z.union([
|
|
678
|
+
import_zod15.z.number(),
|
|
679
|
+
import_zod15.z.object({ value: import_zod15.z.coerce.number(), unit: import_zod15.z.enum(["PIXELS", "PERCENT", "AUTO"]) })
|
|
680
|
+
])).optional().describe("Line height \u2014 number (px) or {value, unit}"),
|
|
681
|
+
letterSpacing: flexNum(import_zod15.z.union([
|
|
682
|
+
import_zod15.z.number(),
|
|
683
|
+
import_zod15.z.object({ value: import_zod15.z.coerce.number(), unit: import_zod15.z.enum(["PIXELS", "PERCENT"]) })
|
|
684
|
+
])).optional().describe("Letter spacing \u2014 number (px) or {value, unit}"),
|
|
685
|
+
textCase: import_zod15.z.enum(["ORIGINAL", "UPPER", "LOWER", "TITLE"]).optional(),
|
|
686
|
+
textDecoration: import_zod15.z.enum(["NONE", "UNDERLINE", "STRIKETHROUGH"]).optional()
|
|
687
|
+
});
|
|
688
|
+
var patchEffectItem = import_zod15.z.object({
|
|
689
|
+
...patchBase,
|
|
690
|
+
effects: flexJson(import_zod15.z.array(effectEntry)).optional().describe("Array of effects")
|
|
691
|
+
});
|
|
692
|
+
var patchAnyItem = import_zod15.z.object({
|
|
693
|
+
...patchBase,
|
|
694
|
+
color: flexJson(colorRgba).optional(),
|
|
695
|
+
fontFamily: import_zod15.z.string().optional(),
|
|
696
|
+
fontStyle: import_zod15.z.string().optional(),
|
|
697
|
+
fontSize: import_zod15.z.coerce.number().optional(),
|
|
698
|
+
lineHeight: flexNum(import_zod15.z.union([
|
|
699
|
+
import_zod15.z.number(),
|
|
700
|
+
import_zod15.z.object({ value: import_zod15.z.coerce.number(), unit: import_zod15.z.enum(["PIXELS", "PERCENT", "AUTO"]) })
|
|
701
|
+
])).optional(),
|
|
702
|
+
letterSpacing: flexNum(import_zod15.z.union([
|
|
703
|
+
import_zod15.z.number(),
|
|
704
|
+
import_zod15.z.object({ value: import_zod15.z.coerce.number(), unit: import_zod15.z.enum(["PIXELS", "PERCENT"]) })
|
|
705
|
+
])).optional(),
|
|
706
|
+
textCase: import_zod15.z.enum(["ORIGINAL", "UPPER", "LOWER", "TITLE"]).optional(),
|
|
707
|
+
textDecoration: import_zod15.z.enum(["NONE", "UNDERLINE", "STRIKETHROUGH"]).optional(),
|
|
708
|
+
effects: flexJson(import_zod15.z.array(effectEntry)).optional()
|
|
709
|
+
});
|
|
710
|
+
var createSchemas = {
|
|
711
|
+
paint: paintStyleItem,
|
|
712
|
+
text: textStyleItem,
|
|
713
|
+
effect: effectStyleItem
|
|
714
|
+
};
|
|
715
|
+
var updateSchemas = {
|
|
716
|
+
paint: patchPaintItem,
|
|
717
|
+
text: patchTextItem,
|
|
718
|
+
effect: patchEffectItem
|
|
719
|
+
};
|
|
720
|
+
var tools13 = [
|
|
721
|
+
{
|
|
722
|
+
name: "styles",
|
|
723
|
+
description: "CRUD endpoint for local styles (paint, text, effect).\n list \u2192 {type?, fields?, offset?, limit?} \u2192 {totalCount, items: [{id, name, type, ...}]}\n get \u2192 {id, fields?} \u2192 style object (full detail; fields to filter)\n create \u2192 {type, items: [...]} \u2192 {results: [{id}, ...]}\n update \u2192 {type?, items: [{id, ...}]} \u2192 {results: ['ok'|{warning}, ...]}\n delete \u2192 {id} or {items: [{id}, ...]} \u2192 'ok' or {results: ['ok', ...]}",
|
|
724
|
+
schema: (caps2) => endpointSchema(
|
|
725
|
+
["create", "get", "list", "update", "delete"],
|
|
726
|
+
caps2,
|
|
727
|
+
{
|
|
728
|
+
type: import_zod15.z.enum(["paint", "text", "effect"]).optional().describe("Style type. Required for create. Filters list by type. Optional for update (strict per-type validation; omit to auto-detect)."),
|
|
729
|
+
items: flexJson(import_zod15.z.array(import_zod15.z.any())).optional().describe("Create: [{name, color}] (paint), [{name, fontFamily, fontSize, ...}] (text), [{name, effects}] (effect). Update: [{id, ...fields}]. Delete (batch): [{id}, ...]."),
|
|
730
|
+
depth
|
|
731
|
+
}
|
|
732
|
+
),
|
|
733
|
+
tier: "read",
|
|
734
|
+
validate: (params) => {
|
|
735
|
+
if (params.items) {
|
|
736
|
+
const map = params.method === "update" ? updateSchemas : createSchemas;
|
|
737
|
+
const itemSchema = params.type && map[params.type] || patchAnyItem;
|
|
738
|
+
params.items = import_zod15.z.array(itemSchema).parse(params.items);
|
|
1592
739
|
}
|
|
1593
740
|
}
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
741
|
+
}
|
|
742
|
+
];
|
|
743
|
+
|
|
744
|
+
// src/tools/defs/variables.ts
|
|
745
|
+
var import_zod16 = require("zod");
|
|
746
|
+
var collectionCreateItem = import_zod16.z.object({
|
|
747
|
+
name: import_zod16.z.string().describe("Collection name")
|
|
748
|
+
});
|
|
749
|
+
var addModeItem = import_zod16.z.object({
|
|
750
|
+
collectionId: import_zod16.z.string().describe("Collection ID"),
|
|
751
|
+
name: import_zod16.z.string().describe("Mode name")
|
|
752
|
+
});
|
|
753
|
+
var renameModeItem = import_zod16.z.object({
|
|
754
|
+
collectionId: import_zod16.z.string().describe("Collection ID"),
|
|
755
|
+
modeId: import_zod16.z.string().describe("Mode ID"),
|
|
756
|
+
name: import_zod16.z.string().describe("New name")
|
|
757
|
+
});
|
|
758
|
+
var removeModeItem = import_zod16.z.object({
|
|
759
|
+
collectionId: import_zod16.z.string().describe("Collection ID"),
|
|
760
|
+
modeId: import_zod16.z.string().describe("Mode ID")
|
|
761
|
+
});
|
|
762
|
+
var deleteCollectionItem = import_zod16.z.object({
|
|
763
|
+
id: import_zod16.z.string().describe("Collection ID")
|
|
764
|
+
});
|
|
765
|
+
var collectionMethodSchemas = {
|
|
766
|
+
create: collectionCreateItem,
|
|
767
|
+
delete: deleteCollectionItem,
|
|
768
|
+
add_mode: addModeItem,
|
|
769
|
+
rename_mode: renameModeItem,
|
|
770
|
+
remove_mode: removeModeItem
|
|
771
|
+
};
|
|
772
|
+
var variableCreateItem = import_zod16.z.object({
|
|
773
|
+
collectionId: import_zod16.z.string().describe("Variable collection ID"),
|
|
774
|
+
name: import_zod16.z.string().describe("Variable name"),
|
|
775
|
+
resolvedType: import_zod16.z.enum(["COLOR", "FLOAT", "STRING", "BOOLEAN"]).describe("Variable type")
|
|
776
|
+
});
|
|
777
|
+
var variableUpdateItem = import_zod16.z.object({
|
|
778
|
+
id: import_zod16.z.string().describe("Variable ID (full ID, e.g. VariableID:1:6)"),
|
|
779
|
+
modeId: import_zod16.z.string().describe("Mode ID"),
|
|
780
|
+
value: flexJson(import_zod16.z.union([
|
|
781
|
+
import_zod16.z.number(),
|
|
782
|
+
import_zod16.z.boolean(),
|
|
783
|
+
colorRgba
|
|
784
|
+
])).describe('Value: number, boolean, or color (hex "#RRGGBB" or {r,g,b,a?} 0-1)')
|
|
785
|
+
});
|
|
786
|
+
var variableMethodSchemas = {
|
|
787
|
+
create: variableCreateItem,
|
|
788
|
+
update: variableUpdateItem
|
|
789
|
+
};
|
|
790
|
+
var bindingItem = import_zod16.z.object({
|
|
791
|
+
nodeId: import_zod16.z.string().describe("Node ID"),
|
|
792
|
+
field: import_zod16.z.string().describe("Property field (e.g., 'opacity', 'fills/0/color')"),
|
|
793
|
+
variableId: import_zod16.z.string().describe("Variable ID (use full ID from create_variable response, e.g. VariableID:1:6)")
|
|
794
|
+
});
|
|
795
|
+
var setExplicitModeItem = import_zod16.z.object({
|
|
796
|
+
nodeId,
|
|
797
|
+
collectionId: import_zod16.z.string().describe("Variable collection ID"),
|
|
798
|
+
modeId: import_zod16.z.string().describe("Mode ID to pin (e.g. Dark mode)")
|
|
799
|
+
});
|
|
800
|
+
var vcMethodTiers = {
|
|
801
|
+
add_mode: "create",
|
|
802
|
+
rename_mode: "edit",
|
|
803
|
+
remove_mode: "edit"
|
|
804
|
+
};
|
|
805
|
+
var tools14 = [
|
|
806
|
+
{
|
|
807
|
+
name: "variable_collections",
|
|
808
|
+
description: `CRUD endpoint for variable collections + mode management.
|
|
809
|
+
create \u2192 {items: [{name}]} \u2192 {results: [{id, modes, defaultModeId}]}
|
|
810
|
+
get \u2192 {id, fields?} \u2192 collection object
|
|
811
|
+
list \u2192 {fields?, offset?, limit?} \u2192 paginated stubs
|
|
812
|
+
delete \u2192 {id} or {items: [{id}]} \u2192 'ok' or {results: ['ok', ...]}
|
|
813
|
+
add_mode \u2192 {items: [{collectionId, name}]} \u2192 {results: [{modeId, modes}]}
|
|
814
|
+
rename_mode \u2192 {items: [{collectionId, modeId, name}]} \u2192 {results: [{modes}]}
|
|
815
|
+
remove_mode \u2192 {items: [{collectionId, modeId}]} \u2192 {results: [{modes}]}`,
|
|
816
|
+
schema: (caps2) => endpointSchema(
|
|
817
|
+
["create", "get", "list", "delete", "add_mode", "rename_mode", "remove_mode"],
|
|
818
|
+
caps2,
|
|
819
|
+
{
|
|
820
|
+
items: flexJson(import_zod16.z.array(import_zod16.z.any())).optional().describe("create: [{name}]. delete (batch): [{id}]. add_mode: [{collectionId, name}]. rename_mode: [{collectionId, modeId, name}]. remove_mode: [{collectionId, modeId}].")
|
|
821
|
+
},
|
|
822
|
+
vcMethodTiers
|
|
823
|
+
),
|
|
824
|
+
tier: "read",
|
|
825
|
+
validate: (params) => {
|
|
826
|
+
if (params.items) {
|
|
827
|
+
const schema = collectionMethodSchemas[params.method];
|
|
828
|
+
if (schema) params.items = import_zod16.z.array(schema).parse(params.items);
|
|
1610
829
|
}
|
|
1611
830
|
}
|
|
1612
|
-
|
|
1613
|
-
|
|
831
|
+
},
|
|
832
|
+
{
|
|
833
|
+
name: "variables",
|
|
834
|
+
description: `CRUD endpoint for design variables.
|
|
835
|
+
create \u2192 {items: [{collectionId, name, resolvedType}]} \u2192 {results: [{id}]}
|
|
836
|
+
get \u2192 {id, fields?} \u2192 variable object (full detail)
|
|
837
|
+
list \u2192 {type?, collectionId?, fields?, offset?, limit?} \u2192 paginated stubs (fields for detail)
|
|
838
|
+
update \u2192 {items: [{id, modeId, value}]} \u2192 {results: ['ok', ...]}`,
|
|
839
|
+
schema: (caps2) => endpointSchema(
|
|
840
|
+
["create", "get", "list", "update"],
|
|
841
|
+
caps2,
|
|
842
|
+
{
|
|
843
|
+
items: flexJson(import_zod16.z.array(import_zod16.z.any())).optional().describe("create: [{collectionId, name, resolvedType}]. update: [{id, modeId, value}]."),
|
|
844
|
+
type: import_zod16.z.enum(["COLOR", "FLOAT", "STRING", "BOOLEAN"]).optional().describe("Filter list by variable type."),
|
|
845
|
+
collectionId: import_zod16.z.string().optional().describe("Filter list by collection ID.")
|
|
846
|
+
}
|
|
847
|
+
),
|
|
848
|
+
tier: "read",
|
|
849
|
+
validate: (params) => {
|
|
850
|
+
if (params.items) {
|
|
851
|
+
const schema = variableMethodSchemas[params.method];
|
|
852
|
+
if (schema) params.items = import_zod16.z.array(schema).parse(params.items);
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
},
|
|
856
|
+
{
|
|
857
|
+
name: "set_variable_binding",
|
|
858
|
+
description: "Bind variables to node properties. Common fields: 'fills/0/color', 'strokes/0/color', 'opacity', 'topLeftRadius', 'itemSpacing'. Batch: pass multiple items.",
|
|
859
|
+
schema: { items: flexJson(import_zod16.z.array(bindingItem)).describe("Array of {nodeId, field, variableId}") },
|
|
860
|
+
tier: "edit"
|
|
861
|
+
},
|
|
862
|
+
{
|
|
863
|
+
name: "set_explicit_variable_mode",
|
|
864
|
+
description: "Pin a variable collection mode on a frame (e.g. show Dark mode). Batch: pass multiple items.",
|
|
865
|
+
schema: { items: flexJson(import_zod16.z.array(setExplicitModeItem)).describe("Array of {nodeId, collectionId, modeId}") },
|
|
866
|
+
tier: "edit"
|
|
867
|
+
},
|
|
868
|
+
{
|
|
869
|
+
name: "get_node_variables",
|
|
870
|
+
description: "Get variable bindings on a node. Returns which variables are bound to fills, strokes, opacity, corner radius, etc.",
|
|
871
|
+
schema: { nodeId },
|
|
872
|
+
tier: "read"
|
|
873
|
+
}
|
|
874
|
+
];
|
|
1614
875
|
|
|
1615
|
-
// src/tools/
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
876
|
+
// src/tools/defs/components.ts
|
|
877
|
+
var import_zod17 = require("zod");
|
|
878
|
+
var componentItem = import_zod17.z.object({
|
|
879
|
+
name: import_zod17.z.string().describe("Component name"),
|
|
880
|
+
x: xPos,
|
|
881
|
+
y: yPos,
|
|
882
|
+
width: import_zod17.z.coerce.number().optional().describe("Width (default: 100)"),
|
|
883
|
+
height: import_zod17.z.coerce.number().optional().describe("Height (default: 100)"),
|
|
884
|
+
parentId,
|
|
885
|
+
fillColor: flexJson(colorRgba).optional().describe("Fill color. Omit for no fill."),
|
|
886
|
+
fillStyleName: import_zod17.z.string().optional().describe("Apply a fill paint style by name (case-insensitive)."),
|
|
887
|
+
fillVariableId: import_zod17.z.string().optional().describe("Bind a color variable to the fill."),
|
|
888
|
+
strokeColor: flexJson(colorRgba).optional().describe("Stroke color. Omit for no stroke."),
|
|
889
|
+
strokeStyleName: import_zod17.z.string().optional().describe("Apply a stroke paint style by name."),
|
|
890
|
+
strokeVariableId: import_zod17.z.string().optional().describe("Bind a color variable to the stroke."),
|
|
891
|
+
strokeWeight: import_zod17.z.coerce.number().positive().optional().describe("Stroke weight (default: 1)"),
|
|
892
|
+
cornerRadius: import_zod17.z.coerce.number().optional().describe("Corner radius (default: 0)"),
|
|
893
|
+
layoutMode: import_zod17.z.enum(["NONE", "HORIZONTAL", "VERTICAL"]).optional().describe("Layout direction (default: NONE)"),
|
|
894
|
+
layoutWrap: import_zod17.z.enum(["NO_WRAP", "WRAP"]).optional().describe("Wrap behavior (default: NO_WRAP)"),
|
|
895
|
+
paddingTop: import_zod17.z.coerce.number().optional().describe("Top padding (default: 0)"),
|
|
896
|
+
paddingRight: import_zod17.z.coerce.number().optional().describe("Right padding (default: 0)"),
|
|
897
|
+
paddingBottom: import_zod17.z.coerce.number().optional().describe("Bottom padding (default: 0)"),
|
|
898
|
+
paddingLeft: import_zod17.z.coerce.number().optional().describe("Left padding (default: 0)"),
|
|
899
|
+
primaryAxisAlignItems: import_zod17.z.enum(["MIN", "MAX", "CENTER", "SPACE_BETWEEN"]).optional().describe("Primary axis alignment (default: MIN)"),
|
|
900
|
+
counterAxisAlignItems: import_zod17.z.enum(["MIN", "MAX", "CENTER", "BASELINE"]).optional().describe("Counter axis alignment (default: MIN)"),
|
|
901
|
+
layoutSizingHorizontal: import_zod17.z.enum(["FIXED", "HUG", "FILL"]).optional().describe("Horizontal sizing (default: FIXED)"),
|
|
902
|
+
layoutSizingVertical: import_zod17.z.enum(["FIXED", "HUG", "FILL"]).optional().describe("Vertical sizing (default: FIXED)"),
|
|
903
|
+
itemSpacing: import_zod17.z.coerce.number().optional().describe("Spacing between children (default: 0)")
|
|
904
|
+
});
|
|
905
|
+
var fromNodeItem = import_zod17.z.object({
|
|
906
|
+
nodeId
|
|
907
|
+
});
|
|
908
|
+
var combineItem = import_zod17.z.object({
|
|
909
|
+
componentIds: flexJson(import_zod17.z.array(import_zod17.z.string())).describe("Component IDs to combine (min 2)"),
|
|
910
|
+
name: import_zod17.z.string().optional().describe("Name for the component set. Omit to auto-generate.")
|
|
911
|
+
});
|
|
912
|
+
var updateComponentItem = import_zod17.z.object({
|
|
913
|
+
id: import_zod17.z.string().describe("Component node ID"),
|
|
914
|
+
propertyName: import_zod17.z.string().describe("Property name"),
|
|
915
|
+
type: import_zod17.z.enum(["BOOLEAN", "TEXT", "INSTANCE_SWAP", "VARIANT"]).describe("Property type"),
|
|
916
|
+
defaultValue: flexBool(import_zod17.z.union([import_zod17.z.string(), import_zod17.z.boolean()])).describe("Default value (string for TEXT/VARIANT, boolean for BOOLEAN)"),
|
|
917
|
+
preferredValues: flexJson(import_zod17.z.array(import_zod17.z.object({
|
|
918
|
+
type: import_zod17.z.enum(["COMPONENT", "COMPONENT_SET"]),
|
|
919
|
+
key: import_zod17.z.string()
|
|
920
|
+
})).optional()).describe("Preferred values for INSTANCE_SWAP type. Omit for none.")
|
|
921
|
+
});
|
|
922
|
+
var componentCreateSchemas = {
|
|
923
|
+
component: componentItem,
|
|
924
|
+
from_node: fromNodeItem,
|
|
925
|
+
variant_set: combineItem
|
|
926
|
+
};
|
|
927
|
+
var instanceCreateItem = import_zod17.z.object({
|
|
928
|
+
componentId: import_zod17.z.string().describe("Component or component set ID"),
|
|
929
|
+
variantProperties: flexJson(import_zod17.z.record(import_zod17.z.string(), import_zod17.z.string())).optional().describe('Pick variant by properties, e.g. {"Style":"Secondary","Size":"Large"}. Ignored for plain COMPONENT IDs.'),
|
|
930
|
+
x: import_zod17.z.coerce.number().optional().describe("X position. Omit to keep default."),
|
|
931
|
+
y: import_zod17.z.coerce.number().optional().describe("Y position. Omit to keep default."),
|
|
932
|
+
parentId
|
|
933
|
+
});
|
|
934
|
+
var instanceUpdateItem = import_zod17.z.object({
|
|
935
|
+
id: nodeId,
|
|
936
|
+
properties: flexJson(import_zod17.z.record(import_zod17.z.string(), import_zod17.z.union([import_zod17.z.string(), import_zod17.z.boolean()]))).describe('Property key\u2192value map, e.g. {"Label#1:0":"Click Me"}')
|
|
937
|
+
});
|
|
938
|
+
var tools15 = [
|
|
939
|
+
{
|
|
940
|
+
name: "components",
|
|
941
|
+
description: `CRUD endpoint for components.
|
|
942
|
+
create \u2192 {type, items, depth?} \u2192 {results: [{id}, ...]}
|
|
943
|
+
type 'component': create from scratch with layout/style params
|
|
944
|
+
type 'from_node': convert existing nodes to components
|
|
945
|
+
type 'variant_set': combine components into variant sets
|
|
946
|
+
get \u2192 {id, fields?} \u2192 component object (full detail, field-filterable)
|
|
947
|
+
list \u2192 {name?, setsOnly?, fields?, offset?, limit?} \u2192 paginated stubs
|
|
948
|
+
update \u2192 {items: [{id, propertyName, type, defaultValue}]} \u2192 {results: ['ok', ...]}`,
|
|
949
|
+
schema: (caps2) => endpointSchema(
|
|
950
|
+
["create", "get", "list", "update"],
|
|
951
|
+
caps2,
|
|
952
|
+
{
|
|
953
|
+
items: flexJson(import_zod17.z.array(import_zod17.z.any())).optional().describe("create (component): [{name, parentId?, ...layout}]. create (from_node): [{nodeId}]. create (variant_set): [{componentIds, name?}]. update: [{id, propertyName, type, defaultValue}]."),
|
|
954
|
+
type: import_zod17.z.enum(["component", "from_node", "variant_set"]).optional().describe("Create type. Required for create: 'component' (from scratch), 'from_node' (convert existing), 'variant_set' (combine as variants)."),
|
|
955
|
+
depth,
|
|
956
|
+
name: import_zod17.z.string().optional().describe("Filter list by name (case-insensitive substring)."),
|
|
957
|
+
setsOnly: flexBool(import_zod17.z.boolean()).optional().describe("If true, list returns only COMPONENT_SET nodes.")
|
|
958
|
+
}
|
|
959
|
+
),
|
|
960
|
+
tier: "read",
|
|
961
|
+
validate: (params) => {
|
|
962
|
+
if (params.items) {
|
|
963
|
+
if (params.method === "create") {
|
|
964
|
+
const schema = params.type && componentCreateSchemas[params.type];
|
|
965
|
+
if (!schema) throw new Error(`create requires type: component, from_node, or variant_set`);
|
|
966
|
+
params.items = import_zod17.z.array(schema).parse(params.items);
|
|
967
|
+
} else if (params.method === "update") {
|
|
968
|
+
params.items = import_zod17.z.array(updateComponentItem).parse(params.items);
|
|
969
|
+
}
|
|
1626
970
|
}
|
|
1627
971
|
}
|
|
1628
|
-
|
|
1629
|
-
|
|
972
|
+
},
|
|
973
|
+
{
|
|
974
|
+
name: "instances",
|
|
975
|
+
description: `CRUD endpoint for component instances.
|
|
976
|
+
create \u2192 {items: [{componentId, variantProperties?, x?, y?, parentId?}], depth?} \u2192 {results: [{id}]}
|
|
977
|
+
get \u2192 {id} \u2192 {mainComponentId, overrides: [{id, fields}]}
|
|
978
|
+
update \u2192 {items: [{id, properties}]} \u2192 {results: ['ok', ...]}`,
|
|
979
|
+
schema: (caps2) => endpointSchema(
|
|
980
|
+
["create", "get", "update"],
|
|
981
|
+
caps2,
|
|
982
|
+
{
|
|
983
|
+
items: flexJson(import_zod17.z.array(import_zod17.z.any())).optional().describe("create: [{componentId, variantProperties?, x?, y?, parentId?}]. update: [{id, properties}]."),
|
|
984
|
+
depth
|
|
985
|
+
}
|
|
986
|
+
),
|
|
987
|
+
tier: "read",
|
|
988
|
+
validate: (params) => {
|
|
989
|
+
if (params.items) {
|
|
990
|
+
if (params.method === "create") {
|
|
991
|
+
params.items = import_zod17.z.array(instanceCreateItem).parse(params.items);
|
|
992
|
+
} else if (params.method === "update") {
|
|
993
|
+
params.items = import_zod17.z.array(instanceUpdateItem).parse(params.items);
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
];
|
|
1630
999
|
|
|
1631
1000
|
// src/tools/prompts.ts
|
|
1632
1001
|
function registerPrompts(server2) {
|
|
1633
|
-
server2.
|
|
1002
|
+
server2.registerPrompt(
|
|
1634
1003
|
"design_strategy",
|
|
1635
|
-
"Best practices for working with Figma designs",
|
|
1004
|
+
{ description: "Best practices for working with Figma designs" },
|
|
1636
1005
|
() => ({
|
|
1637
1006
|
messages: [{
|
|
1638
1007
|
role: "assistant",
|
|
@@ -1642,7 +1011,7 @@ function registerPrompts(server2) {
|
|
|
1642
1011
|
|
|
1643
1012
|
1. Understand Before Creating:
|
|
1644
1013
|
- Use get_document_info() to see pages and current page
|
|
1645
|
-
- Use
|
|
1014
|
+
- Use styles(method: "list") and get_local_variables() to discover existing design tokens
|
|
1646
1015
|
- Plan layout hierarchy before creating elements
|
|
1647
1016
|
|
|
1648
1017
|
2. Use Design Tokens \u2014 Never Hardcode:
|
|
@@ -1659,7 +1028,7 @@ function registerPrompts(server2) {
|
|
|
1659
1028
|
|
|
1660
1029
|
4. Naming Conventions:
|
|
1661
1030
|
- Use descriptive, semantic names for all elements
|
|
1662
|
-
- Name components with Property=Value pattern (e.g. "Size=Small") before
|
|
1031
|
+
- Name components with Property=Value pattern (e.g. "Size=Small") before components(method: "create", type: "variant_set")
|
|
1663
1032
|
|
|
1664
1033
|
5. Variable Modes:
|
|
1665
1034
|
- Use set_explicit_variable_mode() to pin a frame to a specific mode (e.g. Dark)
|
|
@@ -1671,16 +1040,16 @@ function registerPrompts(server2) {
|
|
|
1671
1040
|
* no-text-style: text without a text style applied
|
|
1672
1041
|
* no-autolayout: frames with children but no auto-layout
|
|
1673
1042
|
* default-name: nodes still named "Frame", "Rectangle", etc.
|
|
1674
|
-
- Use lint_fix_autolayout()
|
|
1043
|
+
- Use lint_fix_autolayout() to auto-fix layout issues
|
|
1675
1044
|
- Lint early and often \u2014 it is cheaper to fix issues during creation than after`
|
|
1676
1045
|
}
|
|
1677
1046
|
}],
|
|
1678
1047
|
description: "Best practices for working with Figma designs"
|
|
1679
1048
|
})
|
|
1680
1049
|
);
|
|
1681
|
-
server2.
|
|
1050
|
+
server2.registerPrompt(
|
|
1682
1051
|
"read_design_strategy",
|
|
1683
|
-
"Best practices for reading Figma designs",
|
|
1052
|
+
{ description: "Best practices for reading Figma designs" },
|
|
1684
1053
|
() => ({
|
|
1685
1054
|
messages: [{
|
|
1686
1055
|
role: "assistant",
|
|
@@ -1689,7 +1058,7 @@ function registerPrompts(server2) {
|
|
|
1689
1058
|
text: `When reading Figma designs, follow these best practices:
|
|
1690
1059
|
|
|
1691
1060
|
1. Start with selection:
|
|
1692
|
-
- First use
|
|
1061
|
+
- First use get_selection() to understand the current selection
|
|
1693
1062
|
- If no selection ask user to select single or multiple nodes
|
|
1694
1063
|
`
|
|
1695
1064
|
}
|
|
@@ -1697,9 +1066,9 @@ function registerPrompts(server2) {
|
|
|
1697
1066
|
description: "Best practices for reading Figma designs"
|
|
1698
1067
|
})
|
|
1699
1068
|
);
|
|
1700
|
-
server2.
|
|
1069
|
+
server2.registerPrompt(
|
|
1701
1070
|
"text_replacement_strategy",
|
|
1702
|
-
"Systematic approach for replacing text in Figma designs",
|
|
1071
|
+
{ description: "Systematic approach for replacing text in Figma designs" },
|
|
1703
1072
|
() => ({
|
|
1704
1073
|
messages: [{
|
|
1705
1074
|
role: "assistant",
|
|
@@ -1824,9 +1193,9 @@ Remember that text is never just text\u2014it's a core design element that must
|
|
|
1824
1193
|
description: "Systematic approach for replacing text in Figma designs"
|
|
1825
1194
|
})
|
|
1826
1195
|
);
|
|
1827
|
-
server2.
|
|
1196
|
+
server2.registerPrompt(
|
|
1828
1197
|
"swap_overrides_instances",
|
|
1829
|
-
"Guide to swap instance overrides between instances",
|
|
1198
|
+
{ description: "Guide to swap instance overrides between instances" },
|
|
1830
1199
|
() => ({
|
|
1831
1200
|
messages: [{
|
|
1832
1201
|
role: "assistant",
|
|
@@ -1844,12 +1213,12 @@ Transfer content overrides from a source instance to target instances.
|
|
|
1844
1213
|
- Use \`search_nodes(types: ["INSTANCE"])\` to find instances on the page
|
|
1845
1214
|
|
|
1846
1215
|
### 2. Extract Source Overrides
|
|
1847
|
-
- \`
|
|
1216
|
+
- \`instances(method: "get", id: "source-instance-id")\`
|
|
1848
1217
|
- Returns mainComponentId and per-child override fields (characters, fills, fontSize, etc.)
|
|
1849
1218
|
|
|
1850
1219
|
### 3. Apply to Targets
|
|
1851
1220
|
- For text overrides: use \`set_text_content\` on matching child node IDs
|
|
1852
|
-
- For style overrides: use \`
|
|
1221
|
+
- For style overrides: use \`patch_nodes\` with fill/stroke/text/effects styleName fields
|
|
1853
1222
|
- Match children by name path \u2014 source and target instances share the same internal structure
|
|
1854
1223
|
|
|
1855
1224
|
### 4. Verify
|
|
@@ -1863,31 +1232,33 @@ Transfer content overrides from a source instance to target instances.
|
|
|
1863
1232
|
}
|
|
1864
1233
|
|
|
1865
1234
|
// src/tools/mcp-registry.ts
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1235
|
+
var allTools = [
|
|
1236
|
+
...tools,
|
|
1237
|
+
...tools2,
|
|
1238
|
+
...tools3,
|
|
1239
|
+
...tools4,
|
|
1240
|
+
...tools5,
|
|
1241
|
+
...tools6,
|
|
1242
|
+
...tools7,
|
|
1243
|
+
...tools8,
|
|
1244
|
+
...tools9,
|
|
1245
|
+
...tools10,
|
|
1246
|
+
...tools11,
|
|
1247
|
+
...tools12,
|
|
1248
|
+
...tools13,
|
|
1249
|
+
...tools14,
|
|
1250
|
+
...tools15
|
|
1251
|
+
];
|
|
1252
|
+
function registerAllTools(server2, sendCommand, caps2) {
|
|
1253
|
+
registerTools(server2, sendCommand, caps2, allTools);
|
|
1884
1254
|
registerPrompts(server2);
|
|
1885
1255
|
}
|
|
1886
1256
|
|
|
1887
1257
|
// src/mcp.ts
|
|
1258
|
+
var import_meta = {};
|
|
1888
1259
|
var VIBMA_VERSION = "0.0.0";
|
|
1889
1260
|
try {
|
|
1890
|
-
const start = typeof __dirname !== "undefined" ? __dirname : process.cwd();
|
|
1261
|
+
const start = typeof import_meta?.url !== "undefined" ? (0, import_path.join)((0, import_url.fileURLToPath)(import_meta.url), "..") : typeof __dirname !== "undefined" ? __dirname : process.cwd();
|
|
1891
1262
|
for (let dir = start; dir !== "/"; dir = (0, import_path.join)(dir, "..")) {
|
|
1892
1263
|
try {
|
|
1893
1264
|
const pkg = JSON.parse((0, import_fs.readFileSync)((0, import_path.join)(dir, "package.json"), "utf8"));
|
|
@@ -1925,6 +1296,10 @@ var portArg = args.find((a) => a.startsWith("--port="));
|
|
|
1925
1296
|
var serverUrl = serverArg ? serverArg.split("=")[1] : "localhost";
|
|
1926
1297
|
if (portArg) activePort = parseInt(portArg.split("=")[1]);
|
|
1927
1298
|
var WS_URL = serverUrl === "localhost" ? `ws://${serverUrl}` : `wss://${serverUrl}`;
|
|
1299
|
+
var caps = {
|
|
1300
|
+
create: args.includes("--create") || args.includes("--edit"),
|
|
1301
|
+
edit: args.includes("--edit")
|
|
1302
|
+
};
|
|
1928
1303
|
function connectToFigma(port = activePort) {
|
|
1929
1304
|
activePort = port;
|
|
1930
1305
|
if (ws && ws.readyState === import_ws.default.OPEN) {
|
|
@@ -2092,10 +1467,12 @@ var server = new import_mcp.McpServer({
|
|
|
2092
1467
|
name: "VibmaMCP",
|
|
2093
1468
|
version: "1.0.0"
|
|
2094
1469
|
});
|
|
2095
|
-
server.
|
|
1470
|
+
server.registerTool(
|
|
2096
1471
|
"join_channel",
|
|
2097
|
-
|
|
2098
|
-
|
|
1472
|
+
{
|
|
1473
|
+
description: "REQUIRED FIRST STEP: Join a channel before using any other tool. The channel name is shown in the Figma plugin UI (defaults to 'vibma' if not customised). After joining, call `ping` to verify the Figma plugin is connected. All subsequent commands are sent through this channel.",
|
|
1474
|
+
inputSchema: { channel: import_zod18.z.string().describe("The channel name displayed in the Figma plugin panel. Defaults to 'vibma' if omitted.").default("vibma") }
|
|
1475
|
+
},
|
|
2099
1476
|
async ({ channel }) => {
|
|
2100
1477
|
try {
|
|
2101
1478
|
await joinChannel(channel);
|
|
@@ -2118,10 +1495,11 @@ See "Version mismatch" in CARRYME.md or DRAGME.md for update steps.`;
|
|
|
2118
1495
|
}
|
|
2119
1496
|
}
|
|
2120
1497
|
);
|
|
2121
|
-
server.
|
|
1498
|
+
server.registerTool(
|
|
2122
1499
|
"channel_info",
|
|
2123
|
-
|
|
2124
|
-
|
|
1500
|
+
{
|
|
1501
|
+
description: "Debug: inspect which clients (MCP, plugin) are connected to each relay channel. Useful for diagnosing connection issues. Does not require an active channel."
|
|
1502
|
+
},
|
|
2125
1503
|
async () => {
|
|
2126
1504
|
try {
|
|
2127
1505
|
const url = serverUrl === "localhost" ? `http://localhost:${activePort}/channels` : `https://${serverUrl}/channels`;
|
|
@@ -2141,11 +1519,13 @@ server.tool(
|
|
|
2141
1519
|
}
|
|
2142
1520
|
}
|
|
2143
1521
|
);
|
|
2144
|
-
server.
|
|
1522
|
+
server.registerTool(
|
|
2145
1523
|
"reset_tunnel",
|
|
2146
|
-
"DESTRUCTIVE: Factory-reset a channel on the relay via HTTP, disconnecting ALL occupants (MCP + Figma plugin). Only use this when the channel is stuck or occupied by another MCP \u2014 do NOT use it just to reconnect yourself (use `join_channel` instead). After reset, ask the user to reopen the Vibma plugin in Figma, then call `join_channel` and `ping`.",
|
|
2147
1524
|
{
|
|
2148
|
-
|
|
1525
|
+
description: "DESTRUCTIVE: Factory-reset a channel on the relay via HTTP, disconnecting ALL occupants (MCP + Figma plugin). Only use this when the channel is stuck or occupied by another MCP \u2014 do NOT use it just to reconnect yourself (use `join_channel` instead). After reset, ask the user to reopen the Vibma plugin in Figma, then call `join_channel` and `ping`.",
|
|
1526
|
+
inputSchema: {
|
|
1527
|
+
channel: import_zod18.z.string().describe("Channel to reset. Defaults to 'vibma'.").default("vibma")
|
|
1528
|
+
}
|
|
2149
1529
|
},
|
|
2150
1530
|
async ({ channel }) => {
|
|
2151
1531
|
const targetChannel = channel || currentChannel || "vibma";
|
|
@@ -2189,7 +1569,7 @@ IMPORTANT: The Figma plugin was also disconnected. Ask the user to reopen the Vi
|
|
|
2189
1569
|
}
|
|
2190
1570
|
}
|
|
2191
1571
|
);
|
|
2192
|
-
registerAllTools(server, sendCommandToFigma);
|
|
1572
|
+
registerAllTools(server, sendCommandToFigma, caps);
|
|
2193
1573
|
function cleanup() {
|
|
2194
1574
|
if (ws && ws.readyState === import_ws.default.OPEN) {
|
|
2195
1575
|
ws.close(1e3, "MCP server shutting down");
|