@biolab/talk-to-figma 0.7.0 → 0.8.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/cli.cjs +31 -2582
- package/dist/cli.js +31 -2562
- package/dist/relay.cjs +4 -158
- package/dist/relay.js +4 -135
- package/dist/talk_to_figma_mcp/server.cjs +28 -2422
- package/dist/talk_to_figma_mcp/server.js +28 -2400
- package/figma-plugin/code.js +489 -3
- package/figma-plugin/figma-plugin.zip +0 -0
- package/package.json +1 -1
- package/dist/cli.cjs.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/relay.cjs.map +0 -1
- package/dist/relay.js.map +0 -1
- package/dist/talk_to_figma_mcp/server.cjs.map +0 -1
- package/dist/talk_to_figma_mcp/server.js.map +0 -1
package/dist/cli.cjs
CHANGED
|
@@ -1,1807 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
var
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
var
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
};
|
|
11
|
-
var __export = (target, all) => {
|
|
12
|
-
for (var name in all)
|
|
13
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
14
|
-
};
|
|
15
|
-
var __copyProps = (to, from, except, desc) => {
|
|
16
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
17
|
-
for (let key of __getOwnPropNames(from))
|
|
18
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
19
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
20
|
-
}
|
|
21
|
-
return to;
|
|
22
|
-
};
|
|
23
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
24
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
25
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
26
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
27
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
28
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
29
|
-
mod
|
|
30
|
-
));
|
|
31
|
-
|
|
32
|
-
// src/relay.ts
|
|
33
|
-
var relay_exports = {};
|
|
34
|
-
__export(relay_exports, {
|
|
35
|
-
startRelay: () => startRelay
|
|
36
|
-
});
|
|
37
|
-
function startRelay() {
|
|
38
|
-
const port = parseInt(process.env.PORT || "3055", 10);
|
|
39
|
-
const httpServer = (0, import_http.createServer)((req, res) => {
|
|
40
|
-
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
41
|
-
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
42
|
-
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
|
|
43
|
-
if (req.method === "OPTIONS") {
|
|
44
|
-
res.writeHead(204);
|
|
45
|
-
res.end();
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
res.writeHead(200);
|
|
49
|
-
res.end("WebSocket server running");
|
|
50
|
-
});
|
|
51
|
-
const wss = new import_ws.WebSocketServer({ server: httpServer });
|
|
52
|
-
wss.on("connection", (ws2) => {
|
|
53
|
-
console.log("New client connected");
|
|
54
|
-
ws2.send(
|
|
55
|
-
JSON.stringify({
|
|
56
|
-
type: "system",
|
|
57
|
-
message: "Please join a channel to start chatting"
|
|
58
|
-
})
|
|
59
|
-
);
|
|
60
|
-
ws2.on("message", (raw) => {
|
|
61
|
-
try {
|
|
62
|
-
const data = JSON.parse(raw.toString());
|
|
63
|
-
console.log(`
|
|
64
|
-
=== Received message from client ===`);
|
|
65
|
-
console.log(`Type: ${data.type}, Channel: ${data.channel || "N/A"}`);
|
|
66
|
-
if (data.message?.command) {
|
|
67
|
-
console.log(`Command: ${data.message.command}, ID: ${data.id}`);
|
|
68
|
-
} else if (data.message?.result) {
|
|
69
|
-
console.log(`Response: ID: ${data.id}, Has Result: ${!!data.message.result}`);
|
|
70
|
-
}
|
|
71
|
-
console.log(`Full message:`, JSON.stringify(data, null, 2));
|
|
72
|
-
if (data.type === "join") {
|
|
73
|
-
const channelName = data.channel;
|
|
74
|
-
if (!channelName || typeof channelName !== "string") {
|
|
75
|
-
ws2.send(JSON.stringify({ type: "error", message: "Channel name is required" }));
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
if (!channels.has(channelName)) {
|
|
79
|
-
channels.set(channelName, /* @__PURE__ */ new Set());
|
|
80
|
-
}
|
|
81
|
-
const channelClients = channels.get(channelName);
|
|
82
|
-
channelClients.add(ws2);
|
|
83
|
-
console.log(`
|
|
84
|
-
\u2713 Client joined channel "${channelName}" (${channelClients.size} total clients)`);
|
|
85
|
-
ws2.send(
|
|
86
|
-
JSON.stringify({
|
|
87
|
-
type: "system",
|
|
88
|
-
message: `Joined channel: ${channelName}`,
|
|
89
|
-
channel: channelName
|
|
90
|
-
})
|
|
91
|
-
);
|
|
92
|
-
ws2.send(
|
|
93
|
-
JSON.stringify({
|
|
94
|
-
type: "system",
|
|
95
|
-
message: {
|
|
96
|
-
id: data.id,
|
|
97
|
-
result: "Connected to channel: " + channelName
|
|
98
|
-
},
|
|
99
|
-
channel: channelName
|
|
100
|
-
})
|
|
101
|
-
);
|
|
102
|
-
channelClients.forEach((client) => {
|
|
103
|
-
if (client !== ws2 && client.readyState === import_ws.WebSocket.OPEN) {
|
|
104
|
-
client.send(
|
|
105
|
-
JSON.stringify({
|
|
106
|
-
type: "system",
|
|
107
|
-
message: "A new user has joined the channel",
|
|
108
|
-
channel: channelName
|
|
109
|
-
})
|
|
110
|
-
);
|
|
111
|
-
}
|
|
112
|
-
});
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
if (data.type === "message") {
|
|
116
|
-
const channelName = data.channel;
|
|
117
|
-
if (!channelName || typeof channelName !== "string") {
|
|
118
|
-
ws2.send(JSON.stringify({ type: "error", message: "Channel name is required" }));
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
const channelClients = channels.get(channelName);
|
|
122
|
-
if (!channelClients || !channelClients.has(ws2)) {
|
|
123
|
-
ws2.send(JSON.stringify({ type: "error", message: "You must join the channel first" }));
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
let broadcastCount = 0;
|
|
127
|
-
channelClients.forEach((client) => {
|
|
128
|
-
if (client !== ws2 && client.readyState === import_ws.WebSocket.OPEN) {
|
|
129
|
-
broadcastCount++;
|
|
130
|
-
const broadcastMessage = {
|
|
131
|
-
type: "broadcast",
|
|
132
|
-
message: data.message,
|
|
133
|
-
sender: "peer",
|
|
134
|
-
channel: channelName
|
|
135
|
-
};
|
|
136
|
-
console.log(`
|
|
137
|
-
=== Broadcasting to peer #${broadcastCount} ===`);
|
|
138
|
-
console.log(JSON.stringify(broadcastMessage, null, 2));
|
|
139
|
-
client.send(JSON.stringify(broadcastMessage));
|
|
140
|
-
}
|
|
141
|
-
});
|
|
142
|
-
if (broadcastCount === 0) {
|
|
143
|
-
console.log(`\u26A0\uFE0F No other clients in channel "${channelName}" to receive message!`);
|
|
144
|
-
} else {
|
|
145
|
-
console.log(`\u2713 Broadcast to ${broadcastCount} peer(s) in channel "${channelName}"`);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
} catch (err) {
|
|
149
|
-
console.error("Error handling message:", err);
|
|
150
|
-
}
|
|
151
|
-
});
|
|
152
|
-
ws2.on("close", () => {
|
|
153
|
-
console.log("Client disconnected");
|
|
154
|
-
channels.forEach((clients) => {
|
|
155
|
-
clients.delete(ws2);
|
|
156
|
-
});
|
|
157
|
-
});
|
|
158
|
-
});
|
|
159
|
-
httpServer.listen(port, () => {
|
|
160
|
-
console.log(`WebSocket relay running on port ${port}`);
|
|
161
|
-
});
|
|
162
|
-
}
|
|
163
|
-
var import_ws, import_http, channels;
|
|
164
|
-
var init_relay = __esm({
|
|
165
|
-
"src/relay.ts"() {
|
|
166
|
-
import_ws = require("ws");
|
|
167
|
-
import_http = require("http");
|
|
168
|
-
channels = /* @__PURE__ */ new Map();
|
|
169
|
-
}
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
// src/talk_to_figma_mcp/server.ts
|
|
173
|
-
var server_exports = {};
|
|
174
|
-
function rgbaToHex(color) {
|
|
175
|
-
if (color.startsWith("#")) {
|
|
176
|
-
return color;
|
|
177
|
-
}
|
|
178
|
-
const r = Math.round(color.r * 255);
|
|
179
|
-
const g = Math.round(color.g * 255);
|
|
180
|
-
const b = Math.round(color.b * 255);
|
|
181
|
-
const a = Math.round(color.a * 255);
|
|
182
|
-
return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}${a === 255 ? "" : a.toString(16).padStart(2, "0")}`;
|
|
183
|
-
}
|
|
184
|
-
function filterFigmaNode(node) {
|
|
185
|
-
if (node.type === "VECTOR") {
|
|
186
|
-
return null;
|
|
187
|
-
}
|
|
188
|
-
const filtered = {
|
|
189
|
-
id: node.id,
|
|
190
|
-
name: node.name,
|
|
191
|
-
type: node.type
|
|
192
|
-
};
|
|
193
|
-
if (node.fills && node.fills.length > 0) {
|
|
194
|
-
filtered.fills = node.fills.map((fill) => {
|
|
195
|
-
const processedFill = { ...fill };
|
|
196
|
-
delete processedFill.imageRef;
|
|
197
|
-
if (processedFill.gradientStops) {
|
|
198
|
-
processedFill.gradientStops = processedFill.gradientStops.map((stop) => {
|
|
199
|
-
const processedStop = { ...stop };
|
|
200
|
-
if (processedStop.color) {
|
|
201
|
-
processedStop.color = rgbaToHex(processedStop.color);
|
|
202
|
-
}
|
|
203
|
-
return processedStop;
|
|
204
|
-
});
|
|
205
|
-
}
|
|
206
|
-
if (processedFill.color) {
|
|
207
|
-
processedFill.color = rgbaToHex(processedFill.color);
|
|
208
|
-
}
|
|
209
|
-
return processedFill;
|
|
210
|
-
});
|
|
211
|
-
}
|
|
212
|
-
if (node.strokes && node.strokes.length > 0) {
|
|
213
|
-
filtered.strokes = node.strokes.map((stroke) => {
|
|
214
|
-
const processedStroke = { ...stroke };
|
|
215
|
-
if (processedStroke.color) {
|
|
216
|
-
processedStroke.color = rgbaToHex(processedStroke.color);
|
|
217
|
-
}
|
|
218
|
-
return processedStroke;
|
|
219
|
-
});
|
|
220
|
-
}
|
|
221
|
-
if (node.cornerRadius !== void 0) {
|
|
222
|
-
filtered.cornerRadius = node.cornerRadius;
|
|
223
|
-
}
|
|
224
|
-
if (node.absoluteBoundingBox) {
|
|
225
|
-
filtered.absoluteBoundingBox = node.absoluteBoundingBox;
|
|
226
|
-
}
|
|
227
|
-
if (node.characters) {
|
|
228
|
-
filtered.characters = node.characters;
|
|
229
|
-
}
|
|
230
|
-
if (node.style) {
|
|
231
|
-
filtered.style = {
|
|
232
|
-
fontFamily: node.style.fontFamily,
|
|
233
|
-
fontStyle: node.style.fontStyle,
|
|
234
|
-
fontWeight: node.style.fontWeight,
|
|
235
|
-
fontSize: node.style.fontSize,
|
|
236
|
-
textAlignHorizontal: node.style.textAlignHorizontal,
|
|
237
|
-
letterSpacing: node.style.letterSpacing,
|
|
238
|
-
lineHeightPx: node.style.lineHeightPx
|
|
239
|
-
};
|
|
240
|
-
}
|
|
241
|
-
if (node.children) {
|
|
242
|
-
filtered.children = node.children.map((child) => filterFigmaNode(child)).filter((child) => child !== null);
|
|
243
|
-
}
|
|
244
|
-
return filtered;
|
|
245
|
-
}
|
|
246
|
-
function connectToFigma(port = 3055) {
|
|
247
|
-
if (ws && ws.readyState === import_ws2.default.OPEN) {
|
|
248
|
-
logger.info("Already connected to Figma");
|
|
249
|
-
return;
|
|
250
|
-
}
|
|
251
|
-
const wsUrl = serverUrl === "localhost" ? `${WS_URL}:${port}` : WS_URL;
|
|
252
|
-
logger.info(`Connecting to Figma socket server at ${wsUrl}...`);
|
|
253
|
-
ws = new import_ws2.default(wsUrl);
|
|
254
|
-
ws.on("open", () => {
|
|
255
|
-
logger.info("Connected to Figma socket server");
|
|
256
|
-
currentChannel = null;
|
|
257
|
-
});
|
|
258
|
-
ws.on("message", (data) => {
|
|
259
|
-
try {
|
|
260
|
-
const json = JSON.parse(data);
|
|
261
|
-
if (json.type === "progress_update") {
|
|
262
|
-
const progressData = json.message.data;
|
|
263
|
-
const requestId = json.id || "";
|
|
264
|
-
if (requestId && pendingRequests.has(requestId)) {
|
|
265
|
-
const request = pendingRequests.get(requestId);
|
|
266
|
-
request.lastActivity = Date.now();
|
|
267
|
-
clearTimeout(request.timeout);
|
|
268
|
-
request.timeout = setTimeout(() => {
|
|
269
|
-
if (pendingRequests.has(requestId)) {
|
|
270
|
-
logger.error(`Request ${requestId} timed out after extended period of inactivity`);
|
|
271
|
-
pendingRequests.delete(requestId);
|
|
272
|
-
request.reject(new Error("Request to Figma timed out"));
|
|
273
|
-
}
|
|
274
|
-
}, 6e4);
|
|
275
|
-
logger.info(`Progress update for ${progressData.commandType}: ${progressData.progress}% - ${progressData.message}`);
|
|
276
|
-
if (progressData.status === "completed" && progressData.progress === 100) {
|
|
277
|
-
logger.info(`Operation ${progressData.commandType} completed, waiting for final result`);
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
return;
|
|
281
|
-
}
|
|
282
|
-
const myResponse = json.message;
|
|
283
|
-
logger.debug(`Received message: ${JSON.stringify(myResponse)}`);
|
|
284
|
-
logger.log("myResponse" + JSON.stringify(myResponse));
|
|
285
|
-
if (myResponse.id && pendingRequests.has(myResponse.id) && myResponse.result) {
|
|
286
|
-
const request = pendingRequests.get(myResponse.id);
|
|
287
|
-
clearTimeout(request.timeout);
|
|
288
|
-
if (myResponse.error) {
|
|
289
|
-
logger.error(`Error from Figma: ${myResponse.error}`);
|
|
290
|
-
request.reject(new Error(myResponse.error));
|
|
291
|
-
} else {
|
|
292
|
-
if (myResponse.result) {
|
|
293
|
-
request.resolve(myResponse.result);
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
pendingRequests.delete(myResponse.id);
|
|
297
|
-
} else {
|
|
298
|
-
logger.info(`Received broadcast message: ${JSON.stringify(myResponse)}`);
|
|
299
|
-
}
|
|
300
|
-
} catch (error) {
|
|
301
|
-
logger.error(`Error parsing message: ${error instanceof Error ? error.message : String(error)}`);
|
|
302
|
-
}
|
|
303
|
-
});
|
|
304
|
-
ws.on("error", (error) => {
|
|
305
|
-
logger.error(`Socket error: ${error}`);
|
|
306
|
-
});
|
|
307
|
-
ws.on("close", () => {
|
|
308
|
-
logger.info("Disconnected from Figma socket server");
|
|
309
|
-
ws = null;
|
|
310
|
-
for (const [id, request] of pendingRequests.entries()) {
|
|
311
|
-
clearTimeout(request.timeout);
|
|
312
|
-
request.reject(new Error("Connection closed"));
|
|
313
|
-
pendingRequests.delete(id);
|
|
314
|
-
}
|
|
315
|
-
logger.info("Attempting to reconnect in 2 seconds...");
|
|
316
|
-
setTimeout(() => connectToFigma(port), 2e3);
|
|
317
|
-
});
|
|
318
|
-
}
|
|
319
|
-
async function joinChannel(channelName) {
|
|
320
|
-
if (!ws || ws.readyState !== import_ws2.default.OPEN) {
|
|
321
|
-
throw new Error("Not connected to Figma");
|
|
322
|
-
}
|
|
323
|
-
try {
|
|
324
|
-
await sendCommandToFigma("join", { channel: channelName });
|
|
325
|
-
currentChannel = channelName;
|
|
326
|
-
logger.info(`Joined channel: ${channelName}`);
|
|
327
|
-
} catch (error) {
|
|
328
|
-
logger.error(`Failed to join channel: ${error instanceof Error ? error.message : String(error)}`);
|
|
329
|
-
throw error;
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
function sendCommandToFigma(command, params = {}, timeoutMs = 3e4) {
|
|
333
|
-
return new Promise((resolve, reject) => {
|
|
334
|
-
if (!ws || ws.readyState !== import_ws2.default.OPEN) {
|
|
335
|
-
connectToFigma();
|
|
336
|
-
reject(new Error("Not connected to Figma. Attempting to connect..."));
|
|
337
|
-
return;
|
|
338
|
-
}
|
|
339
|
-
const requiresChannel = command !== "join";
|
|
340
|
-
if (requiresChannel && !currentChannel) {
|
|
341
|
-
reject(new Error("Must join a channel before sending commands"));
|
|
342
|
-
return;
|
|
343
|
-
}
|
|
344
|
-
const id = (0, import_uuid.v4)();
|
|
345
|
-
const request = {
|
|
346
|
-
id,
|
|
347
|
-
type: command === "join" ? "join" : "message",
|
|
348
|
-
...command === "join" ? { channel: params.channel } : { channel: currentChannel },
|
|
349
|
-
message: {
|
|
350
|
-
id,
|
|
351
|
-
command,
|
|
352
|
-
params: {
|
|
353
|
-
...params,
|
|
354
|
-
commandId: id
|
|
355
|
-
// Include the command ID in params
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
};
|
|
359
|
-
const timeout = setTimeout(() => {
|
|
360
|
-
if (pendingRequests.has(id)) {
|
|
361
|
-
pendingRequests.delete(id);
|
|
362
|
-
logger.error(`Request ${id} to Figma timed out after ${timeoutMs / 1e3} seconds`);
|
|
363
|
-
reject(new Error("Request to Figma timed out"));
|
|
364
|
-
}
|
|
365
|
-
}, timeoutMs);
|
|
366
|
-
pendingRequests.set(id, {
|
|
367
|
-
resolve,
|
|
368
|
-
reject,
|
|
369
|
-
timeout,
|
|
370
|
-
lastActivity: Date.now()
|
|
371
|
-
});
|
|
372
|
-
logger.info(`Sending command to Figma: ${command}`);
|
|
373
|
-
logger.debug(`Request details: ${JSON.stringify(request)}`);
|
|
374
|
-
ws.send(JSON.stringify(request));
|
|
375
|
-
});
|
|
376
|
-
}
|
|
377
|
-
async function main() {
|
|
378
|
-
try {
|
|
379
|
-
connectToFigma();
|
|
380
|
-
} catch (error) {
|
|
381
|
-
logger.warn(`Could not connect to Figma initially: ${error instanceof Error ? error.message : String(error)}`);
|
|
382
|
-
logger.warn("Will try to connect when the first command is sent");
|
|
383
|
-
}
|
|
384
|
-
const transport = new import_stdio.StdioServerTransport();
|
|
385
|
-
await server.connect(transport);
|
|
386
|
-
logger.info("FigmaMCP server running on stdio");
|
|
387
|
-
}
|
|
388
|
-
var import_mcp, import_stdio, import_zod, import_ws2, import_uuid, logger, ws, pendingRequests, currentChannel, server, args, serverArg, serverUrl, WS_URL;
|
|
389
|
-
var init_server = __esm({
|
|
390
|
-
"src/talk_to_figma_mcp/server.ts"() {
|
|
391
|
-
import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
392
|
-
import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
393
|
-
import_zod = require("zod");
|
|
394
|
-
import_ws2 = __toESM(require("ws"), 1);
|
|
395
|
-
import_uuid = require("uuid");
|
|
396
|
-
logger = {
|
|
397
|
-
info: (message) => process.stderr.write(`[INFO] ${message}
|
|
398
|
-
`),
|
|
399
|
-
debug: (message) => process.stderr.write(`[DEBUG] ${message}
|
|
400
|
-
`),
|
|
401
|
-
warn: (message) => process.stderr.write(`[WARN] ${message}
|
|
402
|
-
`),
|
|
403
|
-
error: (message) => process.stderr.write(`[ERROR] ${message}
|
|
404
|
-
`),
|
|
405
|
-
log: (message) => process.stderr.write(`[LOG] ${message}
|
|
406
|
-
`)
|
|
407
|
-
};
|
|
408
|
-
ws = null;
|
|
409
|
-
pendingRequests = /* @__PURE__ */ new Map();
|
|
410
|
-
currentChannel = null;
|
|
411
|
-
server = new import_mcp.McpServer({
|
|
412
|
-
name: "TalkToFigmaMCP",
|
|
413
|
-
version: "1.0.0"
|
|
414
|
-
});
|
|
415
|
-
args = process.argv.slice(2);
|
|
416
|
-
serverArg = args.find((arg) => arg.startsWith("--server="));
|
|
417
|
-
serverUrl = serverArg ? serverArg.split("=")[1] : "localhost";
|
|
418
|
-
WS_URL = serverUrl === "localhost" ? `ws://${serverUrl}` : `wss://${serverUrl}`;
|
|
419
|
-
server.tool(
|
|
420
|
-
"get_document_info",
|
|
421
|
-
"Get detailed information about the current Figma document",
|
|
422
|
-
{},
|
|
423
|
-
async () => {
|
|
424
|
-
try {
|
|
425
|
-
const result = await sendCommandToFigma("get_document_info");
|
|
426
|
-
return {
|
|
427
|
-
content: [
|
|
428
|
-
{
|
|
429
|
-
type: "text",
|
|
430
|
-
text: JSON.stringify(result)
|
|
431
|
-
}
|
|
432
|
-
]
|
|
433
|
-
};
|
|
434
|
-
} catch (error) {
|
|
435
|
-
return {
|
|
436
|
-
content: [
|
|
437
|
-
{
|
|
438
|
-
type: "text",
|
|
439
|
-
text: `Error getting document info: ${error instanceof Error ? error.message : String(error)}`
|
|
440
|
-
}
|
|
441
|
-
]
|
|
442
|
-
};
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
);
|
|
446
|
-
server.tool(
|
|
447
|
-
"get_selection",
|
|
448
|
-
"Get information about the current selection in Figma",
|
|
449
|
-
{},
|
|
450
|
-
async () => {
|
|
451
|
-
try {
|
|
452
|
-
const result = await sendCommandToFigma("get_selection");
|
|
453
|
-
return {
|
|
454
|
-
content: [
|
|
455
|
-
{
|
|
456
|
-
type: "text",
|
|
457
|
-
text: JSON.stringify(result)
|
|
458
|
-
}
|
|
459
|
-
]
|
|
460
|
-
};
|
|
461
|
-
} catch (error) {
|
|
462
|
-
return {
|
|
463
|
-
content: [
|
|
464
|
-
{
|
|
465
|
-
type: "text",
|
|
466
|
-
text: `Error getting selection: ${error instanceof Error ? error.message : String(error)}`
|
|
467
|
-
}
|
|
468
|
-
]
|
|
469
|
-
};
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
);
|
|
473
|
-
server.tool(
|
|
474
|
-
"read_my_design",
|
|
475
|
-
"Get detailed information about the current selection in Figma, including all node details",
|
|
476
|
-
{},
|
|
477
|
-
async () => {
|
|
478
|
-
try {
|
|
479
|
-
const result = await sendCommandToFigma("read_my_design", {});
|
|
480
|
-
return {
|
|
481
|
-
content: [
|
|
482
|
-
{
|
|
483
|
-
type: "text",
|
|
484
|
-
text: JSON.stringify(result)
|
|
485
|
-
}
|
|
486
|
-
]
|
|
487
|
-
};
|
|
488
|
-
} catch (error) {
|
|
489
|
-
return {
|
|
490
|
-
content: [
|
|
491
|
-
{
|
|
492
|
-
type: "text",
|
|
493
|
-
text: `Error getting node info: ${error instanceof Error ? error.message : String(error)}`
|
|
494
|
-
}
|
|
495
|
-
]
|
|
496
|
-
};
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
);
|
|
500
|
-
server.tool(
|
|
501
|
-
"get_node_info",
|
|
502
|
-
"Get detailed information about a specific node in Figma",
|
|
503
|
-
{
|
|
504
|
-
nodeId: import_zod.z.string().describe("The ID of the node to get information about")
|
|
505
|
-
},
|
|
506
|
-
async ({ nodeId }) => {
|
|
507
|
-
try {
|
|
508
|
-
const result = await sendCommandToFigma("get_node_info", { nodeId });
|
|
509
|
-
return {
|
|
510
|
-
content: [
|
|
511
|
-
{
|
|
512
|
-
type: "text",
|
|
513
|
-
text: JSON.stringify(filterFigmaNode(result))
|
|
514
|
-
}
|
|
515
|
-
]
|
|
516
|
-
};
|
|
517
|
-
} catch (error) {
|
|
518
|
-
return {
|
|
519
|
-
content: [
|
|
520
|
-
{
|
|
521
|
-
type: "text",
|
|
522
|
-
text: `Error getting node info: ${error instanceof Error ? error.message : String(error)}`
|
|
523
|
-
}
|
|
524
|
-
]
|
|
525
|
-
};
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
);
|
|
529
|
-
server.tool(
|
|
530
|
-
"get_nodes_info",
|
|
531
|
-
"Get detailed information about multiple nodes in Figma",
|
|
532
|
-
{
|
|
533
|
-
nodeIds: import_zod.z.array(import_zod.z.string()).describe("Array of node IDs to get information about")
|
|
534
|
-
},
|
|
535
|
-
async ({ nodeIds }) => {
|
|
536
|
-
try {
|
|
537
|
-
const results = await Promise.all(
|
|
538
|
-
nodeIds.map(async (nodeId) => {
|
|
539
|
-
const result = await sendCommandToFigma("get_node_info", { nodeId });
|
|
540
|
-
return { nodeId, info: result };
|
|
541
|
-
})
|
|
542
|
-
);
|
|
543
|
-
return {
|
|
544
|
-
content: [
|
|
545
|
-
{
|
|
546
|
-
type: "text",
|
|
547
|
-
text: JSON.stringify(results.map((result) => filterFigmaNode(result.info)))
|
|
548
|
-
}
|
|
549
|
-
]
|
|
550
|
-
};
|
|
551
|
-
} catch (error) {
|
|
552
|
-
return {
|
|
553
|
-
content: [
|
|
554
|
-
{
|
|
555
|
-
type: "text",
|
|
556
|
-
text: `Error getting nodes info: ${error instanceof Error ? error.message : String(error)}`
|
|
557
|
-
}
|
|
558
|
-
]
|
|
559
|
-
};
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
);
|
|
563
|
-
server.tool(
|
|
564
|
-
"create_rectangle",
|
|
565
|
-
"Create a new rectangle in Figma",
|
|
566
|
-
{
|
|
567
|
-
x: import_zod.z.number().describe("X position"),
|
|
568
|
-
y: import_zod.z.number().describe("Y position"),
|
|
569
|
-
width: import_zod.z.number().describe("Width of the rectangle"),
|
|
570
|
-
height: import_zod.z.number().describe("Height of the rectangle"),
|
|
571
|
-
name: import_zod.z.string().optional().describe("Optional name for the rectangle"),
|
|
572
|
-
parentId: import_zod.z.string().optional().describe("Optional parent node ID to append the rectangle to")
|
|
573
|
-
},
|
|
574
|
-
async ({ x, y, width, height, name, parentId }) => {
|
|
575
|
-
try {
|
|
576
|
-
const result = await sendCommandToFigma("create_rectangle", {
|
|
577
|
-
x,
|
|
578
|
-
y,
|
|
579
|
-
width,
|
|
580
|
-
height,
|
|
581
|
-
name: name || "Rectangle",
|
|
582
|
-
parentId
|
|
583
|
-
});
|
|
584
|
-
return {
|
|
585
|
-
content: [
|
|
586
|
-
{
|
|
587
|
-
type: "text",
|
|
588
|
-
text: `Created rectangle "${JSON.stringify(result)}"`
|
|
589
|
-
}
|
|
590
|
-
]
|
|
591
|
-
};
|
|
592
|
-
} catch (error) {
|
|
593
|
-
return {
|
|
594
|
-
content: [
|
|
595
|
-
{
|
|
596
|
-
type: "text",
|
|
597
|
-
text: `Error creating rectangle: ${error instanceof Error ? error.message : String(error)}`
|
|
598
|
-
}
|
|
599
|
-
]
|
|
600
|
-
};
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
);
|
|
604
|
-
server.tool(
|
|
605
|
-
"create_frame",
|
|
606
|
-
"Create a new frame in Figma",
|
|
607
|
-
{
|
|
608
|
-
x: import_zod.z.number().describe("X position"),
|
|
609
|
-
y: import_zod.z.number().describe("Y position"),
|
|
610
|
-
width: import_zod.z.number().describe("Width of the frame"),
|
|
611
|
-
height: import_zod.z.number().describe("Height of the frame"),
|
|
612
|
-
name: import_zod.z.string().optional().describe("Optional name for the frame"),
|
|
613
|
-
parentId: import_zod.z.string().optional().describe("Optional parent node ID to append the frame to"),
|
|
614
|
-
fillColor: import_zod.z.object({
|
|
615
|
-
r: import_zod.z.number().min(0).max(1).describe("Red component (0-1)"),
|
|
616
|
-
g: import_zod.z.number().min(0).max(1).describe("Green component (0-1)"),
|
|
617
|
-
b: import_zod.z.number().min(0).max(1).describe("Blue component (0-1)"),
|
|
618
|
-
a: import_zod.z.number().min(0).max(1).optional().describe("Alpha component (0-1)")
|
|
619
|
-
}).optional().describe("Fill color in RGBA format"),
|
|
620
|
-
strokeColor: import_zod.z.object({
|
|
621
|
-
r: import_zod.z.number().min(0).max(1).describe("Red component (0-1)"),
|
|
622
|
-
g: import_zod.z.number().min(0).max(1).describe("Green component (0-1)"),
|
|
623
|
-
b: import_zod.z.number().min(0).max(1).describe("Blue component (0-1)"),
|
|
624
|
-
a: import_zod.z.number().min(0).max(1).optional().describe("Alpha component (0-1)")
|
|
625
|
-
}).optional().describe("Stroke color in RGBA format"),
|
|
626
|
-
strokeWeight: import_zod.z.number().positive().optional().describe("Stroke weight"),
|
|
627
|
-
layoutMode: import_zod.z.enum(["NONE", "HORIZONTAL", "VERTICAL"]).optional().describe("Auto-layout mode for the frame"),
|
|
628
|
-
layoutWrap: import_zod.z.enum(["NO_WRAP", "WRAP"]).optional().describe("Whether the auto-layout frame wraps its children"),
|
|
629
|
-
paddingTop: import_zod.z.number().optional().describe("Top padding for auto-layout frame"),
|
|
630
|
-
paddingRight: import_zod.z.number().optional().describe("Right padding for auto-layout frame"),
|
|
631
|
-
paddingBottom: import_zod.z.number().optional().describe("Bottom padding for auto-layout frame"),
|
|
632
|
-
paddingLeft: import_zod.z.number().optional().describe("Left padding for auto-layout frame"),
|
|
633
|
-
primaryAxisAlignItems: import_zod.z.enum(["MIN", "MAX", "CENTER", "SPACE_BETWEEN"]).optional().describe("Primary axis alignment for auto-layout frame. Note: When set to SPACE_BETWEEN, itemSpacing will be ignored as children will be evenly spaced."),
|
|
634
|
-
counterAxisAlignItems: import_zod.z.enum(["MIN", "MAX", "CENTER", "BASELINE"]).optional().describe("Counter axis alignment for auto-layout frame"),
|
|
635
|
-
layoutSizingHorizontal: import_zod.z.enum(["FIXED", "HUG", "FILL"]).optional().describe("Horizontal sizing mode for auto-layout frame"),
|
|
636
|
-
layoutSizingVertical: import_zod.z.enum(["FIXED", "HUG", "FILL"]).optional().describe("Vertical sizing mode for auto-layout frame"),
|
|
637
|
-
itemSpacing: import_zod.z.number().optional().describe("Distance between children in auto-layout frame. Note: This value will be ignored if primaryAxisAlignItems is set to SPACE_BETWEEN.")
|
|
638
|
-
},
|
|
639
|
-
async ({
|
|
640
|
-
x,
|
|
641
|
-
y,
|
|
642
|
-
width,
|
|
643
|
-
height,
|
|
644
|
-
name,
|
|
645
|
-
parentId,
|
|
646
|
-
fillColor,
|
|
647
|
-
strokeColor,
|
|
648
|
-
strokeWeight,
|
|
649
|
-
layoutMode,
|
|
650
|
-
layoutWrap,
|
|
651
|
-
paddingTop,
|
|
652
|
-
paddingRight,
|
|
653
|
-
paddingBottom,
|
|
654
|
-
paddingLeft,
|
|
655
|
-
primaryAxisAlignItems,
|
|
656
|
-
counterAxisAlignItems,
|
|
657
|
-
layoutSizingHorizontal,
|
|
658
|
-
layoutSizingVertical,
|
|
659
|
-
itemSpacing
|
|
660
|
-
}) => {
|
|
661
|
-
try {
|
|
662
|
-
const result = await sendCommandToFigma("create_frame", {
|
|
663
|
-
x,
|
|
664
|
-
y,
|
|
665
|
-
width,
|
|
666
|
-
height,
|
|
667
|
-
name: name || "Frame",
|
|
668
|
-
parentId,
|
|
669
|
-
fillColor: fillColor || { r: 1, g: 1, b: 1, a: 1 },
|
|
670
|
-
strokeColor,
|
|
671
|
-
strokeWeight,
|
|
672
|
-
layoutMode,
|
|
673
|
-
layoutWrap,
|
|
674
|
-
paddingTop,
|
|
675
|
-
paddingRight,
|
|
676
|
-
paddingBottom,
|
|
677
|
-
paddingLeft,
|
|
678
|
-
primaryAxisAlignItems,
|
|
679
|
-
counterAxisAlignItems,
|
|
680
|
-
layoutSizingHorizontal,
|
|
681
|
-
layoutSizingVertical,
|
|
682
|
-
itemSpacing
|
|
683
|
-
});
|
|
684
|
-
const typedResult = result;
|
|
685
|
-
return {
|
|
686
|
-
content: [
|
|
687
|
-
{
|
|
688
|
-
type: "text",
|
|
689
|
-
text: `Created frame "${typedResult.name}" with ID: ${typedResult.id}. Use the ID as the parentId to appendChild inside this frame.`
|
|
690
|
-
}
|
|
691
|
-
]
|
|
692
|
-
};
|
|
693
|
-
} catch (error) {
|
|
694
|
-
return {
|
|
695
|
-
content: [
|
|
696
|
-
{
|
|
697
|
-
type: "text",
|
|
698
|
-
text: `Error creating frame: ${error instanceof Error ? error.message : String(error)}`
|
|
699
|
-
}
|
|
700
|
-
]
|
|
701
|
-
};
|
|
702
|
-
}
|
|
703
|
-
}
|
|
704
|
-
);
|
|
705
|
-
server.tool(
|
|
706
|
-
"create_text",
|
|
707
|
-
"Create a new text element in Figma",
|
|
708
|
-
{
|
|
709
|
-
x: import_zod.z.number().describe("X position"),
|
|
710
|
-
y: import_zod.z.number().describe("Y position"),
|
|
711
|
-
text: import_zod.z.string().describe("Text content"),
|
|
712
|
-
fontSize: import_zod.z.number().optional().describe("Font size (default: 14)"),
|
|
713
|
-
fontWeight: import_zod.z.number().optional().describe("Font weight (e.g., 400 for Regular, 700 for Bold)"),
|
|
714
|
-
fontColor: import_zod.z.object({
|
|
715
|
-
r: import_zod.z.number().min(0).max(1).describe("Red component (0-1)"),
|
|
716
|
-
g: import_zod.z.number().min(0).max(1).describe("Green component (0-1)"),
|
|
717
|
-
b: import_zod.z.number().min(0).max(1).describe("Blue component (0-1)"),
|
|
718
|
-
a: import_zod.z.number().min(0).max(1).optional().describe("Alpha component (0-1)")
|
|
719
|
-
}).optional().describe("Font color in RGBA format"),
|
|
720
|
-
name: import_zod.z.string().optional().describe("Semantic layer name for the text node"),
|
|
721
|
-
parentId: import_zod.z.string().optional().describe("Optional parent node ID to append the text to")
|
|
722
|
-
},
|
|
723
|
-
async ({ x, y, text, fontSize, fontWeight, fontColor, name, parentId }) => {
|
|
724
|
-
try {
|
|
725
|
-
const result = await sendCommandToFigma("create_text", {
|
|
726
|
-
x,
|
|
727
|
-
y,
|
|
728
|
-
text,
|
|
729
|
-
fontSize: fontSize || 14,
|
|
730
|
-
fontWeight: fontWeight || 400,
|
|
731
|
-
fontColor: fontColor || { r: 0, g: 0, b: 0, a: 1 },
|
|
732
|
-
name: name || "Text",
|
|
733
|
-
parentId
|
|
734
|
-
});
|
|
735
|
-
const typedResult = result;
|
|
736
|
-
return {
|
|
737
|
-
content: [
|
|
738
|
-
{
|
|
739
|
-
type: "text",
|
|
740
|
-
text: `Created text "${typedResult.name}" with ID: ${typedResult.id}`
|
|
741
|
-
}
|
|
742
|
-
]
|
|
743
|
-
};
|
|
744
|
-
} catch (error) {
|
|
745
|
-
return {
|
|
746
|
-
content: [
|
|
747
|
-
{
|
|
748
|
-
type: "text",
|
|
749
|
-
text: `Error creating text: ${error instanceof Error ? error.message : String(error)}`
|
|
750
|
-
}
|
|
751
|
-
]
|
|
752
|
-
};
|
|
753
|
-
}
|
|
754
|
-
}
|
|
755
|
-
);
|
|
756
|
-
server.tool(
|
|
757
|
-
"set_fill_color",
|
|
758
|
-
"Set the fill color of a node in Figma can be TextNode or FrameNode",
|
|
759
|
-
{
|
|
760
|
-
nodeId: import_zod.z.string().describe("The ID of the node to modify"),
|
|
761
|
-
r: import_zod.z.number().min(0).max(1).describe("Red component (0-1)"),
|
|
762
|
-
g: import_zod.z.number().min(0).max(1).describe("Green component (0-1)"),
|
|
763
|
-
b: import_zod.z.number().min(0).max(1).describe("Blue component (0-1)"),
|
|
764
|
-
a: import_zod.z.number().min(0).max(1).optional().describe("Alpha component (0-1)")
|
|
765
|
-
},
|
|
766
|
-
async ({ nodeId, r, g, b, a }) => {
|
|
767
|
-
try {
|
|
768
|
-
const result = await sendCommandToFigma("set_fill_color", {
|
|
769
|
-
nodeId,
|
|
770
|
-
color: { r, g, b, a: a || 1 }
|
|
771
|
-
});
|
|
772
|
-
const typedResult = result;
|
|
773
|
-
return {
|
|
774
|
-
content: [
|
|
775
|
-
{
|
|
776
|
-
type: "text",
|
|
777
|
-
text: `Set fill color of node "${typedResult.name}" to RGBA(${r}, ${g}, ${b}, ${a || 1})`
|
|
778
|
-
}
|
|
779
|
-
]
|
|
780
|
-
};
|
|
781
|
-
} catch (error) {
|
|
782
|
-
return {
|
|
783
|
-
content: [
|
|
784
|
-
{
|
|
785
|
-
type: "text",
|
|
786
|
-
text: `Error setting fill color: ${error instanceof Error ? error.message : String(error)}`
|
|
787
|
-
}
|
|
788
|
-
]
|
|
789
|
-
};
|
|
790
|
-
}
|
|
791
|
-
}
|
|
792
|
-
);
|
|
793
|
-
server.tool(
|
|
794
|
-
"set_stroke_color",
|
|
795
|
-
"Set the stroke color of a node in Figma",
|
|
796
|
-
{
|
|
797
|
-
nodeId: import_zod.z.string().describe("The ID of the node to modify"),
|
|
798
|
-
r: import_zod.z.number().min(0).max(1).describe("Red component (0-1)"),
|
|
799
|
-
g: import_zod.z.number().min(0).max(1).describe("Green component (0-1)"),
|
|
800
|
-
b: import_zod.z.number().min(0).max(1).describe("Blue component (0-1)"),
|
|
801
|
-
a: import_zod.z.number().min(0).max(1).optional().describe("Alpha component (0-1)"),
|
|
802
|
-
weight: import_zod.z.number().positive().optional().describe("Stroke weight")
|
|
803
|
-
},
|
|
804
|
-
async ({ nodeId, r, g, b, a, weight }) => {
|
|
805
|
-
try {
|
|
806
|
-
const result = await sendCommandToFigma("set_stroke_color", {
|
|
807
|
-
nodeId,
|
|
808
|
-
color: { r, g, b, a: a || 1 },
|
|
809
|
-
weight: weight || 1
|
|
810
|
-
});
|
|
811
|
-
const typedResult = result;
|
|
812
|
-
return {
|
|
813
|
-
content: [
|
|
814
|
-
{
|
|
815
|
-
type: "text",
|
|
816
|
-
text: `Set stroke color of node "${typedResult.name}" to RGBA(${r}, ${g}, ${b}, ${a || 1}) with weight ${weight || 1}`
|
|
817
|
-
}
|
|
818
|
-
]
|
|
819
|
-
};
|
|
820
|
-
} catch (error) {
|
|
821
|
-
return {
|
|
822
|
-
content: [
|
|
823
|
-
{
|
|
824
|
-
type: "text",
|
|
825
|
-
text: `Error setting stroke color: ${error instanceof Error ? error.message : String(error)}`
|
|
826
|
-
}
|
|
827
|
-
]
|
|
828
|
-
};
|
|
829
|
-
}
|
|
830
|
-
}
|
|
831
|
-
);
|
|
832
|
-
server.tool(
|
|
833
|
-
"move_node",
|
|
834
|
-
"Move a node to a new position in Figma",
|
|
835
|
-
{
|
|
836
|
-
nodeId: import_zod.z.string().describe("The ID of the node to move"),
|
|
837
|
-
x: import_zod.z.number().describe("New X position"),
|
|
838
|
-
y: import_zod.z.number().describe("New Y position")
|
|
839
|
-
},
|
|
840
|
-
async ({ nodeId, x, y }) => {
|
|
841
|
-
try {
|
|
842
|
-
const result = await sendCommandToFigma("move_node", { nodeId, x, y });
|
|
843
|
-
const typedResult = result;
|
|
844
|
-
return {
|
|
845
|
-
content: [
|
|
846
|
-
{
|
|
847
|
-
type: "text",
|
|
848
|
-
text: `Moved node "${typedResult.name}" to position (${x}, ${y})`
|
|
849
|
-
}
|
|
850
|
-
]
|
|
851
|
-
};
|
|
852
|
-
} catch (error) {
|
|
853
|
-
return {
|
|
854
|
-
content: [
|
|
855
|
-
{
|
|
856
|
-
type: "text",
|
|
857
|
-
text: `Error moving node: ${error instanceof Error ? error.message : String(error)}`
|
|
858
|
-
}
|
|
859
|
-
]
|
|
860
|
-
};
|
|
861
|
-
}
|
|
862
|
-
}
|
|
863
|
-
);
|
|
864
|
-
server.tool(
|
|
865
|
-
"clone_node",
|
|
866
|
-
"Clone an existing node in Figma",
|
|
867
|
-
{
|
|
868
|
-
nodeId: import_zod.z.string().describe("The ID of the node to clone"),
|
|
869
|
-
x: import_zod.z.number().optional().describe("New X position for the clone"),
|
|
870
|
-
y: import_zod.z.number().optional().describe("New Y position for the clone")
|
|
871
|
-
},
|
|
872
|
-
async ({ nodeId, x, y }) => {
|
|
873
|
-
try {
|
|
874
|
-
const result = await sendCommandToFigma("clone_node", { nodeId, x, y });
|
|
875
|
-
const typedResult = result;
|
|
876
|
-
return {
|
|
877
|
-
content: [
|
|
878
|
-
{
|
|
879
|
-
type: "text",
|
|
880
|
-
text: `Cloned node "${typedResult.name}" with new ID: ${typedResult.id}${x !== void 0 && y !== void 0 ? ` at position (${x}, ${y})` : ""}`
|
|
881
|
-
}
|
|
882
|
-
]
|
|
883
|
-
};
|
|
884
|
-
} catch (error) {
|
|
885
|
-
return {
|
|
886
|
-
content: [
|
|
887
|
-
{
|
|
888
|
-
type: "text",
|
|
889
|
-
text: `Error cloning node: ${error instanceof Error ? error.message : String(error)}`
|
|
890
|
-
}
|
|
891
|
-
]
|
|
892
|
-
};
|
|
893
|
-
}
|
|
894
|
-
}
|
|
895
|
-
);
|
|
896
|
-
server.tool(
|
|
897
|
-
"resize_node",
|
|
898
|
-
"Resize a node in Figma",
|
|
899
|
-
{
|
|
900
|
-
nodeId: import_zod.z.string().describe("The ID of the node to resize"),
|
|
901
|
-
width: import_zod.z.number().positive().describe("New width"),
|
|
902
|
-
height: import_zod.z.number().positive().describe("New height")
|
|
903
|
-
},
|
|
904
|
-
async ({ nodeId, width, height }) => {
|
|
905
|
-
try {
|
|
906
|
-
const result = await sendCommandToFigma("resize_node", {
|
|
907
|
-
nodeId,
|
|
908
|
-
width,
|
|
909
|
-
height
|
|
910
|
-
});
|
|
911
|
-
const typedResult = result;
|
|
912
|
-
return {
|
|
913
|
-
content: [
|
|
914
|
-
{
|
|
915
|
-
type: "text",
|
|
916
|
-
text: `Resized node "${typedResult.name}" to width ${width} and height ${height}`
|
|
917
|
-
}
|
|
918
|
-
]
|
|
919
|
-
};
|
|
920
|
-
} catch (error) {
|
|
921
|
-
return {
|
|
922
|
-
content: [
|
|
923
|
-
{
|
|
924
|
-
type: "text",
|
|
925
|
-
text: `Error resizing node: ${error instanceof Error ? error.message : String(error)}`
|
|
926
|
-
}
|
|
927
|
-
]
|
|
928
|
-
};
|
|
929
|
-
}
|
|
930
|
-
}
|
|
931
|
-
);
|
|
932
|
-
server.tool(
|
|
933
|
-
"delete_node",
|
|
934
|
-
"Delete a node from Figma",
|
|
935
|
-
{
|
|
936
|
-
nodeId: import_zod.z.string().describe("The ID of the node to delete")
|
|
937
|
-
},
|
|
938
|
-
async ({ nodeId }) => {
|
|
939
|
-
try {
|
|
940
|
-
await sendCommandToFigma("delete_node", { nodeId });
|
|
941
|
-
return {
|
|
942
|
-
content: [
|
|
943
|
-
{
|
|
944
|
-
type: "text",
|
|
945
|
-
text: `Deleted node with ID: ${nodeId}`
|
|
946
|
-
}
|
|
947
|
-
]
|
|
948
|
-
};
|
|
949
|
-
} catch (error) {
|
|
950
|
-
return {
|
|
951
|
-
content: [
|
|
952
|
-
{
|
|
953
|
-
type: "text",
|
|
954
|
-
text: `Error deleting node: ${error instanceof Error ? error.message : String(error)}`
|
|
955
|
-
}
|
|
956
|
-
]
|
|
957
|
-
};
|
|
958
|
-
}
|
|
959
|
-
}
|
|
960
|
-
);
|
|
961
|
-
server.tool(
|
|
962
|
-
"delete_multiple_nodes",
|
|
963
|
-
"Delete multiple nodes from Figma at once",
|
|
964
|
-
{
|
|
965
|
-
nodeIds: import_zod.z.array(import_zod.z.string()).describe("Array of node IDs to delete")
|
|
966
|
-
},
|
|
967
|
-
async ({ nodeIds }) => {
|
|
968
|
-
try {
|
|
969
|
-
const result = await sendCommandToFigma("delete_multiple_nodes", { nodeIds });
|
|
970
|
-
return {
|
|
971
|
-
content: [
|
|
972
|
-
{
|
|
973
|
-
type: "text",
|
|
974
|
-
text: JSON.stringify(result)
|
|
975
|
-
}
|
|
976
|
-
]
|
|
977
|
-
};
|
|
978
|
-
} catch (error) {
|
|
979
|
-
return {
|
|
980
|
-
content: [
|
|
981
|
-
{
|
|
982
|
-
type: "text",
|
|
983
|
-
text: `Error deleting multiple nodes: ${error instanceof Error ? error.message : String(error)}`
|
|
984
|
-
}
|
|
985
|
-
]
|
|
986
|
-
};
|
|
987
|
-
}
|
|
988
|
-
}
|
|
989
|
-
);
|
|
990
|
-
server.tool(
|
|
991
|
-
"export_node_as_image",
|
|
992
|
-
"Export a node as an image from Figma",
|
|
993
|
-
{
|
|
994
|
-
nodeId: import_zod.z.string().describe("The ID of the node to export"),
|
|
995
|
-
format: import_zod.z.enum(["PNG", "JPG", "SVG", "PDF"]).optional().describe("Export format"),
|
|
996
|
-
scale: import_zod.z.number().positive().optional().describe("Export scale")
|
|
997
|
-
},
|
|
998
|
-
async ({ nodeId, format, scale }) => {
|
|
999
|
-
try {
|
|
1000
|
-
const result = await sendCommandToFigma("export_node_as_image", {
|
|
1001
|
-
nodeId,
|
|
1002
|
-
format: format || "PNG",
|
|
1003
|
-
scale: scale || 1
|
|
1004
|
-
});
|
|
1005
|
-
const typedResult = result;
|
|
1006
|
-
return {
|
|
1007
|
-
content: [
|
|
1008
|
-
{
|
|
1009
|
-
type: "image",
|
|
1010
|
-
data: typedResult.imageData,
|
|
1011
|
-
mimeType: typedResult.mimeType || "image/png"
|
|
1012
|
-
}
|
|
1013
|
-
]
|
|
1014
|
-
};
|
|
1015
|
-
} catch (error) {
|
|
1016
|
-
return {
|
|
1017
|
-
content: [
|
|
1018
|
-
{
|
|
1019
|
-
type: "text",
|
|
1020
|
-
text: `Error exporting node as image: ${error instanceof Error ? error.message : String(error)}`
|
|
1021
|
-
}
|
|
1022
|
-
]
|
|
1023
|
-
};
|
|
1024
|
-
}
|
|
1025
|
-
}
|
|
1026
|
-
);
|
|
1027
|
-
server.tool(
|
|
1028
|
-
"set_text_content",
|
|
1029
|
-
"Set the text content of an existing text node in Figma",
|
|
1030
|
-
{
|
|
1031
|
-
nodeId: import_zod.z.string().describe("The ID of the text node to modify"),
|
|
1032
|
-
text: import_zod.z.string().describe("New text content")
|
|
1033
|
-
},
|
|
1034
|
-
async ({ nodeId, text }) => {
|
|
1035
|
-
try {
|
|
1036
|
-
const result = await sendCommandToFigma("set_text_content", {
|
|
1037
|
-
nodeId,
|
|
1038
|
-
text
|
|
1039
|
-
});
|
|
1040
|
-
const typedResult = result;
|
|
1041
|
-
return {
|
|
1042
|
-
content: [
|
|
1043
|
-
{
|
|
1044
|
-
type: "text",
|
|
1045
|
-
text: `Updated text content of node "${typedResult.name}" to "${text}"`
|
|
1046
|
-
}
|
|
1047
|
-
]
|
|
1048
|
-
};
|
|
1049
|
-
} catch (error) {
|
|
1050
|
-
return {
|
|
1051
|
-
content: [
|
|
1052
|
-
{
|
|
1053
|
-
type: "text",
|
|
1054
|
-
text: `Error setting text content: ${error instanceof Error ? error.message : String(error)}`
|
|
1055
|
-
}
|
|
1056
|
-
]
|
|
1057
|
-
};
|
|
1058
|
-
}
|
|
1059
|
-
}
|
|
1060
|
-
);
|
|
1061
|
-
server.tool(
|
|
1062
|
-
"set_font_family",
|
|
1063
|
-
"Set the font family of an existing text node in Figma",
|
|
1064
|
-
{
|
|
1065
|
-
nodeId: import_zod.z.string().describe("The ID of the text node to modify"),
|
|
1066
|
-
fontFamily: import_zod.z.string().describe("Font family name (e.g. 'Inter', 'Roboto', 'Arial')"),
|
|
1067
|
-
fontStyle: import_zod.z.string().optional().describe("Font style (e.g. 'Regular', 'Bold', 'Italic'). Defaults to 'Regular'")
|
|
1068
|
-
},
|
|
1069
|
-
async ({ nodeId, fontFamily, fontStyle }) => {
|
|
1070
|
-
try {
|
|
1071
|
-
const result = await sendCommandToFigma("set_font_family", {
|
|
1072
|
-
nodeId,
|
|
1073
|
-
fontFamily,
|
|
1074
|
-
fontStyle: fontStyle || "Regular"
|
|
1075
|
-
});
|
|
1076
|
-
const typedResult = result;
|
|
1077
|
-
return {
|
|
1078
|
-
content: [
|
|
1079
|
-
{
|
|
1080
|
-
type: "text",
|
|
1081
|
-
text: `Updated font family of node "${typedResult.name}" to "${typedResult.fontName.family} ${typedResult.fontName.style}"`
|
|
1082
|
-
}
|
|
1083
|
-
]
|
|
1084
|
-
};
|
|
1085
|
-
} catch (error) {
|
|
1086
|
-
return {
|
|
1087
|
-
content: [
|
|
1088
|
-
{
|
|
1089
|
-
type: "text",
|
|
1090
|
-
text: `Error setting font family: ${error instanceof Error ? error.message : String(error)}`
|
|
1091
|
-
}
|
|
1092
|
-
]
|
|
1093
|
-
};
|
|
1094
|
-
}
|
|
1095
|
-
}
|
|
1096
|
-
);
|
|
1097
|
-
server.tool(
|
|
1098
|
-
"set_font_size",
|
|
1099
|
-
"Set the font size of an existing text node in Figma",
|
|
1100
|
-
{
|
|
1101
|
-
nodeId: import_zod.z.string().describe("The ID of the text node to modify"),
|
|
1102
|
-
fontSize: import_zod.z.number().min(1).describe("Font size in pixels")
|
|
1103
|
-
},
|
|
1104
|
-
async ({ nodeId, fontSize }) => {
|
|
1105
|
-
try {
|
|
1106
|
-
const result = await sendCommandToFigma("set_font_size", {
|
|
1107
|
-
nodeId,
|
|
1108
|
-
fontSize
|
|
1109
|
-
});
|
|
1110
|
-
const typedResult = result;
|
|
1111
|
-
return {
|
|
1112
|
-
content: [
|
|
1113
|
-
{
|
|
1114
|
-
type: "text",
|
|
1115
|
-
text: `Updated font size of node "${typedResult.name}" to ${typedResult.fontSize}px`
|
|
1116
|
-
}
|
|
1117
|
-
]
|
|
1118
|
-
};
|
|
1119
|
-
} catch (error) {
|
|
1120
|
-
return {
|
|
1121
|
-
content: [
|
|
1122
|
-
{
|
|
1123
|
-
type: "text",
|
|
1124
|
-
text: `Error setting font size: ${error instanceof Error ? error.message : String(error)}`
|
|
1125
|
-
}
|
|
1126
|
-
]
|
|
1127
|
-
};
|
|
1128
|
-
}
|
|
1129
|
-
}
|
|
1130
|
-
);
|
|
1131
|
-
server.tool(
|
|
1132
|
-
"set_font_weight",
|
|
1133
|
-
"Set the font weight of an existing text node in Figma. Maps numeric weight to font style (100=Thin, 300=Light, 400=Regular, 500=Medium, 600=Semi Bold, 700=Bold, 800=Extra Bold, 900=Black)",
|
|
1134
|
-
{
|
|
1135
|
-
nodeId: import_zod.z.string().describe("The ID of the text node to modify"),
|
|
1136
|
-
fontWeight: import_zod.z.number().min(100).max(900).describe("Font weight (100-900)")
|
|
1137
|
-
},
|
|
1138
|
-
async ({ nodeId, fontWeight }) => {
|
|
1139
|
-
try {
|
|
1140
|
-
const result = await sendCommandToFigma("set_font_weight", {
|
|
1141
|
-
nodeId,
|
|
1142
|
-
fontWeight
|
|
1143
|
-
});
|
|
1144
|
-
const typedResult = result;
|
|
1145
|
-
return {
|
|
1146
|
-
content: [
|
|
1147
|
-
{
|
|
1148
|
-
type: "text",
|
|
1149
|
-
text: `Updated font weight of node "${typedResult.name}" to ${fontWeight} (${typedResult.fontName.style})`
|
|
1150
|
-
}
|
|
1151
|
-
]
|
|
1152
|
-
};
|
|
1153
|
-
} catch (error) {
|
|
1154
|
-
return {
|
|
1155
|
-
content: [
|
|
1156
|
-
{
|
|
1157
|
-
type: "text",
|
|
1158
|
-
text: `Error setting font weight: ${error instanceof Error ? error.message : String(error)}`
|
|
1159
|
-
}
|
|
1160
|
-
]
|
|
1161
|
-
};
|
|
1162
|
-
}
|
|
1163
|
-
}
|
|
1164
|
-
);
|
|
1165
|
-
server.tool(
|
|
1166
|
-
"get_styles",
|
|
1167
|
-
"Get all styles from the current Figma document",
|
|
1168
|
-
{},
|
|
1169
|
-
async () => {
|
|
1170
|
-
try {
|
|
1171
|
-
const result = await sendCommandToFigma("get_styles");
|
|
1172
|
-
return {
|
|
1173
|
-
content: [
|
|
1174
|
-
{
|
|
1175
|
-
type: "text",
|
|
1176
|
-
text: JSON.stringify(result)
|
|
1177
|
-
}
|
|
1178
|
-
]
|
|
1179
|
-
};
|
|
1180
|
-
} catch (error) {
|
|
1181
|
-
return {
|
|
1182
|
-
content: [
|
|
1183
|
-
{
|
|
1184
|
-
type: "text",
|
|
1185
|
-
text: `Error getting styles: ${error instanceof Error ? error.message : String(error)}`
|
|
1186
|
-
}
|
|
1187
|
-
]
|
|
1188
|
-
};
|
|
1189
|
-
}
|
|
1190
|
-
}
|
|
1191
|
-
);
|
|
1192
|
-
server.tool(
|
|
1193
|
-
"get_local_components",
|
|
1194
|
-
"Get all local components from the Figma document",
|
|
1195
|
-
{},
|
|
1196
|
-
async () => {
|
|
1197
|
-
try {
|
|
1198
|
-
const result = await sendCommandToFigma("get_local_components");
|
|
1199
|
-
return {
|
|
1200
|
-
content: [
|
|
1201
|
-
{
|
|
1202
|
-
type: "text",
|
|
1203
|
-
text: JSON.stringify(result)
|
|
1204
|
-
}
|
|
1205
|
-
]
|
|
1206
|
-
};
|
|
1207
|
-
} catch (error) {
|
|
1208
|
-
return {
|
|
1209
|
-
content: [
|
|
1210
|
-
{
|
|
1211
|
-
type: "text",
|
|
1212
|
-
text: `Error getting local components: ${error instanceof Error ? error.message : String(error)}`
|
|
1213
|
-
}
|
|
1214
|
-
]
|
|
1215
|
-
};
|
|
1216
|
-
}
|
|
1217
|
-
}
|
|
1218
|
-
);
|
|
1219
|
-
server.tool(
|
|
1220
|
-
"get_local_variables",
|
|
1221
|
-
"Get all local variables and variable collections from the Figma document, including values per mode",
|
|
1222
|
-
{},
|
|
1223
|
-
async () => {
|
|
1224
|
-
try {
|
|
1225
|
-
const result = await sendCommandToFigma("get_local_variables");
|
|
1226
|
-
return {
|
|
1227
|
-
content: [{ type: "text", text: JSON.stringify(result) }]
|
|
1228
|
-
};
|
|
1229
|
-
} catch (error) {
|
|
1230
|
-
return {
|
|
1231
|
-
content: [{ type: "text", text: `Error getting local variables: ${error instanceof Error ? error.message : String(error)}` }]
|
|
1232
|
-
};
|
|
1233
|
-
}
|
|
1234
|
-
}
|
|
1235
|
-
);
|
|
1236
|
-
server.tool(
|
|
1237
|
-
"get_local_variable_collections",
|
|
1238
|
-
"Get all local variable collections with their modes",
|
|
1239
|
-
{},
|
|
1240
|
-
async () => {
|
|
1241
|
-
try {
|
|
1242
|
-
const result = await sendCommandToFigma("get_local_variable_collections");
|
|
1243
|
-
return {
|
|
1244
|
-
content: [{ type: "text", text: JSON.stringify(result) }]
|
|
1245
|
-
};
|
|
1246
|
-
} catch (error) {
|
|
1247
|
-
return {
|
|
1248
|
-
content: [{ type: "text", text: `Error getting local variable collections: ${error instanceof Error ? error.message : String(error)}` }]
|
|
1249
|
-
};
|
|
1250
|
-
}
|
|
1251
|
-
}
|
|
1252
|
-
);
|
|
1253
|
-
server.tool(
|
|
1254
|
-
"get_variable_by_id",
|
|
1255
|
-
"Get detailed information about a specific variable by its ID, including values per mode, scopes, and code syntax",
|
|
1256
|
-
{
|
|
1257
|
-
variableId: import_zod.z.string().describe("The ID of the variable to retrieve")
|
|
1258
|
-
},
|
|
1259
|
-
async ({ variableId }) => {
|
|
1260
|
-
try {
|
|
1261
|
-
const result = await sendCommandToFigma("get_variable_by_id", { variableId });
|
|
1262
|
-
return {
|
|
1263
|
-
content: [{ type: "text", text: JSON.stringify(result) }]
|
|
1264
|
-
};
|
|
1265
|
-
} catch (error) {
|
|
1266
|
-
return {
|
|
1267
|
-
content: [{ type: "text", text: `Error getting variable: ${error instanceof Error ? error.message : String(error)}` }]
|
|
1268
|
-
};
|
|
1269
|
-
}
|
|
1270
|
-
}
|
|
1271
|
-
);
|
|
1272
|
-
server.tool(
|
|
1273
|
-
"create_variable_collection",
|
|
1274
|
-
"Create a new variable collection in the Figma document",
|
|
1275
|
-
{
|
|
1276
|
-
name: import_zod.z.string().describe("Name for the new variable collection")
|
|
1277
|
-
},
|
|
1278
|
-
async ({ name }) => {
|
|
1279
|
-
try {
|
|
1280
|
-
const result = await sendCommandToFigma("create_variable_collection", { name });
|
|
1281
|
-
return {
|
|
1282
|
-
content: [{ type: "text", text: JSON.stringify(result) }]
|
|
1283
|
-
};
|
|
1284
|
-
} catch (error) {
|
|
1285
|
-
return {
|
|
1286
|
-
content: [{ type: "text", text: `Error creating variable collection: ${error instanceof Error ? error.message : String(error)}` }]
|
|
1287
|
-
};
|
|
1288
|
-
}
|
|
1289
|
-
}
|
|
1290
|
-
);
|
|
1291
|
-
server.tool(
|
|
1292
|
-
"create_variable",
|
|
1293
|
-
"Create a new variable in a variable collection",
|
|
1294
|
-
{
|
|
1295
|
-
name: import_zod.z.string().describe("Name for the new variable"),
|
|
1296
|
-
collectionId: import_zod.z.string().describe("ID of the variable collection to add the variable to"),
|
|
1297
|
-
resolvedType: import_zod.z.enum(["COLOR", "FLOAT", "STRING", "BOOLEAN"]).describe("The resolved type of the variable")
|
|
1298
|
-
},
|
|
1299
|
-
async ({ name, collectionId, resolvedType }) => {
|
|
1300
|
-
try {
|
|
1301
|
-
const result = await sendCommandToFigma("create_variable", { name, collectionId, resolvedType });
|
|
1302
|
-
return {
|
|
1303
|
-
content: [{ type: "text", text: JSON.stringify(result) }]
|
|
1304
|
-
};
|
|
1305
|
-
} catch (error) {
|
|
1306
|
-
return {
|
|
1307
|
-
content: [{ type: "text", text: `Error creating variable: ${error instanceof Error ? error.message : String(error)}` }]
|
|
1308
|
-
};
|
|
1309
|
-
}
|
|
1310
|
-
}
|
|
1311
|
-
);
|
|
1312
|
-
server.tool(
|
|
1313
|
-
"set_variable_value",
|
|
1314
|
-
"Set the value of a variable for a specific mode. For COLOR type use {r, g, b, a} with 0-1 range. For FLOAT use a number. For STRING use a string. For BOOLEAN use true/false.",
|
|
1315
|
-
{
|
|
1316
|
-
variableId: import_zod.z.string().describe("The ID of the variable"),
|
|
1317
|
-
modeId: import_zod.z.string().describe("The mode ID to set the value for"),
|
|
1318
|
-
value: import_zod.z.any().describe("The value to set - type depends on variable resolvedType (COLOR: {r,g,b,a}, FLOAT: number, STRING: string, BOOLEAN: boolean)")
|
|
1319
|
-
},
|
|
1320
|
-
async ({ variableId, modeId, value }) => {
|
|
1321
|
-
try {
|
|
1322
|
-
const result = await sendCommandToFigma("set_variable_value", { variableId, modeId, value });
|
|
1323
|
-
return {
|
|
1324
|
-
content: [{ type: "text", text: JSON.stringify(result) }]
|
|
1325
|
-
};
|
|
1326
|
-
} catch (error) {
|
|
1327
|
-
return {
|
|
1328
|
-
content: [{ type: "text", text: `Error setting variable value: ${error instanceof Error ? error.message : String(error)}` }]
|
|
1329
|
-
};
|
|
1330
|
-
}
|
|
1331
|
-
}
|
|
1332
|
-
);
|
|
1333
|
-
server.tool(
|
|
1334
|
-
"set_variable_mode_name",
|
|
1335
|
-
"Rename a mode in a variable collection",
|
|
1336
|
-
{
|
|
1337
|
-
collectionId: import_zod.z.string().describe("The ID of the variable collection"),
|
|
1338
|
-
modeId: import_zod.z.string().describe("The ID of the mode to rename"),
|
|
1339
|
-
newName: import_zod.z.string().describe("The new name for the mode")
|
|
1340
|
-
},
|
|
1341
|
-
async ({ collectionId, modeId, newName }) => {
|
|
1342
|
-
try {
|
|
1343
|
-
const result = await sendCommandToFigma("set_variable_mode_name", { collectionId, modeId, newName });
|
|
1344
|
-
return {
|
|
1345
|
-
content: [{ type: "text", text: JSON.stringify(result) }]
|
|
1346
|
-
};
|
|
1347
|
-
} catch (error) {
|
|
1348
|
-
return {
|
|
1349
|
-
content: [{ type: "text", text: `Error renaming variable mode: ${error instanceof Error ? error.message : String(error)}` }]
|
|
1350
|
-
};
|
|
1351
|
-
}
|
|
1352
|
-
}
|
|
1353
|
-
);
|
|
1354
|
-
server.tool(
|
|
1355
|
-
"set_variable_binding",
|
|
1356
|
-
"Bind a variable to a node property. Fields include 'fills/0/color', 'strokes/0/color', 'width', 'height', 'itemSpacing', 'paddingLeft', 'paddingRight', 'paddingTop', 'paddingBottom', etc.",
|
|
1357
|
-
{
|
|
1358
|
-
nodeId: import_zod.z.string().describe("The ID of the node to bind the variable to"),
|
|
1359
|
-
field: import_zod.z.string().describe("The property field to bind (e.g. 'fills/0/color', 'width', 'itemSpacing')"),
|
|
1360
|
-
variableId: import_zod.z.string().describe("The ID of the variable to bind")
|
|
1361
|
-
},
|
|
1362
|
-
async ({ nodeId, field, variableId }) => {
|
|
1363
|
-
try {
|
|
1364
|
-
const result = await sendCommandToFigma("set_variable_binding", { nodeId, field, variableId });
|
|
1365
|
-
return {
|
|
1366
|
-
content: [{ type: "text", text: JSON.stringify(result) }]
|
|
1367
|
-
};
|
|
1368
|
-
} catch (error) {
|
|
1369
|
-
return {
|
|
1370
|
-
content: [{ type: "text", text: `Error setting variable binding: ${error instanceof Error ? error.message : String(error)}` }]
|
|
1371
|
-
};
|
|
1372
|
-
}
|
|
1373
|
-
}
|
|
1374
|
-
);
|
|
1375
|
-
server.tool(
|
|
1376
|
-
"get_team_library_components",
|
|
1377
|
-
"Get all available components from team libraries",
|
|
1378
|
-
{},
|
|
1379
|
-
async () => {
|
|
1380
|
-
try {
|
|
1381
|
-
const result = await sendCommandToFigma("get_team_library_components");
|
|
1382
|
-
return {
|
|
1383
|
-
content: [{ type: "text", text: JSON.stringify(result) }]
|
|
1384
|
-
};
|
|
1385
|
-
} catch (error) {
|
|
1386
|
-
return {
|
|
1387
|
-
content: [{ type: "text", text: `Error getting team library components: ${error instanceof Error ? error.message : String(error)}` }]
|
|
1388
|
-
};
|
|
1389
|
-
}
|
|
1390
|
-
}
|
|
1391
|
-
);
|
|
1392
|
-
server.tool(
|
|
1393
|
-
"get_team_library_variables",
|
|
1394
|
-
"Get all available variables from team libraries",
|
|
1395
|
-
{},
|
|
1396
|
-
async () => {
|
|
1397
|
-
try {
|
|
1398
|
-
const result = await sendCommandToFigma("get_team_library_variables");
|
|
1399
|
-
return {
|
|
1400
|
-
content: [{ type: "text", text: JSON.stringify(result) }]
|
|
1401
|
-
};
|
|
1402
|
-
} catch (error) {
|
|
1403
|
-
return {
|
|
1404
|
-
content: [{ type: "text", text: `Error getting team library variables: ${error instanceof Error ? error.message : String(error)}` }]
|
|
1405
|
-
};
|
|
1406
|
-
}
|
|
1407
|
-
}
|
|
1408
|
-
);
|
|
1409
|
-
server.tool(
|
|
1410
|
-
"import_variable_by_key",
|
|
1411
|
-
"Import a variable from a team library by its key",
|
|
1412
|
-
{
|
|
1413
|
-
key: import_zod.z.string().describe("The key of the variable to import from the team library")
|
|
1414
|
-
},
|
|
1415
|
-
async ({ key }) => {
|
|
1416
|
-
try {
|
|
1417
|
-
const result = await sendCommandToFigma("import_variable_by_key", { key });
|
|
1418
|
-
return {
|
|
1419
|
-
content: [{ type: "text", text: JSON.stringify(result) }]
|
|
1420
|
-
};
|
|
1421
|
-
} catch (error) {
|
|
1422
|
-
return {
|
|
1423
|
-
content: [{ type: "text", text: `Error importing variable: ${error instanceof Error ? error.message : String(error)}` }]
|
|
1424
|
-
};
|
|
1425
|
-
}
|
|
1426
|
-
}
|
|
1427
|
-
);
|
|
1428
|
-
server.tool(
|
|
1429
|
-
"get_annotations",
|
|
1430
|
-
"Get all annotations in the current document or specific node",
|
|
1431
|
-
{
|
|
1432
|
-
nodeId: import_zod.z.string().describe("node ID to get annotations for specific node"),
|
|
1433
|
-
includeCategories: import_zod.z.boolean().optional().default(true).describe("Whether to include category information")
|
|
1434
|
-
},
|
|
1435
|
-
async ({ nodeId, includeCategories }) => {
|
|
1436
|
-
try {
|
|
1437
|
-
const result = await sendCommandToFigma("get_annotations", {
|
|
1438
|
-
nodeId,
|
|
1439
|
-
includeCategories
|
|
1440
|
-
});
|
|
1441
|
-
return {
|
|
1442
|
-
content: [
|
|
1443
|
-
{
|
|
1444
|
-
type: "text",
|
|
1445
|
-
text: JSON.stringify(result)
|
|
1446
|
-
}
|
|
1447
|
-
]
|
|
1448
|
-
};
|
|
1449
|
-
} catch (error) {
|
|
1450
|
-
return {
|
|
1451
|
-
content: [
|
|
1452
|
-
{
|
|
1453
|
-
type: "text",
|
|
1454
|
-
text: `Error getting annotations: ${error instanceof Error ? error.message : String(error)}`
|
|
1455
|
-
}
|
|
1456
|
-
]
|
|
1457
|
-
};
|
|
1458
|
-
}
|
|
1459
|
-
}
|
|
1460
|
-
);
|
|
1461
|
-
server.tool(
|
|
1462
|
-
"set_annotation",
|
|
1463
|
-
"Create or update an annotation",
|
|
1464
|
-
{
|
|
1465
|
-
nodeId: import_zod.z.string().describe("The ID of the node to annotate"),
|
|
1466
|
-
annotationId: import_zod.z.string().optional().describe("The ID of the annotation to update (if updating existing annotation)"),
|
|
1467
|
-
labelMarkdown: import_zod.z.string().describe("The annotation text in markdown format"),
|
|
1468
|
-
categoryId: import_zod.z.string().optional().describe("The ID of the annotation category"),
|
|
1469
|
-
properties: import_zod.z.array(import_zod.z.object({
|
|
1470
|
-
type: import_zod.z.string()
|
|
1471
|
-
})).optional().describe("Additional properties for the annotation")
|
|
1472
|
-
},
|
|
1473
|
-
async ({ nodeId, annotationId, labelMarkdown, categoryId, properties }) => {
|
|
1474
|
-
try {
|
|
1475
|
-
const result = await sendCommandToFigma("set_annotation", {
|
|
1476
|
-
nodeId,
|
|
1477
|
-
annotationId,
|
|
1478
|
-
labelMarkdown,
|
|
1479
|
-
categoryId,
|
|
1480
|
-
properties
|
|
1481
|
-
});
|
|
1482
|
-
return {
|
|
1483
|
-
content: [
|
|
1484
|
-
{
|
|
1485
|
-
type: "text",
|
|
1486
|
-
text: JSON.stringify(result)
|
|
1487
|
-
}
|
|
1488
|
-
]
|
|
1489
|
-
};
|
|
1490
|
-
} catch (error) {
|
|
1491
|
-
return {
|
|
1492
|
-
content: [
|
|
1493
|
-
{
|
|
1494
|
-
type: "text",
|
|
1495
|
-
text: `Error setting annotation: ${error instanceof Error ? error.message : String(error)}`
|
|
1496
|
-
}
|
|
1497
|
-
]
|
|
1498
|
-
};
|
|
1499
|
-
}
|
|
1500
|
-
}
|
|
1501
|
-
);
|
|
1502
|
-
server.tool(
|
|
1503
|
-
"set_multiple_annotations",
|
|
1504
|
-
"Set multiple annotations parallelly in a node",
|
|
1505
|
-
{
|
|
1506
|
-
nodeId: import_zod.z.string().describe("The ID of the node containing the elements to annotate"),
|
|
1507
|
-
annotations: import_zod.z.array(
|
|
1508
|
-
import_zod.z.object({
|
|
1509
|
-
nodeId: import_zod.z.string().describe("The ID of the node to annotate"),
|
|
1510
|
-
labelMarkdown: import_zod.z.string().describe("The annotation text in markdown format"),
|
|
1511
|
-
categoryId: import_zod.z.string().optional().describe("The ID of the annotation category"),
|
|
1512
|
-
annotationId: import_zod.z.string().optional().describe("The ID of the annotation to update (if updating existing annotation)"),
|
|
1513
|
-
properties: import_zod.z.array(import_zod.z.object({
|
|
1514
|
-
type: import_zod.z.string()
|
|
1515
|
-
})).optional().describe("Additional properties for the annotation")
|
|
1516
|
-
})
|
|
1517
|
-
).describe("Array of annotations to apply")
|
|
1518
|
-
},
|
|
1519
|
-
async ({ nodeId, annotations }) => {
|
|
1520
|
-
try {
|
|
1521
|
-
if (!annotations || annotations.length === 0) {
|
|
1522
|
-
return {
|
|
1523
|
-
content: [
|
|
1524
|
-
{
|
|
1525
|
-
type: "text",
|
|
1526
|
-
text: "No annotations provided"
|
|
1527
|
-
}
|
|
1528
|
-
]
|
|
1529
|
-
};
|
|
1530
|
-
}
|
|
1531
|
-
const initialStatus = {
|
|
1532
|
-
type: "text",
|
|
1533
|
-
text: `Starting annotation process for ${annotations.length} nodes. This will be processed in batches of 5...`
|
|
1534
|
-
};
|
|
1535
|
-
let totalProcessed = 0;
|
|
1536
|
-
const totalToProcess = annotations.length;
|
|
1537
|
-
const result = await sendCommandToFigma("set_multiple_annotations", {
|
|
1538
|
-
nodeId,
|
|
1539
|
-
annotations
|
|
1540
|
-
});
|
|
1541
|
-
const typedResult = result;
|
|
1542
|
-
const success = typedResult.annotationsApplied && typedResult.annotationsApplied > 0;
|
|
1543
|
-
const progressText = `
|
|
2
|
+
var V=Object.create;var E=Object.defineProperty;var q=Object.getOwnPropertyDescriptor;var X=Object.getOwnPropertyNames;var Y=Object.getPrototypeOf,K=Object.prototype.hasOwnProperty;var A=(e,t)=>()=>(e&&(t=e(e=0)),t);var Q=(e,t)=>{for(var o in t)E(e,o,{get:t[o],enumerable:!0})},Z=(e,t,o,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let a of X(t))!K.call(e,a)&&a!==o&&E(e,a,{get:()=>t[a],enumerable:!(n=q(t,a))||n.enumerable});return e};var ee=(e,t,o)=>(o=e!=null?V(Y(e)):{},Z(t||!e||!e.__esModule?E(o,"default",{value:e,enumerable:!0}):o,e));var k={};Q(k,{startRelay:()=>te});function te(){let e=parseInt(process.env.PORT||"3055",10),t=(0,R.createServer)((n,a)=>{if(a.setHeader("Access-Control-Allow-Origin","*"),a.setHeader("Access-Control-Allow-Methods","GET, POST, OPTIONS"),a.setHeader("Access-Control-Allow-Headers","Content-Type, Authorization"),n.method==="OPTIONS"){a.writeHead(204),a.end();return}a.writeHead(200),a.end("WebSocket server running")});new x.WebSocketServer({server:t}).on("connection",n=>{console.log("New client connected"),n.send(JSON.stringify({type:"system",message:"Please join a channel to start chatting"})),n.on("message",a=>{try{let s=JSON.parse(a.toString());if(console.log(`
|
|
3
|
+
=== Received message from client ===`),console.log(`Type: ${s.type}, Channel: ${s.channel||"N/A"}`),s.message?.command?console.log(`Command: ${s.message.command}, ID: ${s.id}`):s.message?.result&&console.log(`Response: ID: ${s.id}, Has Result: ${!!s.message.result}`),console.log("Full message:",JSON.stringify(s,null,2)),s.type==="join"){let i=s.channel;if(!i||typeof i!="string"){n.send(JSON.stringify({type:"error",message:"Channel name is required"}));return}_.has(i)||_.set(i,new Set);let d=_.get(i);d.add(n),console.log(`
|
|
4
|
+
\u2713 Client joined channel "${i}" (${d.size} total clients)`),n.send(JSON.stringify({type:"system",message:`Joined channel: ${i}`,channel:i})),n.send(JSON.stringify({type:"system",message:{id:s.id,result:"Connected to channel: "+i},channel:i})),d.forEach(m=>{m!==n&&m.readyState===x.WebSocket.OPEN&&m.send(JSON.stringify({type:"system",message:"A new user has joined the channel",channel:i}))});return}if(s.type==="message"){let i=s.channel;if(!i||typeof i!="string"){n.send(JSON.stringify({type:"error",message:"Channel name is required"}));return}let d=_.get(i);if(!d||!d.has(n)){n.send(JSON.stringify({type:"error",message:"You must join the channel first"}));return}let m=0;d.forEach(f=>{if(f!==n&&f.readyState===x.WebSocket.OPEN){m++;let h={type:"broadcast",message:s.message,sender:"peer",channel:i};console.log(`
|
|
5
|
+
=== Broadcasting to peer #${m} ===`),console.log(JSON.stringify(h,null,2)),f.send(JSON.stringify(h))}}),console.log(m===0?`\u26A0\uFE0F No other clients in channel "${i}" to receive message!`:`\u2713 Broadcast to ${m} peer(s) in channel "${i}"`)}}catch(s){console.error("Error handling message:",s)}}),n.on("close",()=>{console.log("Client disconnected"),_.forEach(a=>{a.delete(n)})})}),t.listen(e,()=>{console.log(`WebSocket relay running on port ${e}`)})}var x,R,_,F=A(()=>{x=require("ws"),R=require("http"),_=new Map});var ae={};function N(e){if(e.startsWith("#"))return e;let t=Math.round(e.r*255),o=Math.round(e.g*255),n=Math.round(e.b*255),a=Math.round(e.a*255);return`#${t.toString(16).padStart(2,"0")}${o.toString(16).padStart(2,"0")}${n.toString(16).padStart(2,"0")}${a===255?"":a.toString(16).padStart(2,"0")}`}function $(e){if(e.type==="VECTOR")return null;let t={id:e.id,name:e.name,type:e.type};return e.fills&&e.fills.length>0&&(t.fills=e.fills.map(o=>{let n={...o};return delete n.imageRef,n.gradientStops&&(n.gradientStops=n.gradientStops.map(a=>{let s={...a};return s.color&&(s.color=N(s.color)),s})),n.color&&(n.color=N(n.color)),n})),e.strokes&&e.strokes.length>0&&(t.strokes=e.strokes.map(o=>{let n={...o};return n.color&&(n.color=N(n.color)),n})),e.cornerRadius!==void 0&&(t.cornerRadius=e.cornerRadius),e.absoluteBoundingBox&&(t.absoluteBoundingBox=e.absoluteBoundingBox),e.characters&&(t.characters=e.characters),e.style&&(t.style={fontFamily:e.style.fontFamily,fontStyle:e.style.fontStyle,fontWeight:e.style.fontWeight,fontSize:e.style.fontSize,textAlignHorizontal:e.style.textAlignHorizontal,letterSpacing:e.style.letterSpacing,lineHeightPx:e.style.lineHeightPx}),e.children&&(t.children=e.children.map(o=>$(o)).filter(o=>o!==null)),t}function T(e=3055){if(p&&p.readyState===I.default.OPEN){g.info("Already connected to Figma");return}let t=v==="localhost"?`${D}:${e}`:D;g.info(`Connecting to Figma socket server at ${t}...`),p=new I.default(t),p.on("open",()=>{g.info("Connected to Figma socket server"),w=null}),p.on("message",o=>{try{let n=JSON.parse(o);if(n.type==="progress_update"){let s=n.message.data,i=n.id||"";if(i&&u.has(i)){let d=u.get(i);d.lastActivity=Date.now(),clearTimeout(d.timeout),d.timeout=setTimeout(()=>{u.has(i)&&(g.error(`Request ${i} timed out after extended period of inactivity`),u.delete(i),d.reject(new Error("Request to Figma timed out")))},6e4),g.info(`Progress update for ${s.commandType}: ${s.progress}% - ${s.message}`),s.status==="completed"&&s.progress===100&&g.info(`Operation ${s.commandType} completed, waiting for final result`)}return}let a=n.message;if(g.debug(`Received message: ${JSON.stringify(a)}`),g.log("myResponse"+JSON.stringify(a)),a.id&&u.has(a.id)&&a.result){let s=u.get(a.id);clearTimeout(s.timeout),a.error?(g.error(`Error from Figma: ${a.error}`),s.reject(new Error(a.error))):a.result&&s.resolve(a.result),u.delete(a.id)}else g.info(`Received broadcast message: ${JSON.stringify(a)}`)}catch(n){g.error(`Error parsing message: ${n instanceof Error?n.message:String(n)}`)}}),p.on("error",o=>{g.error(`Socket error: ${o}`)}),p.on("close",()=>{g.info("Disconnected from Figma socket server"),p=null;for(let[o,n]of u.entries())clearTimeout(n.timeout),n.reject(new Error("Connection closed")),u.delete(o);g.info("Attempting to reconnect in 2 seconds..."),setTimeout(()=>T(e),2e3)})}async function re(e){if(!p||p.readyState!==I.default.OPEN)throw new Error("Not connected to Figma");try{await l("join",{channel:e}),w=e,g.info(`Joined channel: ${e}`)}catch(t){throw g.error(`Failed to join channel: ${t instanceof Error?t.message:String(t)}`),t}}function l(e,t={},o=3e4){return new Promise((n,a)=>{if(!p||p.readyState!==I.default.OPEN){T(),a(new Error("Not connected to Figma. Attempting to connect..."));return}if(e!=="join"&&!w){a(new Error("Must join a channel before sending commands"));return}let i=(0,M.v4)(),d={id:i,type:e==="join"?"join":"message",...e==="join"?{channel:t.channel}:{channel:w},message:{id:i,command:e,params:{...t,commandId:i}}},m=setTimeout(()=>{u.has(i)&&(u.delete(i),g.error(`Request ${i} to Figma timed out after ${o/1e3} seconds`),a(new Error("Request to Figma timed out")))},o);u.set(i,{resolve:n,reject:a,timeout:m,lastActivity:Date.now()}),g.info(`Sending command to Figma: ${e}`),g.debug(`Request details: ${JSON.stringify(d)}`),p.send(JSON.stringify(d))})}async function oe(){try{T()}catch(t){g.warn(`Could not connect to Figma initially: ${t instanceof Error?t.message:String(t)}`),g.warn("Will try to connect when the first command is sent")}let e=new L.StdioServerTransport;await c.connect(e),g.info("FigmaMCP server running on stdio")}var P,L,r,I,M,g,p,u,w,c,ne,O,v,D,G=A(()=>{P=require("@modelcontextprotocol/sdk/server/mcp.js"),L=require("@modelcontextprotocol/sdk/server/stdio.js"),r=require("zod"),I=ee(require("ws"),1),M=require("uuid"),g={info:e=>process.stderr.write(`[INFO] ${e}
|
|
6
|
+
`),debug:e=>process.stderr.write(`[DEBUG] ${e}
|
|
7
|
+
`),warn:e=>process.stderr.write(`[WARN] ${e}
|
|
8
|
+
`),error:e=>process.stderr.write(`[ERROR] ${e}
|
|
9
|
+
`),log:e=>process.stderr.write(`[LOG] ${e}
|
|
10
|
+
`)},p=null,u=new Map,w=null,c=new P.McpServer({name:"TalkToFigmaMCP",version:"1.0.0"}),ne=process.argv.slice(2),O=ne.find(e=>e.startsWith("--server=")),v=O?O.split("=")[1]:"localhost",D=v==="localhost"?`ws://${v}`:`wss://${v}`;c.tool("get_document_info","Get detailed information about the current Figma document",{},async()=>{try{let e=await l("get_document_info");return{content:[{type:"text",text:JSON.stringify(e)}]}}catch(e){return{content:[{type:"text",text:`Error getting document info: ${e instanceof Error?e.message:String(e)}`}]}}});c.tool("get_selection","Get information about the current selection in Figma",{},async()=>{try{let e=await l("get_selection");return{content:[{type:"text",text:JSON.stringify(e)}]}}catch(e){return{content:[{type:"text",text:`Error getting selection: ${e instanceof Error?e.message:String(e)}`}]}}});c.tool("read_my_design","Get detailed information about the current selection in Figma, including all node details",{},async()=>{try{let e=await l("read_my_design",{});return{content:[{type:"text",text:JSON.stringify(e)}]}}catch(e){return{content:[{type:"text",text:`Error getting node info: ${e instanceof Error?e.message:String(e)}`}]}}});c.tool("get_node_info","Get detailed information about a specific node in Figma",{nodeId:r.z.string().describe("The ID of the node to get information about")},async({nodeId:e})=>{try{let t=await l("get_node_info",{nodeId:e});return{content:[{type:"text",text:JSON.stringify($(t))}]}}catch(t){return{content:[{type:"text",text:`Error getting node info: ${t instanceof Error?t.message:String(t)}`}]}}});c.tool("get_nodes_info","Get detailed information about multiple nodes in Figma",{nodeIds:r.z.array(r.z.string()).describe("Array of node IDs to get information about")},async({nodeIds:e})=>{try{let t=await Promise.all(e.map(async o=>{let n=await l("get_node_info",{nodeId:o});return{nodeId:o,info:n}}));return{content:[{type:"text",text:JSON.stringify(t.map(o=>$(o.info)))}]}}catch(t){return{content:[{type:"text",text:`Error getting nodes info: ${t instanceof Error?t.message:String(t)}`}]}}});c.tool("create_rectangle","Create a new rectangle in Figma",{x:r.z.number().describe("X position"),y:r.z.number().describe("Y position"),width:r.z.number().describe("Width of the rectangle"),height:r.z.number().describe("Height of the rectangle"),name:r.z.string().optional().describe("Optional name for the rectangle"),parentId:r.z.string().optional().describe("Optional parent node ID to append the rectangle to")},async({x:e,y:t,width:o,height:n,name:a,parentId:s})=>{try{let i=await l("create_rectangle",{x:e,y:t,width:o,height:n,name:a||"Rectangle",parentId:s});return{content:[{type:"text",text:`Created rectangle "${JSON.stringify(i)}"`}]}}catch(i){return{content:[{type:"text",text:`Error creating rectangle: ${i instanceof Error?i.message:String(i)}`}]}}});c.tool("create_frame","Create a new frame in Figma",{x:r.z.number().describe("X position"),y:r.z.number().describe("Y position"),width:r.z.number().describe("Width of the frame"),height:r.z.number().describe("Height of the frame"),name:r.z.string().optional().describe("Optional name for the frame"),parentId:r.z.string().optional().describe("Optional parent node ID to append the frame to"),fillColor:r.z.object({r:r.z.number().min(0).max(1).describe("Red component (0-1)"),g:r.z.number().min(0).max(1).describe("Green component (0-1)"),b:r.z.number().min(0).max(1).describe("Blue component (0-1)"),a:r.z.number().min(0).max(1).optional().describe("Alpha component (0-1)")}).optional().describe("Fill color in RGBA format"),strokeColor:r.z.object({r:r.z.number().min(0).max(1).describe("Red component (0-1)"),g:r.z.number().min(0).max(1).describe("Green component (0-1)"),b:r.z.number().min(0).max(1).describe("Blue component (0-1)"),a:r.z.number().min(0).max(1).optional().describe("Alpha component (0-1)")}).optional().describe("Stroke color in RGBA format"),strokeWeight:r.z.number().positive().optional().describe("Stroke weight"),layoutMode:r.z.enum(["NONE","HORIZONTAL","VERTICAL"]).optional().describe("Auto-layout mode for the frame"),layoutWrap:r.z.enum(["NO_WRAP","WRAP"]).optional().describe("Whether the auto-layout frame wraps its children"),paddingTop:r.z.number().optional().describe("Top padding for auto-layout frame"),paddingRight:r.z.number().optional().describe("Right padding for auto-layout frame"),paddingBottom:r.z.number().optional().describe("Bottom padding for auto-layout frame"),paddingLeft:r.z.number().optional().describe("Left padding for auto-layout frame"),primaryAxisAlignItems:r.z.enum(["MIN","MAX","CENTER","SPACE_BETWEEN"]).optional().describe("Primary axis alignment for auto-layout frame. Note: When set to SPACE_BETWEEN, itemSpacing will be ignored as children will be evenly spaced."),counterAxisAlignItems:r.z.enum(["MIN","MAX","CENTER","BASELINE"]).optional().describe("Counter axis alignment for auto-layout frame"),layoutSizingHorizontal:r.z.enum(["FIXED","HUG","FILL"]).optional().describe("Horizontal sizing mode for auto-layout frame"),layoutSizingVertical:r.z.enum(["FIXED","HUG","FILL"]).optional().describe("Vertical sizing mode for auto-layout frame"),itemSpacing:r.z.number().optional().describe("Distance between children in auto-layout frame. Note: This value will be ignored if primaryAxisAlignItems is set to SPACE_BETWEEN.")},async({x:e,y:t,width:o,height:n,name:a,parentId:s,fillColor:i,strokeColor:d,strokeWeight:m,layoutMode:f,layoutWrap:h,paddingTop:b,paddingRight:y,paddingBottom:J,paddingLeft:z,primaryAxisAlignItems:B,counterAxisAlignItems:j,layoutSizingHorizontal:U,layoutSizingVertical:W,itemSpacing:H})=>{try{let C=await l("create_frame",{x:e,y:t,width:o,height:n,name:a||"Frame",parentId:s,fillColor:i||{r:1,g:1,b:1,a:1},strokeColor:d,strokeWeight:m,layoutMode:f,layoutWrap:h,paddingTop:b,paddingRight:y,paddingBottom:J,paddingLeft:z,primaryAxisAlignItems:B,counterAxisAlignItems:j,layoutSizingHorizontal:U,layoutSizingVertical:W,itemSpacing:H});return{content:[{type:"text",text:`Created frame "${C.name}" with ID: ${C.id}. Use the ID as the parentId to appendChild inside this frame.`}]}}catch(S){return{content:[{type:"text",text:`Error creating frame: ${S instanceof Error?S.message:String(S)}`}]}}});c.tool("create_text","Create a new text element in Figma",{x:r.z.number().describe("X position"),y:r.z.number().describe("Y position"),text:r.z.string().describe("Text content"),fontSize:r.z.number().optional().describe("Font size (default: 14)"),fontWeight:r.z.number().optional().describe("Font weight (e.g., 400 for Regular, 700 for Bold)"),fontColor:r.z.object({r:r.z.number().min(0).max(1).describe("Red component (0-1)"),g:r.z.number().min(0).max(1).describe("Green component (0-1)"),b:r.z.number().min(0).max(1).describe("Blue component (0-1)"),a:r.z.number().min(0).max(1).optional().describe("Alpha component (0-1)")}).optional().describe("Font color in RGBA format"),name:r.z.string().optional().describe("Semantic layer name for the text node"),parentId:r.z.string().optional().describe("Optional parent node ID to append the text to")},async({x:e,y:t,text:o,fontSize:n,fontWeight:a,fontColor:s,name:i,parentId:d})=>{try{let f=await l("create_text",{x:e,y:t,text:o,fontSize:n||14,fontWeight:a||400,fontColor:s||{r:0,g:0,b:0,a:1},name:i||"Text",parentId:d});return{content:[{type:"text",text:`Created text "${f.name}" with ID: ${f.id}`}]}}catch(m){return{content:[{type:"text",text:`Error creating text: ${m instanceof Error?m.message:String(m)}`}]}}});c.tool("set_fill_color","Set the fill color of a node in Figma can be TextNode or FrameNode",{nodeId:r.z.string().describe("The ID of the node to modify"),r:r.z.number().min(0).max(1).describe("Red component (0-1)"),g:r.z.number().min(0).max(1).describe("Green component (0-1)"),b:r.z.number().min(0).max(1).describe("Blue component (0-1)"),a:r.z.number().min(0).max(1).optional().describe("Alpha component (0-1)")},async({nodeId:e,r:t,g:o,b:n,a})=>{try{return{content:[{type:"text",text:`Set fill color of node "${(await l("set_fill_color",{nodeId:e,color:{r:t,g:o,b:n,a:a||1}})).name}" to RGBA(${t}, ${o}, ${n}, ${a||1})`}]}}catch(s){return{content:[{type:"text",text:`Error setting fill color: ${s instanceof Error?s.message:String(s)}`}]}}});c.tool("set_stroke_color","Set the stroke color of a node in Figma",{nodeId:r.z.string().describe("The ID of the node to modify"),r:r.z.number().min(0).max(1).describe("Red component (0-1)"),g:r.z.number().min(0).max(1).describe("Green component (0-1)"),b:r.z.number().min(0).max(1).describe("Blue component (0-1)"),a:r.z.number().min(0).max(1).optional().describe("Alpha component (0-1)"),weight:r.z.number().positive().optional().describe("Stroke weight")},async({nodeId:e,r:t,g:o,b:n,a,weight:s})=>{try{return{content:[{type:"text",text:`Set stroke color of node "${(await l("set_stroke_color",{nodeId:e,color:{r:t,g:o,b:n,a:a||1},weight:s||1})).name}" to RGBA(${t}, ${o}, ${n}, ${a||1}) with weight ${s||1}`}]}}catch(i){return{content:[{type:"text",text:`Error setting stroke color: ${i instanceof Error?i.message:String(i)}`}]}}});c.tool("move_node","Move a node to a new position in Figma",{nodeId:r.z.string().describe("The ID of the node to move"),x:r.z.number().describe("New X position"),y:r.z.number().describe("New Y position")},async({nodeId:e,x:t,y:o})=>{try{return{content:[{type:"text",text:`Moved node "${(await l("move_node",{nodeId:e,x:t,y:o})).name}" to position (${t}, ${o})`}]}}catch(n){return{content:[{type:"text",text:`Error moving node: ${n instanceof Error?n.message:String(n)}`}]}}});c.tool("clone_node","Clone an existing node in Figma",{nodeId:r.z.string().describe("The ID of the node to clone"),x:r.z.number().optional().describe("New X position for the clone"),y:r.z.number().optional().describe("New Y position for the clone")},async({nodeId:e,x:t,y:o})=>{try{let a=await l("clone_node",{nodeId:e,x:t,y:o});return{content:[{type:"text",text:`Cloned node "${a.name}" with new ID: ${a.id}${t!==void 0&&o!==void 0?` at position (${t}, ${o})`:""}`}]}}catch(n){return{content:[{type:"text",text:`Error cloning node: ${n instanceof Error?n.message:String(n)}`}]}}});c.tool("resize_node","Resize a node in Figma",{nodeId:r.z.string().describe("The ID of the node to resize"),width:r.z.number().positive().describe("New width"),height:r.z.number().positive().describe("New height")},async({nodeId:e,width:t,height:o})=>{try{return{content:[{type:"text",text:`Resized node "${(await l("resize_node",{nodeId:e,width:t,height:o})).name}" to width ${t} and height ${o}`}]}}catch(n){return{content:[{type:"text",text:`Error resizing node: ${n instanceof Error?n.message:String(n)}`}]}}});c.tool("delete_node","Delete a node from Figma",{nodeId:r.z.string().describe("The ID of the node to delete")},async({nodeId:e})=>{try{return await l("delete_node",{nodeId:e}),{content:[{type:"text",text:`Deleted node with ID: ${e}`}]}}catch(t){return{content:[{type:"text",text:`Error deleting node: ${t instanceof Error?t.message:String(t)}`}]}}});c.tool("delete_multiple_nodes","Delete multiple nodes from Figma at once",{nodeIds:r.z.array(r.z.string()).describe("Array of node IDs to delete")},async({nodeIds:e})=>{try{let t=await l("delete_multiple_nodes",{nodeIds:e});return{content:[{type:"text",text:JSON.stringify(t)}]}}catch(t){return{content:[{type:"text",text:`Error deleting multiple nodes: ${t instanceof Error?t.message:String(t)}`}]}}});c.tool("export_node_as_image","Export a node as an image from Figma",{nodeId:r.z.string().describe("The ID of the node to export"),format:r.z.enum(["PNG","JPG","SVG","PDF"]).optional().describe("Export format"),scale:r.z.number().positive().optional().describe("Export scale")},async({nodeId:e,format:t,scale:o})=>{try{let a=await l("export_node_as_image",{nodeId:e,format:t||"PNG",scale:o||1});return{content:[{type:"image",data:a.imageData,mimeType:a.mimeType||"image/png"}]}}catch(n){return{content:[{type:"text",text:`Error exporting node as image: ${n instanceof Error?n.message:String(n)}`}]}}});c.tool("set_text_content","Set the text content of an existing text node in Figma",{nodeId:r.z.string().describe("The ID of the text node to modify"),text:r.z.string().describe("New text content")},async({nodeId:e,text:t})=>{try{return{content:[{type:"text",text:`Updated text content of node "${(await l("set_text_content",{nodeId:e,text:t})).name}" to "${t}"`}]}}catch(o){return{content:[{type:"text",text:`Error setting text content: ${o instanceof Error?o.message:String(o)}`}]}}});c.tool("set_font_family","Set the font family of an existing text node in Figma",{nodeId:r.z.string().describe("The ID of the text node to modify"),fontFamily:r.z.string().describe("Font family name (e.g. 'Inter', 'Roboto', 'Arial')"),fontStyle:r.z.string().optional().describe("Font style (e.g. 'Regular', 'Bold', 'Italic'). Defaults to 'Regular'")},async({nodeId:e,fontFamily:t,fontStyle:o})=>{try{let a=await l("set_font_family",{nodeId:e,fontFamily:t,fontStyle:o||"Regular"});return{content:[{type:"text",text:`Updated font family of node "${a.name}" to "${a.fontName.family} ${a.fontName.style}"`}]}}catch(n){return{content:[{type:"text",text:`Error setting font family: ${n instanceof Error?n.message:String(n)}`}]}}});c.tool("set_font_size","Set the font size of an existing text node in Figma",{nodeId:r.z.string().describe("The ID of the text node to modify"),fontSize:r.z.number().min(1).describe("Font size in pixels")},async({nodeId:e,fontSize:t})=>{try{let n=await l("set_font_size",{nodeId:e,fontSize:t});return{content:[{type:"text",text:`Updated font size of node "${n.name}" to ${n.fontSize}px`}]}}catch(o){return{content:[{type:"text",text:`Error setting font size: ${o instanceof Error?o.message:String(o)}`}]}}});c.tool("set_font_weight","Set the font weight of an existing text node in Figma. Maps numeric weight to font style (100=Thin, 300=Light, 400=Regular, 500=Medium, 600=Semi Bold, 700=Bold, 800=Extra Bold, 900=Black)",{nodeId:r.z.string().describe("The ID of the text node to modify"),fontWeight:r.z.number().min(100).max(900).describe("Font weight (100-900)")},async({nodeId:e,fontWeight:t})=>{try{let n=await l("set_font_weight",{nodeId:e,fontWeight:t});return{content:[{type:"text",text:`Updated font weight of node "${n.name}" to ${t} (${n.fontName.style})`}]}}catch(o){return{content:[{type:"text",text:`Error setting font weight: ${o instanceof Error?o.message:String(o)}`}]}}});c.tool("get_styles","Get all styles from the current Figma document",{},async()=>{try{let e=await l("get_styles");return{content:[{type:"text",text:JSON.stringify(e)}]}}catch(e){return{content:[{type:"text",text:`Error getting styles: ${e instanceof Error?e.message:String(e)}`}]}}});c.tool("get_local_components","Get all local components from the Figma document",{},async()=>{try{let e=await l("get_local_components");return{content:[{type:"text",text:JSON.stringify(e)}]}}catch(e){return{content:[{type:"text",text:`Error getting local components: ${e instanceof Error?e.message:String(e)}`}]}}});c.tool("get_local_variables","Get all local variables and variable collections from the Figma document, including values per mode",{},async()=>{try{let e=await l("get_local_variables");return{content:[{type:"text",text:JSON.stringify(e)}]}}catch(e){return{content:[{type:"text",text:`Error getting local variables: ${e instanceof Error?e.message:String(e)}`}]}}});c.tool("get_local_variable_collections","Get all local variable collections with their modes",{},async()=>{try{let e=await l("get_local_variable_collections");return{content:[{type:"text",text:JSON.stringify(e)}]}}catch(e){return{content:[{type:"text",text:`Error getting local variable collections: ${e instanceof Error?e.message:String(e)}`}]}}});c.tool("get_variable_by_id","Get detailed information about a specific variable by its ID, including values per mode, scopes, and code syntax",{variableId:r.z.string().describe("The ID of the variable to retrieve")},async({variableId:e})=>{try{let t=await l("get_variable_by_id",{variableId:e});return{content:[{type:"text",text:JSON.stringify(t)}]}}catch(t){return{content:[{type:"text",text:`Error getting variable: ${t instanceof Error?t.message:String(t)}`}]}}});c.tool("create_variable_collection","Create a new variable collection in the Figma document",{name:r.z.string().describe("Name for the new variable collection")},async({name:e})=>{try{let t=await l("create_variable_collection",{name:e});return{content:[{type:"text",text:JSON.stringify(t)}]}}catch(t){return{content:[{type:"text",text:`Error creating variable collection: ${t instanceof Error?t.message:String(t)}`}]}}});c.tool("create_variable","Create a new variable in a variable collection",{name:r.z.string().describe("Name for the new variable"),collectionId:r.z.string().describe("ID of the variable collection to add the variable to"),resolvedType:r.z.enum(["COLOR","FLOAT","STRING","BOOLEAN"]).describe("The resolved type of the variable")},async({name:e,collectionId:t,resolvedType:o})=>{try{let n=await l("create_variable",{name:e,collectionId:t,resolvedType:o});return{content:[{type:"text",text:JSON.stringify(n)}]}}catch(n){return{content:[{type:"text",text:`Error creating variable: ${n instanceof Error?n.message:String(n)}`}]}}});c.tool("set_variable_value","Set the value of a variable for a specific mode. For COLOR type use {r, g, b, a} with 0-1 range. For FLOAT use a number. For STRING use a string. For BOOLEAN use true/false.",{variableId:r.z.string().describe("The ID of the variable"),modeId:r.z.string().describe("The mode ID to set the value for"),value:r.z.any().describe("The value to set - type depends on variable resolvedType (COLOR: {r,g,b,a}, FLOAT: number, STRING: string, BOOLEAN: boolean)")},async({variableId:e,modeId:t,value:o})=>{try{let n=await l("set_variable_value",{variableId:e,modeId:t,value:o});return{content:[{type:"text",text:JSON.stringify(n)}]}}catch(n){return{content:[{type:"text",text:`Error setting variable value: ${n instanceof Error?n.message:String(n)}`}]}}});c.tool("set_variable_mode_name","Rename a mode in a variable collection",{collectionId:r.z.string().describe("The ID of the variable collection"),modeId:r.z.string().describe("The ID of the mode to rename"),newName:r.z.string().describe("The new name for the mode")},async({collectionId:e,modeId:t,newName:o})=>{try{let n=await l("set_variable_mode_name",{collectionId:e,modeId:t,newName:o});return{content:[{type:"text",text:JSON.stringify(n)}]}}catch(n){return{content:[{type:"text",text:`Error renaming variable mode: ${n instanceof Error?n.message:String(n)}`}]}}});c.tool("set_variable_binding","Bind a variable to a node property. Fields include 'fills/0/color', 'strokes/0/color', 'width', 'height', 'itemSpacing', 'paddingLeft', 'paddingRight', 'paddingTop', 'paddingBottom', etc.",{nodeId:r.z.string().describe("The ID of the node to bind the variable to"),field:r.z.string().describe("The property field to bind (e.g. 'fills/0/color', 'width', 'itemSpacing')"),variableId:r.z.string().describe("The ID of the variable to bind")},async({nodeId:e,field:t,variableId:o})=>{try{let n=await l("set_variable_binding",{nodeId:e,field:t,variableId:o});return{content:[{type:"text",text:JSON.stringify(n)}]}}catch(n){return{content:[{type:"text",text:`Error setting variable binding: ${n instanceof Error?n.message:String(n)}`}]}}});c.tool("get_team_library_components","Get all available components from team libraries",{},async()=>{try{let e=await l("get_team_library_components");return{content:[{type:"text",text:JSON.stringify(e)}]}}catch(e){return{content:[{type:"text",text:`Error getting team library components: ${e instanceof Error?e.message:String(e)}`}]}}});c.tool("get_team_library_variables","Get all available variables from team libraries",{},async()=>{try{let e=await l("get_team_library_variables");return{content:[{type:"text",text:JSON.stringify(e)}]}}catch(e){return{content:[{type:"text",text:`Error getting team library variables: ${e instanceof Error?e.message:String(e)}`}]}}});c.tool("import_variable_by_key","Import a variable from a team library by its key",{key:r.z.string().describe("The key of the variable to import from the team library")},async({key:e})=>{try{let t=await l("import_variable_by_key",{key:e});return{content:[{type:"text",text:JSON.stringify(t)}]}}catch(t){return{content:[{type:"text",text:`Error importing variable: ${t instanceof Error?t.message:String(t)}`}]}}});c.tool("get_annotations","Get all annotations in the current document or specific node",{nodeId:r.z.string().describe("node ID to get annotations for specific node"),includeCategories:r.z.boolean().optional().default(!0).describe("Whether to include category information")},async({nodeId:e,includeCategories:t})=>{try{let o=await l("get_annotations",{nodeId:e,includeCategories:t});return{content:[{type:"text",text:JSON.stringify(o)}]}}catch(o){return{content:[{type:"text",text:`Error getting annotations: ${o instanceof Error?o.message:String(o)}`}]}}});c.tool("set_annotation","Create or update an annotation",{nodeId:r.z.string().describe("The ID of the node to annotate"),annotationId:r.z.string().optional().describe("The ID of the annotation to update (if updating existing annotation)"),labelMarkdown:r.z.string().describe("The annotation text in markdown format"),categoryId:r.z.string().optional().describe("The ID of the annotation category"),properties:r.z.array(r.z.object({type:r.z.string()})).optional().describe("Additional properties for the annotation")},async({nodeId:e,annotationId:t,labelMarkdown:o,categoryId:n,properties:a})=>{try{let s=await l("set_annotation",{nodeId:e,annotationId:t,labelMarkdown:o,categoryId:n,properties:a});return{content:[{type:"text",text:JSON.stringify(s)}]}}catch(s){return{content:[{type:"text",text:`Error setting annotation: ${s instanceof Error?s.message:String(s)}`}]}}});c.tool("set_multiple_annotations","Set multiple annotations parallelly in a node",{nodeId:r.z.string().describe("The ID of the node containing the elements to annotate"),annotations:r.z.array(r.z.object({nodeId:r.z.string().describe("The ID of the node to annotate"),labelMarkdown:r.z.string().describe("The annotation text in markdown format"),categoryId:r.z.string().optional().describe("The ID of the annotation category"),annotationId:r.z.string().optional().describe("The ID of the annotation to update (if updating existing annotation)"),properties:r.z.array(r.z.object({type:r.z.string()})).optional().describe("Additional properties for the annotation")})).describe("Array of annotations to apply")},async({nodeId:e,annotations:t})=>{try{if(!t||t.length===0)return{content:[{type:"text",text:"No annotations provided"}]};let o={type:"text",text:`Starting annotation process for ${t.length} nodes. This will be processed in batches of 5...`},n=0,a=t.length,i=await l("set_multiple_annotations",{nodeId:e,annotations:t}),d=i.annotationsApplied&&i.annotationsApplied>0,m=`
|
|
1544
11
|
Annotation process completed:
|
|
1545
|
-
- ${
|
|
1546
|
-
- ${
|
|
1547
|
-
- Processed in ${
|
|
1548
|
-
|
|
1549
|
-
const detailedResults = typedResult.results || [];
|
|
1550
|
-
const failedResults = detailedResults.filter((item) => !item.success);
|
|
1551
|
-
let detailedResponse = "";
|
|
1552
|
-
if (failedResults.length > 0) {
|
|
1553
|
-
detailedResponse = `
|
|
12
|
+
- ${i.annotationsApplied||0} of ${a} successfully applied
|
|
13
|
+
- ${i.annotationsFailed||0} failed
|
|
14
|
+
- Processed in ${i.completedInChunks||1} batches
|
|
15
|
+
`,h=(i.results||[]).filter(y=>!y.success),b="";return h.length>0&&(b=`
|
|
1554
16
|
|
|
1555
17
|
Nodes that failed:
|
|
1556
|
-
${
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
return {
|
|
1561
|
-
content: [
|
|
1562
|
-
initialStatus,
|
|
1563
|
-
{
|
|
1564
|
-
type: "text",
|
|
1565
|
-
text: progressText + detailedResponse
|
|
1566
|
-
}
|
|
1567
|
-
]
|
|
1568
|
-
};
|
|
1569
|
-
} catch (error) {
|
|
1570
|
-
return {
|
|
1571
|
-
content: [
|
|
1572
|
-
{
|
|
1573
|
-
type: "text",
|
|
1574
|
-
text: `Error setting multiple annotations: ${error instanceof Error ? error.message : String(error)}`
|
|
1575
|
-
}
|
|
1576
|
-
]
|
|
1577
|
-
};
|
|
1578
|
-
}
|
|
1579
|
-
}
|
|
1580
|
-
);
|
|
1581
|
-
server.tool(
|
|
1582
|
-
"create_component_instance",
|
|
1583
|
-
"Create an instance of a component in Figma",
|
|
1584
|
-
{
|
|
1585
|
-
componentKey: import_zod.z.string().describe("Key of the component to instantiate"),
|
|
1586
|
-
x: import_zod.z.number().describe("X position"),
|
|
1587
|
-
y: import_zod.z.number().describe("Y position")
|
|
1588
|
-
},
|
|
1589
|
-
async ({ componentKey, x, y }) => {
|
|
1590
|
-
try {
|
|
1591
|
-
const result = await sendCommandToFigma("create_component_instance", {
|
|
1592
|
-
componentKey,
|
|
1593
|
-
x,
|
|
1594
|
-
y
|
|
1595
|
-
});
|
|
1596
|
-
const typedResult = result;
|
|
1597
|
-
return {
|
|
1598
|
-
content: [
|
|
1599
|
-
{
|
|
1600
|
-
type: "text",
|
|
1601
|
-
text: JSON.stringify(typedResult)
|
|
1602
|
-
}
|
|
1603
|
-
]
|
|
1604
|
-
};
|
|
1605
|
-
} catch (error) {
|
|
1606
|
-
return {
|
|
1607
|
-
content: [
|
|
1608
|
-
{
|
|
1609
|
-
type: "text",
|
|
1610
|
-
text: `Error creating component instance: ${error instanceof Error ? error.message : String(error)}`
|
|
1611
|
-
}
|
|
1612
|
-
]
|
|
1613
|
-
};
|
|
1614
|
-
}
|
|
1615
|
-
}
|
|
1616
|
-
);
|
|
1617
|
-
server.tool(
|
|
1618
|
-
"create_component",
|
|
1619
|
-
"Convert an existing node (typically a frame) into a reusable Figma Component. The node must not be nested inside another component, component set, or instance.",
|
|
1620
|
-
{
|
|
1621
|
-
nodeId: import_zod.z.string().describe("ID of the node to convert into a component")
|
|
1622
|
-
},
|
|
1623
|
-
async ({ nodeId }) => {
|
|
1624
|
-
try {
|
|
1625
|
-
const result = await sendCommandToFigma("create_component", { nodeId });
|
|
1626
|
-
const typedResult = result;
|
|
1627
|
-
return {
|
|
1628
|
-
content: [
|
|
1629
|
-
{
|
|
1630
|
-
type: "text",
|
|
1631
|
-
text: `Created component "${typedResult.name}" with ID: ${typedResult.id}, key: ${typedResult.key}`
|
|
1632
|
-
}
|
|
1633
|
-
]
|
|
1634
|
-
};
|
|
1635
|
-
} catch (error) {
|
|
1636
|
-
return {
|
|
1637
|
-
content: [
|
|
1638
|
-
{
|
|
1639
|
-
type: "text",
|
|
1640
|
-
text: `Error creating component: ${error instanceof Error ? error.message : String(error)}`
|
|
1641
|
-
}
|
|
1642
|
-
]
|
|
1643
|
-
};
|
|
1644
|
-
}
|
|
1645
|
-
}
|
|
1646
|
-
);
|
|
1647
|
-
server.tool(
|
|
1648
|
-
"create_component_set",
|
|
1649
|
-
"Combine multiple Component nodes into a single ComponentSet (variant component). All provided nodes must already be Components (use create_component first). Components should use naming like 'property=value, property=value' for Figma to auto-detect variant properties.",
|
|
1650
|
-
{
|
|
1651
|
-
nodeIds: import_zod.z.array(import_zod.z.string()).min(1).describe("Array of Component node IDs to combine into a variant set"),
|
|
1652
|
-
parentId: import_zod.z.string().optional().describe("Optional parent node ID. Defaults to the first component's parent.")
|
|
1653
|
-
},
|
|
1654
|
-
async ({ nodeIds, parentId }) => {
|
|
1655
|
-
try {
|
|
1656
|
-
const result = await sendCommandToFigma("create_component_set", { nodeIds, parentId });
|
|
1657
|
-
const typedResult = result;
|
|
1658
|
-
return {
|
|
1659
|
-
content: [
|
|
1660
|
-
{
|
|
1661
|
-
type: "text",
|
|
1662
|
-
text: `Created component set "${typedResult.name}" with ID: ${typedResult.id}, containing ${typedResult.childCount} variants`
|
|
1663
|
-
}
|
|
1664
|
-
]
|
|
1665
|
-
};
|
|
1666
|
-
} catch (error) {
|
|
1667
|
-
return {
|
|
1668
|
-
content: [
|
|
1669
|
-
{
|
|
1670
|
-
type: "text",
|
|
1671
|
-
text: `Error creating component set: ${error instanceof Error ? error.message : String(error)}`
|
|
1672
|
-
}
|
|
1673
|
-
]
|
|
1674
|
-
};
|
|
1675
|
-
}
|
|
1676
|
-
}
|
|
1677
|
-
);
|
|
1678
|
-
server.tool(
|
|
1679
|
-
"get_instance_overrides",
|
|
1680
|
-
"Get all override properties from a selected component instance. These overrides can be applied to other instances, which will swap them to match the source component.",
|
|
1681
|
-
{
|
|
1682
|
-
nodeId: import_zod.z.string().optional().describe("Optional ID of the component instance to get overrides from. If not provided, currently selected instance will be used.")
|
|
1683
|
-
},
|
|
1684
|
-
async ({ nodeId }) => {
|
|
1685
|
-
try {
|
|
1686
|
-
const result = await sendCommandToFigma("get_instance_overrides", {
|
|
1687
|
-
instanceNodeId: nodeId || null
|
|
1688
|
-
});
|
|
1689
|
-
const typedResult = result;
|
|
1690
|
-
return {
|
|
1691
|
-
content: [
|
|
1692
|
-
{
|
|
1693
|
-
type: "text",
|
|
1694
|
-
text: typedResult.success ? `Successfully got instance overrides: ${typedResult.message}` : `Failed to get instance overrides: ${typedResult.message}`
|
|
1695
|
-
}
|
|
1696
|
-
]
|
|
1697
|
-
};
|
|
1698
|
-
} catch (error) {
|
|
1699
|
-
return {
|
|
1700
|
-
content: [
|
|
1701
|
-
{
|
|
1702
|
-
type: "text",
|
|
1703
|
-
text: `Error copying instance overrides: ${error instanceof Error ? error.message : String(error)}`
|
|
1704
|
-
}
|
|
1705
|
-
]
|
|
1706
|
-
};
|
|
1707
|
-
}
|
|
1708
|
-
}
|
|
1709
|
-
);
|
|
1710
|
-
server.tool(
|
|
1711
|
-
"set_instance_overrides",
|
|
1712
|
-
"Apply previously copied overrides to selected component instances. Target instances will be swapped to the source component and all copied override properties will be applied.",
|
|
1713
|
-
{
|
|
1714
|
-
sourceInstanceId: import_zod.z.string().describe("ID of the source component instance"),
|
|
1715
|
-
targetNodeIds: import_zod.z.array(import_zod.z.string()).describe("Array of target instance IDs. Currently selected instances will be used.")
|
|
1716
|
-
},
|
|
1717
|
-
async ({ sourceInstanceId, targetNodeIds }) => {
|
|
1718
|
-
try {
|
|
1719
|
-
const result = await sendCommandToFigma("set_instance_overrides", {
|
|
1720
|
-
sourceInstanceId,
|
|
1721
|
-
targetNodeIds: targetNodeIds || []
|
|
1722
|
-
});
|
|
1723
|
-
const typedResult = result;
|
|
1724
|
-
if (typedResult.success) {
|
|
1725
|
-
const successCount = typedResult.results?.filter((r) => r.success).length || 0;
|
|
1726
|
-
return {
|
|
1727
|
-
content: [
|
|
1728
|
-
{
|
|
1729
|
-
type: "text",
|
|
1730
|
-
text: `Successfully applied ${typedResult.totalCount || 0} overrides to ${successCount} instances.`
|
|
1731
|
-
}
|
|
1732
|
-
]
|
|
1733
|
-
};
|
|
1734
|
-
} else {
|
|
1735
|
-
return {
|
|
1736
|
-
content: [
|
|
1737
|
-
{
|
|
1738
|
-
type: "text",
|
|
1739
|
-
text: `Failed to set instance overrides: ${typedResult.message}`
|
|
1740
|
-
}
|
|
1741
|
-
]
|
|
1742
|
-
};
|
|
1743
|
-
}
|
|
1744
|
-
} catch (error) {
|
|
1745
|
-
return {
|
|
1746
|
-
content: [
|
|
1747
|
-
{
|
|
1748
|
-
type: "text",
|
|
1749
|
-
text: `Error setting instance overrides: ${error instanceof Error ? error.message : String(error)}`
|
|
1750
|
-
}
|
|
1751
|
-
]
|
|
1752
|
-
};
|
|
1753
|
-
}
|
|
1754
|
-
}
|
|
1755
|
-
);
|
|
1756
|
-
server.tool(
|
|
1757
|
-
"set_corner_radius",
|
|
1758
|
-
"Set the corner radius of a node in Figma",
|
|
1759
|
-
{
|
|
1760
|
-
nodeId: import_zod.z.string().describe("The ID of the node to modify"),
|
|
1761
|
-
radius: import_zod.z.number().min(0).describe("Corner radius value"),
|
|
1762
|
-
corners: import_zod.z.array(import_zod.z.boolean()).length(4).optional().describe(
|
|
1763
|
-
"Optional array of 4 booleans to specify which corners to round [topLeft, topRight, bottomRight, bottomLeft]"
|
|
1764
|
-
)
|
|
1765
|
-
},
|
|
1766
|
-
async ({ nodeId, radius, corners }) => {
|
|
1767
|
-
try {
|
|
1768
|
-
const result = await sendCommandToFigma("set_corner_radius", {
|
|
1769
|
-
nodeId,
|
|
1770
|
-
radius,
|
|
1771
|
-
corners: corners || [true, true, true, true]
|
|
1772
|
-
});
|
|
1773
|
-
const typedResult = result;
|
|
1774
|
-
return {
|
|
1775
|
-
content: [
|
|
1776
|
-
{
|
|
1777
|
-
type: "text",
|
|
1778
|
-
text: `Set corner radius of node "${typedResult.name}" to ${radius}px`
|
|
1779
|
-
}
|
|
1780
|
-
]
|
|
1781
|
-
};
|
|
1782
|
-
} catch (error) {
|
|
1783
|
-
return {
|
|
1784
|
-
content: [
|
|
1785
|
-
{
|
|
1786
|
-
type: "text",
|
|
1787
|
-
text: `Error setting corner radius: ${error instanceof Error ? error.message : String(error)}`
|
|
1788
|
-
}
|
|
1789
|
-
]
|
|
1790
|
-
};
|
|
1791
|
-
}
|
|
1792
|
-
}
|
|
1793
|
-
);
|
|
1794
|
-
server.prompt(
|
|
1795
|
-
"design_strategy",
|
|
1796
|
-
"Best practices for working with Figma designs",
|
|
1797
|
-
(extra) => {
|
|
1798
|
-
return {
|
|
1799
|
-
messages: [
|
|
1800
|
-
{
|
|
1801
|
-
role: "assistant",
|
|
1802
|
-
content: {
|
|
1803
|
-
type: "text",
|
|
1804
|
-
text: `When working with Figma designs, follow these best practices:
|
|
18
|
+
${h.map(y=>`- ${y.nodeId}: ${y.error||"Unknown error"}`).join(`
|
|
19
|
+
`)}`),{content:[o,{type:"text",text:m+b}]}}catch(o){return{content:[{type:"text",text:`Error setting multiple annotations: ${o instanceof Error?o.message:String(o)}`}]}}});c.tool("create_component_instance","Create an instance of a component in Figma. Can optionally be placed inside a parent frame/node.",{componentKey:r.z.string().describe("Key of the component to instantiate"),x:r.z.number().describe("X position"),y:r.z.number().describe("Y position"),parentId:r.z.string().optional().describe("Optional parent node ID to place the instance inside (e.g., an auto-layout frame)")},async({componentKey:e,x:t,y:o,parentId:n})=>{try{let s=await l("create_component_instance",{componentKey:e,x:t,y:o,parentId:n});return{content:[{type:"text",text:JSON.stringify(s)}]}}catch(a){return{content:[{type:"text",text:`Error creating component instance: ${a instanceof Error?a.message:String(a)}`}]}}});c.tool("create_component","Convert an existing node (typically a frame) into a reusable Figma Component. The node must not be nested inside another component, component set, or instance.",{nodeId:r.z.string().describe("ID of the node to convert into a component")},async({nodeId:e})=>{try{let o=await l("create_component",{nodeId:e});return{content:[{type:"text",text:`Created component "${o.name}" with ID: ${o.id}, key: ${o.key}`}]}}catch(t){return{content:[{type:"text",text:`Error creating component: ${t instanceof Error?t.message:String(t)}`}]}}});c.tool("create_component_set","Combine multiple Component nodes into a single ComponentSet (variant component). All provided nodes must already be Components (use create_component first). Components should use naming like 'property=value, property=value' for Figma to auto-detect variant properties.",{nodeIds:r.z.array(r.z.string()).min(1).describe("Array of Component node IDs to combine into a variant set"),parentId:r.z.string().optional().describe("Optional parent node ID. Defaults to the first component's parent.")},async({nodeIds:e,parentId:t})=>{try{let n=await l("create_component_set",{nodeIds:e,parentId:t});return{content:[{type:"text",text:`Created component set "${n.name}" with ID: ${n.id}, containing ${n.childCount} variants`}]}}catch(o){return{content:[{type:"text",text:`Error creating component set: ${o instanceof Error?o.message:String(o)}`}]}}});c.tool("set_component_text_property","Add a text component property to a Component or ComponentSet and link specified text nodes to it. This creates a 'Label'-style property that users can override on instances.",{componentId:r.z.string().describe("ID of the Component or ComponentSet to add the property to"),propertyName:r.z.string().describe("Name of the component property (e.g., 'Label')"),defaultValue:r.z.string().optional().describe("Default text value for the property (defaults to current text content)"),textNodeIds:r.z.array(r.z.string()).min(1).describe("Array of text node IDs to link to this property. All text nodes will have their content controlled by the property.")},async({componentId:e,propertyName:t,defaultValue:o,textNodeIds:n})=>{try{let s=await l("set_component_text_property",{componentId:e,propertyName:t,defaultValue:o,textNodeIds:n});return{content:[{type:"text",text:JSON.stringify(s)}]}}catch(a){return{content:[{type:"text",text:`Error setting component text property: ${a instanceof Error?a.message:String(a)}`}]}}});c.tool("rename_node","Rename a single node in Figma. Useful for setting variant naming patterns like 'property=value, property=value'.",{nodeId:r.z.string().describe("ID of the node to rename"),name:r.z.string().describe("New name for the node")},async({nodeId:e,name:t})=>{try{let n=await l("rename_node",{nodeId:e,name:t});return{content:[{type:"text",text:`Renamed node ${n.id} to "${n.name}"`}]}}catch(o){return{content:[{type:"text",text:`Error renaming node: ${o instanceof Error?o.message:String(o)}`}]}}});c.tool("rename_nodes","Rename multiple nodes in a single batch call. More efficient than calling rename_node multiple times.",{nodes:r.z.array(r.z.object({nodeId:r.z.string().describe("ID of the node to rename"),name:r.z.string().describe("New name for the node")})).min(1).describe("Array of {nodeId, name} pairs to rename")},async({nodes:e})=>{try{let o=await l("rename_nodes",{nodes:e});return{content:[{type:"text",text:`Renamed ${o.renamed} nodes. Errors: ${o.errors}${o.errorMessages.length>0?`
|
|
20
|
+
`+o.errorMessages.join(`
|
|
21
|
+
`):""}`}]}}catch(t){return{content:[{type:"text",text:`Error renaming nodes: ${t instanceof Error?t.message:String(t)}`}]}}});c.tool("add_boolean_property","Add a BOOLEAN component property to a ComponentSet and link it to layer visibility. Finds layers matching layerName across all variants and connects their visibility to the boolean property.",{componentSetId:r.z.string().describe("ID of the ComponentSet or Component to add the property to"),propertyName:r.z.string().describe("Name of the boolean property (e.g., 'ShowIconRight')"),defaultValue:r.z.preprocess(e=>e==="true"?!0:e==="false"?!1:e,r.z.boolean().default(!0)).describe("Default value for the boolean property"),layerName:r.z.string().describe("Name of layers to link visibility to this property")},async({componentSetId:e,propertyName:t,defaultValue:o,layerName:n})=>{try{let a=await l("add_boolean_property",{componentSetId:e,propertyName:t,defaultValue:o,layerName:n});return{content:[{type:"text",text:JSON.stringify(a)}]}}catch(a){return{content:[{type:"text",text:`Error adding boolean property: ${a instanceof Error?a.message:String(a)}`}]}}});c.tool("add_instance_swap_property","Add an INSTANCE_SWAP component property to a ComponentSet and link it to nested instances. Finds instance layers matching layerName across all variants and connects their mainComponent to the swap property.",{componentSetId:r.z.string().describe("ID of the ComponentSet or Component to add the property to"),propertyName:r.z.string().describe("Name of the instance swap property (e.g., 'IconRight')"),defaultComponentId:r.z.string().describe("ID of the default component for the instance swap"),layerName:r.z.string().describe("Name of instance layers to link to this property")},async({componentSetId:e,propertyName:t,defaultComponentId:o,layerName:n})=>{try{let a=await l("add_instance_swap_property",{componentSetId:e,propertyName:t,defaultComponentId:o,layerName:n});return{content:[{type:"text",text:JSON.stringify(a)}]}}catch(a){return{content:[{type:"text",text:`Error adding instance swap property: ${a instanceof Error?a.message:String(a)}`}]}}});c.tool("sync_layer_fills","Sync fill colors from one named layer to another across all variants in a ComponentSet. Copies fills (including variable bindings) from source layer to target layer and all its descendants recursively.",{componentSetId:r.z.string().describe("ID of the ComponentSet"),sourceLayerName:r.z.string().describe("Name of the source layer to copy fills from (e.g., 'Label')"),targetLayerName:r.z.string().describe("Name of the target layer to apply fills to (e.g., 'IconRight')")},async({componentSetId:e,sourceLayerName:t,targetLayerName:o})=>{try{let n=await l("sync_layer_fills",{componentSetId:e,sourceLayerName:t,targetLayerName:o});return{content:[{type:"text",text:JSON.stringify(n)}]}}catch(n){return{content:[{type:"text",text:`Error syncing layer fills: ${n instanceof Error?n.message:String(n)}`}]}}});c.tool("move_child_to_index","Move a named child layer to a specific index position within all variants of a ComponentSet. Useful for reordering children in auto-layout frames.",{componentSetId:r.z.string().describe("ID of the ComponentSet"),layerName:r.z.string().describe("Name of the child layer to move"),targetIndex:r.z.number().describe("Target index position (0-based)")},async({componentSetId:e,layerName:t,targetIndex:o})=>{try{let n=await l("move_child_to_index",{componentSetId:e,layerName:t,targetIndex:o});return{content:[{type:"text",text:JSON.stringify(n)}]}}catch(n){return{content:[{type:"text",text:`Error moving child: ${n instanceof Error?n.message:String(n)}`}]}}});c.tool("get_instance_overrides","Get all override properties from a selected component instance. These overrides can be applied to other instances, which will swap them to match the source component.",{nodeId:r.z.string().optional().describe("Optional ID of the component instance to get overrides from. If not provided, currently selected instance will be used.")},async({nodeId:e})=>{try{let o=await l("get_instance_overrides",{instanceNodeId:e||null});return{content:[{type:"text",text:o.success?`Successfully got instance overrides: ${o.message}`:`Failed to get instance overrides: ${o.message}`}]}}catch(t){return{content:[{type:"text",text:`Error copying instance overrides: ${t instanceof Error?t.message:String(t)}`}]}}});c.tool("set_instance_overrides","Apply previously copied overrides to selected component instances. Target instances will be swapped to the source component and all copied override properties will be applied.",{sourceInstanceId:r.z.string().describe("ID of the source component instance"),targetNodeIds:r.z.array(r.z.string()).describe("Array of target instance IDs. Currently selected instances will be used.")},async({sourceInstanceId:e,targetNodeIds:t})=>{try{let n=await l("set_instance_overrides",{sourceInstanceId:e,targetNodeIds:t||[]});if(n.success){let a=n.results?.filter(s=>s.success).length||0;return{content:[{type:"text",text:`Successfully applied ${n.totalCount||0} overrides to ${a} instances.`}]}}else return{content:[{type:"text",text:`Failed to set instance overrides: ${n.message}`}]}}catch(o){return{content:[{type:"text",text:`Error setting instance overrides: ${o instanceof Error?o.message:String(o)}`}]}}});c.tool("set_corner_radius","Set the corner radius of a node in Figma",{nodeId:r.z.string().describe("The ID of the node to modify"),radius:r.z.number().min(0).describe("Corner radius value"),corners:r.z.array(r.z.boolean()).length(4).optional().describe("Optional array of 4 booleans to specify which corners to round [topLeft, topRight, bottomRight, bottomLeft]")},async({nodeId:e,radius:t,corners:o})=>{try{return{content:[{type:"text",text:`Set corner radius of node "${(await l("set_corner_radius",{nodeId:e,radius:t,corners:o||[!0,!0,!0,!0]})).name}" to ${t}px`}]}}catch(n){return{content:[{type:"text",text:`Error setting corner radius: ${n instanceof Error?n.message:String(n)}`}]}}});c.prompt("design_strategy","Best practices for working with Figma designs",e=>({messages:[{role:"assistant",content:{type:"text",text:`When working with Figma designs, follow these best practices:
|
|
1805
22
|
|
|
1806
23
|
1. Start with Document Structure:
|
|
1807
24
|
- First use get_document_info() to understand the current document
|
|
@@ -1869,164 +86,16 @@ Example Login Screen Structure:
|
|
|
1869
86
|
- Button Text (text)
|
|
1870
87
|
- Helper Links (frame)
|
|
1871
88
|
- Forgot Password (text)
|
|
1872
|
-
- Don't have account (text)`
|
|
1873
|
-
}
|
|
1874
|
-
}
|
|
1875
|
-
],
|
|
1876
|
-
description: "Best practices for working with Figma designs"
|
|
1877
|
-
};
|
|
1878
|
-
}
|
|
1879
|
-
);
|
|
1880
|
-
server.prompt(
|
|
1881
|
-
"read_design_strategy",
|
|
1882
|
-
"Best practices for reading Figma designs",
|
|
1883
|
-
(extra) => {
|
|
1884
|
-
return {
|
|
1885
|
-
messages: [
|
|
1886
|
-
{
|
|
1887
|
-
role: "assistant",
|
|
1888
|
-
content: {
|
|
1889
|
-
type: "text",
|
|
1890
|
-
text: `When reading Figma designs, follow these best practices:
|
|
89
|
+
- Don't have account (text)`}}],description:"Best practices for working with Figma designs"}));c.prompt("read_design_strategy","Best practices for reading Figma designs",e=>({messages:[{role:"assistant",content:{type:"text",text:`When reading Figma designs, follow these best practices:
|
|
1891
90
|
|
|
1892
91
|
1. Start with selection:
|
|
1893
92
|
- First use read_my_design() to understand the current selection
|
|
1894
93
|
- If no selection ask user to select single or multiple nodes
|
|
1895
|
-
`
|
|
1896
|
-
}
|
|
1897
|
-
}
|
|
1898
|
-
],
|
|
1899
|
-
description: "Best practices for reading Figma designs"
|
|
1900
|
-
};
|
|
1901
|
-
}
|
|
1902
|
-
);
|
|
1903
|
-
server.tool(
|
|
1904
|
-
"scan_text_nodes",
|
|
1905
|
-
"Scan all text nodes in the selected Figma node",
|
|
1906
|
-
{
|
|
1907
|
-
nodeId: import_zod.z.string().describe("ID of the node to scan")
|
|
1908
|
-
},
|
|
1909
|
-
async ({ nodeId }) => {
|
|
1910
|
-
try {
|
|
1911
|
-
const initialStatus = {
|
|
1912
|
-
type: "text",
|
|
1913
|
-
text: "Starting text node scanning. This may take a moment for large designs..."
|
|
1914
|
-
};
|
|
1915
|
-
const result = await sendCommandToFigma("scan_text_nodes", {
|
|
1916
|
-
nodeId,
|
|
1917
|
-
useChunking: true,
|
|
1918
|
-
// Enable chunking on the plugin side
|
|
1919
|
-
chunkSize: 10
|
|
1920
|
-
// Process 10 nodes at a time
|
|
1921
|
-
});
|
|
1922
|
-
if (result && typeof result === "object" && "chunks" in result) {
|
|
1923
|
-
const typedResult = result;
|
|
1924
|
-
const summaryText = `
|
|
94
|
+
`}}],description:"Best practices for reading Figma designs"}));c.tool("scan_text_nodes","Scan all text nodes in the selected Figma node",{nodeId:r.z.string().describe("ID of the node to scan")},async({nodeId:e})=>{try{let t={type:"text",text:"Starting text node scanning. This may take a moment for large designs..."},o=await l("scan_text_nodes",{nodeId:e,useChunking:!0,chunkSize:10});if(o&&typeof o=="object"&&"chunks"in o){let n=o,a=`
|
|
1925
95
|
Scan completed:
|
|
1926
|
-
- Found ${
|
|
1927
|
-
- Processed in ${
|
|
1928
|
-
`;
|
|
1929
|
-
return {
|
|
1930
|
-
content: [
|
|
1931
|
-
initialStatus,
|
|
1932
|
-
{
|
|
1933
|
-
type: "text",
|
|
1934
|
-
text: summaryText
|
|
1935
|
-
},
|
|
1936
|
-
{
|
|
1937
|
-
type: "text",
|
|
1938
|
-
text: JSON.stringify(typedResult.textNodes, null, 2)
|
|
1939
|
-
}
|
|
1940
|
-
]
|
|
1941
|
-
};
|
|
1942
|
-
}
|
|
1943
|
-
return {
|
|
1944
|
-
content: [
|
|
1945
|
-
initialStatus,
|
|
1946
|
-
{
|
|
1947
|
-
type: "text",
|
|
1948
|
-
text: JSON.stringify(result, null, 2)
|
|
1949
|
-
}
|
|
1950
|
-
]
|
|
1951
|
-
};
|
|
1952
|
-
} catch (error) {
|
|
1953
|
-
return {
|
|
1954
|
-
content: [
|
|
1955
|
-
{
|
|
1956
|
-
type: "text",
|
|
1957
|
-
text: `Error scanning text nodes: ${error instanceof Error ? error.message : String(error)}`
|
|
1958
|
-
}
|
|
1959
|
-
]
|
|
1960
|
-
};
|
|
1961
|
-
}
|
|
1962
|
-
}
|
|
1963
|
-
);
|
|
1964
|
-
server.tool(
|
|
1965
|
-
"scan_nodes_by_types",
|
|
1966
|
-
"Scan for child nodes with specific types in the selected Figma node",
|
|
1967
|
-
{
|
|
1968
|
-
nodeId: import_zod.z.string().describe("ID of the node to scan"),
|
|
1969
|
-
types: import_zod.z.array(import_zod.z.string()).describe("Array of node types to find in the child nodes (e.g. ['COMPONENT', 'FRAME'])")
|
|
1970
|
-
},
|
|
1971
|
-
async ({ nodeId, types }) => {
|
|
1972
|
-
try {
|
|
1973
|
-
const initialStatus = {
|
|
1974
|
-
type: "text",
|
|
1975
|
-
text: `Starting node type scanning for types: ${types.join(", ")}...`
|
|
1976
|
-
};
|
|
1977
|
-
const result = await sendCommandToFigma("scan_nodes_by_types", {
|
|
1978
|
-
nodeId,
|
|
1979
|
-
types
|
|
1980
|
-
});
|
|
1981
|
-
if (result && typeof result === "object" && "matchingNodes" in result) {
|
|
1982
|
-
const typedResult = result;
|
|
1983
|
-
const summaryText = `Scan completed: Found ${typedResult.count} nodes matching types: ${typedResult.searchedTypes.join(", ")}`;
|
|
1984
|
-
return {
|
|
1985
|
-
content: [
|
|
1986
|
-
initialStatus,
|
|
1987
|
-
{
|
|
1988
|
-
type: "text",
|
|
1989
|
-
text: summaryText
|
|
1990
|
-
},
|
|
1991
|
-
{
|
|
1992
|
-
type: "text",
|
|
1993
|
-
text: JSON.stringify(typedResult.matchingNodes, null, 2)
|
|
1994
|
-
}
|
|
1995
|
-
]
|
|
1996
|
-
};
|
|
1997
|
-
}
|
|
1998
|
-
return {
|
|
1999
|
-
content: [
|
|
2000
|
-
initialStatus,
|
|
2001
|
-
{
|
|
2002
|
-
type: "text",
|
|
2003
|
-
text: JSON.stringify(result, null, 2)
|
|
2004
|
-
}
|
|
2005
|
-
]
|
|
2006
|
-
};
|
|
2007
|
-
} catch (error) {
|
|
2008
|
-
return {
|
|
2009
|
-
content: [
|
|
2010
|
-
{
|
|
2011
|
-
type: "text",
|
|
2012
|
-
text: `Error scanning nodes by types: ${error instanceof Error ? error.message : String(error)}`
|
|
2013
|
-
}
|
|
2014
|
-
]
|
|
2015
|
-
};
|
|
2016
|
-
}
|
|
2017
|
-
}
|
|
2018
|
-
);
|
|
2019
|
-
server.prompt(
|
|
2020
|
-
"text_replacement_strategy",
|
|
2021
|
-
"Systematic approach for replacing text in Figma designs",
|
|
2022
|
-
(extra) => {
|
|
2023
|
-
return {
|
|
2024
|
-
messages: [
|
|
2025
|
-
{
|
|
2026
|
-
role: "assistant",
|
|
2027
|
-
content: {
|
|
2028
|
-
type: "text",
|
|
2029
|
-
text: `# Intelligent Text Replacement Strategy
|
|
96
|
+
- Found ${n.totalNodes} text nodes
|
|
97
|
+
- Processed in ${n.chunks} chunks
|
|
98
|
+
`;return{content:[t,{type:"text",text:a},{type:"text",text:JSON.stringify(n.textNodes,null,2)}]}}return{content:[t,{type:"text",text:JSON.stringify(o,null,2)}]}}catch(t){return{content:[{type:"text",text:`Error scanning text nodes: ${t instanceof Error?t.message:String(t)}`}]}}});c.tool("scan_nodes_by_types","Scan for child nodes with specific types in the selected Figma node",{nodeId:r.z.string().describe("ID of the node to scan"),types:r.z.array(r.z.string()).describe("Array of node types to find in the child nodes (e.g. ['COMPONENT', 'FRAME'])")},async({nodeId:e,types:t})=>{try{let o={type:"text",text:`Starting node type scanning for types: ${t.join(", ")}...`},n=await l("scan_nodes_by_types",{nodeId:e,types:t});if(n&&typeof n=="object"&&"matchingNodes"in n){let a=n,s=`Scan completed: Found ${a.count} nodes matching types: ${a.searchedTypes.join(", ")}`;return{content:[o,{type:"text",text:s},{type:"text",text:JSON.stringify(a.matchingNodes,null,2)}]}}return{content:[o,{type:"text",text:JSON.stringify(n,null,2)}]}}catch(o){return{content:[{type:"text",text:`Error scanning nodes by types: ${o instanceof Error?o.message:String(o)}`}]}}});c.prompt("text_replacement_strategy","Systematic approach for replacing text in Figma designs",e=>({messages:[{role:"assistant",content:{type:"text",text:`# Intelligent Text Replacement Strategy
|
|
2030
99
|
|
|
2031
100
|
## 1. Analyze Design & Identify Structure
|
|
2032
101
|
- Scan text nodes to understand the overall structure of the design
|
|
@@ -2140,99 +209,16 @@ export_node_as_image(nodeId: "chunk-node-id", format: "PNG", scale: 0.5)
|
|
|
2140
209
|
- **Balance Automation & Control**: Let AI handle repetitive replacements but maintain oversight
|
|
2141
210
|
- **Respect Content Relationships**: Keep related content consistent across chunks
|
|
2142
211
|
|
|
2143
|
-
Remember that text is never just text\u2014it's a core design element that must work harmoniously with the overall composition. This chunk-based strategy allows you to methodically transform text while maintaining design integrity.`
|
|
2144
|
-
}
|
|
2145
|
-
}
|
|
2146
|
-
],
|
|
2147
|
-
description: "Systematic approach for replacing text in Figma designs"
|
|
2148
|
-
};
|
|
2149
|
-
}
|
|
2150
|
-
);
|
|
2151
|
-
server.tool(
|
|
2152
|
-
"set_multiple_text_contents",
|
|
2153
|
-
"Set multiple text contents parallelly in a node",
|
|
2154
|
-
{
|
|
2155
|
-
nodeId: import_zod.z.string().describe("The ID of the node containing the text nodes to replace"),
|
|
2156
|
-
text: import_zod.z.array(
|
|
2157
|
-
import_zod.z.object({
|
|
2158
|
-
nodeId: import_zod.z.string().describe("The ID of the text node"),
|
|
2159
|
-
text: import_zod.z.string().describe("The replacement text")
|
|
2160
|
-
})
|
|
2161
|
-
).describe("Array of text node IDs and their replacement texts")
|
|
2162
|
-
},
|
|
2163
|
-
async ({ nodeId, text }) => {
|
|
2164
|
-
try {
|
|
2165
|
-
if (!text || text.length === 0) {
|
|
2166
|
-
return {
|
|
2167
|
-
content: [
|
|
2168
|
-
{
|
|
2169
|
-
type: "text",
|
|
2170
|
-
text: "No text provided"
|
|
2171
|
-
}
|
|
2172
|
-
]
|
|
2173
|
-
};
|
|
2174
|
-
}
|
|
2175
|
-
const initialStatus = {
|
|
2176
|
-
type: "text",
|
|
2177
|
-
text: `Starting text replacement for ${text.length} nodes. This will be processed in batches of 5...`
|
|
2178
|
-
};
|
|
2179
|
-
let totalProcessed = 0;
|
|
2180
|
-
const totalToProcess = text.length;
|
|
2181
|
-
const result = await sendCommandToFigma("set_multiple_text_contents", {
|
|
2182
|
-
nodeId,
|
|
2183
|
-
text
|
|
2184
|
-
});
|
|
2185
|
-
const typedResult = result;
|
|
2186
|
-
const success = typedResult.replacementsApplied && typedResult.replacementsApplied > 0;
|
|
2187
|
-
const progressText = `
|
|
212
|
+
Remember that text is never just text\u2014it's a core design element that must work harmoniously with the overall composition. This chunk-based strategy allows you to methodically transform text while maintaining design integrity.`}}],description:"Systematic approach for replacing text in Figma designs"}));c.tool("set_multiple_text_contents","Set multiple text contents parallelly in a node",{nodeId:r.z.string().describe("The ID of the node containing the text nodes to replace"),text:r.z.array(r.z.object({nodeId:r.z.string().describe("The ID of the text node"),text:r.z.string().describe("The replacement text")})).describe("Array of text node IDs and their replacement texts")},async({nodeId:e,text:t})=>{try{if(!t||t.length===0)return{content:[{type:"text",text:"No text provided"}]};let o={type:"text",text:`Starting text replacement for ${t.length} nodes. This will be processed in batches of 5...`},n=0,a=t.length,i=await l("set_multiple_text_contents",{nodeId:e,text:t}),d=i.replacementsApplied&&i.replacementsApplied>0,m=`
|
|
2188
213
|
Text replacement completed:
|
|
2189
|
-
- ${
|
|
2190
|
-
- ${
|
|
2191
|
-
- Processed in ${
|
|
2192
|
-
|
|
2193
|
-
const detailedResults = typedResult.results || [];
|
|
2194
|
-
const failedResults = detailedResults.filter((item) => !item.success);
|
|
2195
|
-
let detailedResponse = "";
|
|
2196
|
-
if (failedResults.length > 0) {
|
|
2197
|
-
detailedResponse = `
|
|
214
|
+
- ${i.replacementsApplied||0} of ${a} successfully updated
|
|
215
|
+
- ${i.replacementsFailed||0} failed
|
|
216
|
+
- Processed in ${i.completedInChunks||1} batches
|
|
217
|
+
`,h=(i.results||[]).filter(y=>!y.success),b="";return h.length>0&&(b=`
|
|
2198
218
|
|
|
2199
219
|
Nodes that failed:
|
|
2200
|
-
${
|
|
2201
|
-
|
|
2202
|
-
).join("\n")}`;
|
|
2203
|
-
}
|
|
2204
|
-
return {
|
|
2205
|
-
content: [
|
|
2206
|
-
initialStatus,
|
|
2207
|
-
{
|
|
2208
|
-
type: "text",
|
|
2209
|
-
text: progressText + detailedResponse
|
|
2210
|
-
}
|
|
2211
|
-
]
|
|
2212
|
-
};
|
|
2213
|
-
} catch (error) {
|
|
2214
|
-
return {
|
|
2215
|
-
content: [
|
|
2216
|
-
{
|
|
2217
|
-
type: "text",
|
|
2218
|
-
text: `Error setting multiple text contents: ${error instanceof Error ? error.message : String(error)}`
|
|
2219
|
-
}
|
|
2220
|
-
]
|
|
2221
|
-
};
|
|
2222
|
-
}
|
|
2223
|
-
}
|
|
2224
|
-
);
|
|
2225
|
-
server.prompt(
|
|
2226
|
-
"annotation_conversion_strategy",
|
|
2227
|
-
"Strategy for converting manual annotations to Figma's native annotations",
|
|
2228
|
-
(extra) => {
|
|
2229
|
-
return {
|
|
2230
|
-
messages: [
|
|
2231
|
-
{
|
|
2232
|
-
role: "assistant",
|
|
2233
|
-
content: {
|
|
2234
|
-
type: "text",
|
|
2235
|
-
text: `# Automatic Annotation Conversion
|
|
220
|
+
${h.map(y=>`- ${y.nodeId}: ${y.error||"Unknown error"}`).join(`
|
|
221
|
+
`)}`),{content:[o,{type:"text",text:m+b}]}}catch(o){return{content:[{type:"text",text:`Error setting multiple text contents: ${o instanceof Error?o.message:String(o)}`}]}}});c.prompt("annotation_conversion_strategy","Strategy for converting manual annotations to Figma's native annotations",e=>({messages:[{role:"assistant",content:{type:"text",text:`# Automatic Annotation Conversion
|
|
2236
222
|
|
|
2237
223
|
## Process Overview
|
|
2238
224
|
|
|
@@ -2369,25 +355,7 @@ if (annotationsToApply.length > 0) {
|
|
|
2369
355
|
\`\`\`
|
|
2370
356
|
|
|
2371
357
|
|
|
2372
|
-
This strategy focuses on practical implementation based on real-world usage patterns, emphasizing the importance of handling various UI elements as annotation targets, not just text nodes.`
|
|
2373
|
-
}
|
|
2374
|
-
}
|
|
2375
|
-
],
|
|
2376
|
-
description: "Strategy for converting manual annotations to Figma's native annotations"
|
|
2377
|
-
};
|
|
2378
|
-
}
|
|
2379
|
-
);
|
|
2380
|
-
server.prompt(
|
|
2381
|
-
"swap_overrides_instances",
|
|
2382
|
-
"Guide to swap instance overrides between instances",
|
|
2383
|
-
(extra) => {
|
|
2384
|
-
return {
|
|
2385
|
-
messages: [
|
|
2386
|
-
{
|
|
2387
|
-
role: "assistant",
|
|
2388
|
-
content: {
|
|
2389
|
-
type: "text",
|
|
2390
|
-
text: `# Swap Component Instance and Override Strategy
|
|
358
|
+
This strategy focuses on practical implementation based on real-world usage patterns, emphasizing the importance of handling various UI elements as annotation targets, not just text nodes.`}}],description:"Strategy for converting manual annotations to Figma's native annotations"}));c.prompt("swap_overrides_instances","Guide to swap instance overrides between instances",e=>({messages:[{role:"assistant",content:{type:"text",text:`# Swap Component Instance and Override Strategy
|
|
2391
359
|
|
|
2392
360
|
## Overview
|
|
2393
361
|
This strategy enables transferring content and property overrides from a source instance to one or more target instances in Figma, maintaining design consistency while reducing manual work.
|
|
@@ -2423,523 +391,4 @@ This strategy enables transferring content and property overrides from a source
|
|
|
2423
391
|
## Key Tips
|
|
2424
392
|
- Always join the appropriate channel first with \`join_channel()\`
|
|
2425
393
|
- When working with multiple targets, check the full selection with \`get_selection()\`
|
|
2426
|
-
- Preserve component relationships by using instance overrides rather than direct text manipulation`
|
|
2427
|
-
}
|
|
2428
|
-
}
|
|
2429
|
-
],
|
|
2430
|
-
description: "Strategy for transferring overrides between component instances in Figma"
|
|
2431
|
-
};
|
|
2432
|
-
}
|
|
2433
|
-
);
|
|
2434
|
-
server.tool(
|
|
2435
|
-
"set_layout_mode",
|
|
2436
|
-
"Set the layout mode and wrap behavior of a frame in Figma",
|
|
2437
|
-
{
|
|
2438
|
-
nodeId: import_zod.z.string().describe("The ID of the frame to modify"),
|
|
2439
|
-
layoutMode: import_zod.z.enum(["NONE", "HORIZONTAL", "VERTICAL"]).describe("Layout mode for the frame"),
|
|
2440
|
-
layoutWrap: import_zod.z.enum(["NO_WRAP", "WRAP"]).optional().describe("Whether the auto-layout frame wraps its children")
|
|
2441
|
-
},
|
|
2442
|
-
async ({ nodeId, layoutMode, layoutWrap }) => {
|
|
2443
|
-
try {
|
|
2444
|
-
const result = await sendCommandToFigma("set_layout_mode", {
|
|
2445
|
-
nodeId,
|
|
2446
|
-
layoutMode,
|
|
2447
|
-
layoutWrap: layoutWrap || "NO_WRAP"
|
|
2448
|
-
});
|
|
2449
|
-
const typedResult = result;
|
|
2450
|
-
return {
|
|
2451
|
-
content: [
|
|
2452
|
-
{
|
|
2453
|
-
type: "text",
|
|
2454
|
-
text: `Set layout mode of frame "${typedResult.name}" to ${layoutMode}${layoutWrap ? ` with ${layoutWrap}` : ""}`
|
|
2455
|
-
}
|
|
2456
|
-
]
|
|
2457
|
-
};
|
|
2458
|
-
} catch (error) {
|
|
2459
|
-
return {
|
|
2460
|
-
content: [
|
|
2461
|
-
{
|
|
2462
|
-
type: "text",
|
|
2463
|
-
text: `Error setting layout mode: ${error instanceof Error ? error.message : String(error)}`
|
|
2464
|
-
}
|
|
2465
|
-
]
|
|
2466
|
-
};
|
|
2467
|
-
}
|
|
2468
|
-
}
|
|
2469
|
-
);
|
|
2470
|
-
server.tool(
|
|
2471
|
-
"set_padding",
|
|
2472
|
-
"Set padding values for an auto-layout frame in Figma",
|
|
2473
|
-
{
|
|
2474
|
-
nodeId: import_zod.z.string().describe("The ID of the frame to modify"),
|
|
2475
|
-
paddingTop: import_zod.z.number().optional().describe("Top padding value"),
|
|
2476
|
-
paddingRight: import_zod.z.number().optional().describe("Right padding value"),
|
|
2477
|
-
paddingBottom: import_zod.z.number().optional().describe("Bottom padding value"),
|
|
2478
|
-
paddingLeft: import_zod.z.number().optional().describe("Left padding value")
|
|
2479
|
-
},
|
|
2480
|
-
async ({ nodeId, paddingTop, paddingRight, paddingBottom, paddingLeft }) => {
|
|
2481
|
-
try {
|
|
2482
|
-
const result = await sendCommandToFigma("set_padding", {
|
|
2483
|
-
nodeId,
|
|
2484
|
-
paddingTop,
|
|
2485
|
-
paddingRight,
|
|
2486
|
-
paddingBottom,
|
|
2487
|
-
paddingLeft
|
|
2488
|
-
});
|
|
2489
|
-
const typedResult = result;
|
|
2490
|
-
const paddingMessages = [];
|
|
2491
|
-
if (paddingTop !== void 0) paddingMessages.push(`top: ${paddingTop}`);
|
|
2492
|
-
if (paddingRight !== void 0) paddingMessages.push(`right: ${paddingRight}`);
|
|
2493
|
-
if (paddingBottom !== void 0) paddingMessages.push(`bottom: ${paddingBottom}`);
|
|
2494
|
-
if (paddingLeft !== void 0) paddingMessages.push(`left: ${paddingLeft}`);
|
|
2495
|
-
const paddingText = paddingMessages.length > 0 ? `padding (${paddingMessages.join(", ")})` : "padding";
|
|
2496
|
-
return {
|
|
2497
|
-
content: [
|
|
2498
|
-
{
|
|
2499
|
-
type: "text",
|
|
2500
|
-
text: `Set ${paddingText} for frame "${typedResult.name}"`
|
|
2501
|
-
}
|
|
2502
|
-
]
|
|
2503
|
-
};
|
|
2504
|
-
} catch (error) {
|
|
2505
|
-
return {
|
|
2506
|
-
content: [
|
|
2507
|
-
{
|
|
2508
|
-
type: "text",
|
|
2509
|
-
text: `Error setting padding: ${error instanceof Error ? error.message : String(error)}`
|
|
2510
|
-
}
|
|
2511
|
-
]
|
|
2512
|
-
};
|
|
2513
|
-
}
|
|
2514
|
-
}
|
|
2515
|
-
);
|
|
2516
|
-
server.tool(
|
|
2517
|
-
"set_axis_align",
|
|
2518
|
-
"Set primary and counter axis alignment for an auto-layout frame in Figma",
|
|
2519
|
-
{
|
|
2520
|
-
nodeId: import_zod.z.string().describe("The ID of the frame to modify"),
|
|
2521
|
-
primaryAxisAlignItems: import_zod.z.enum(["MIN", "MAX", "CENTER", "SPACE_BETWEEN"]).optional().describe("Primary axis alignment (MIN/MAX = left/right in horizontal, top/bottom in vertical). Note: When set to SPACE_BETWEEN, itemSpacing will be ignored as children will be evenly spaced."),
|
|
2522
|
-
counterAxisAlignItems: import_zod.z.enum(["MIN", "MAX", "CENTER", "BASELINE"]).optional().describe("Counter axis alignment (MIN/MAX = top/bottom in horizontal, left/right in vertical)")
|
|
2523
|
-
},
|
|
2524
|
-
async ({ nodeId, primaryAxisAlignItems, counterAxisAlignItems }) => {
|
|
2525
|
-
try {
|
|
2526
|
-
const result = await sendCommandToFigma("set_axis_align", {
|
|
2527
|
-
nodeId,
|
|
2528
|
-
primaryAxisAlignItems,
|
|
2529
|
-
counterAxisAlignItems
|
|
2530
|
-
});
|
|
2531
|
-
const typedResult = result;
|
|
2532
|
-
const alignMessages = [];
|
|
2533
|
-
if (primaryAxisAlignItems !== void 0) alignMessages.push(`primary: ${primaryAxisAlignItems}`);
|
|
2534
|
-
if (counterAxisAlignItems !== void 0) alignMessages.push(`counter: ${counterAxisAlignItems}`);
|
|
2535
|
-
const alignText = alignMessages.length > 0 ? `axis alignment (${alignMessages.join(", ")})` : "axis alignment";
|
|
2536
|
-
return {
|
|
2537
|
-
content: [
|
|
2538
|
-
{
|
|
2539
|
-
type: "text",
|
|
2540
|
-
text: `Set ${alignText} for frame "${typedResult.name}"`
|
|
2541
|
-
}
|
|
2542
|
-
]
|
|
2543
|
-
};
|
|
2544
|
-
} catch (error) {
|
|
2545
|
-
return {
|
|
2546
|
-
content: [
|
|
2547
|
-
{
|
|
2548
|
-
type: "text",
|
|
2549
|
-
text: `Error setting axis alignment: ${error instanceof Error ? error.message : String(error)}`
|
|
2550
|
-
}
|
|
2551
|
-
]
|
|
2552
|
-
};
|
|
2553
|
-
}
|
|
2554
|
-
}
|
|
2555
|
-
);
|
|
2556
|
-
server.tool(
|
|
2557
|
-
"set_layout_sizing",
|
|
2558
|
-
"Set horizontal and vertical sizing modes for an auto-layout frame in Figma",
|
|
2559
|
-
{
|
|
2560
|
-
nodeId: import_zod.z.string().describe("The ID of the frame to modify"),
|
|
2561
|
-
layoutSizingHorizontal: import_zod.z.enum(["FIXED", "HUG", "FILL"]).optional().describe("Horizontal sizing mode (HUG for frames/text only, FILL for auto-layout children only)"),
|
|
2562
|
-
layoutSizingVertical: import_zod.z.enum(["FIXED", "HUG", "FILL"]).optional().describe("Vertical sizing mode (HUG for frames/text only, FILL for auto-layout children only)")
|
|
2563
|
-
},
|
|
2564
|
-
async ({ nodeId, layoutSizingHorizontal, layoutSizingVertical }) => {
|
|
2565
|
-
try {
|
|
2566
|
-
const result = await sendCommandToFigma("set_layout_sizing", {
|
|
2567
|
-
nodeId,
|
|
2568
|
-
layoutSizingHorizontal,
|
|
2569
|
-
layoutSizingVertical
|
|
2570
|
-
});
|
|
2571
|
-
const typedResult = result;
|
|
2572
|
-
const sizingMessages = [];
|
|
2573
|
-
if (layoutSizingHorizontal !== void 0) sizingMessages.push(`horizontal: ${layoutSizingHorizontal}`);
|
|
2574
|
-
if (layoutSizingVertical !== void 0) sizingMessages.push(`vertical: ${layoutSizingVertical}`);
|
|
2575
|
-
const sizingText = sizingMessages.length > 0 ? `layout sizing (${sizingMessages.join(", ")})` : "layout sizing";
|
|
2576
|
-
return {
|
|
2577
|
-
content: [
|
|
2578
|
-
{
|
|
2579
|
-
type: "text",
|
|
2580
|
-
text: `Set ${sizingText} for frame "${typedResult.name}"`
|
|
2581
|
-
}
|
|
2582
|
-
]
|
|
2583
|
-
};
|
|
2584
|
-
} catch (error) {
|
|
2585
|
-
return {
|
|
2586
|
-
content: [
|
|
2587
|
-
{
|
|
2588
|
-
type: "text",
|
|
2589
|
-
text: `Error setting layout sizing: ${error instanceof Error ? error.message : String(error)}`
|
|
2590
|
-
}
|
|
2591
|
-
]
|
|
2592
|
-
};
|
|
2593
|
-
}
|
|
2594
|
-
}
|
|
2595
|
-
);
|
|
2596
|
-
server.tool(
|
|
2597
|
-
"set_item_spacing",
|
|
2598
|
-
"Set distance between children in an auto-layout frame",
|
|
2599
|
-
{
|
|
2600
|
-
nodeId: import_zod.z.string().describe("The ID of the frame to modify"),
|
|
2601
|
-
itemSpacing: import_zod.z.number().optional().describe("Distance between children. Note: This value will be ignored if primaryAxisAlignItems is set to SPACE_BETWEEN."),
|
|
2602
|
-
counterAxisSpacing: import_zod.z.number().optional().describe("Distance between wrapped rows/columns. Only works when layoutWrap is set to WRAP.")
|
|
2603
|
-
},
|
|
2604
|
-
async ({ nodeId, itemSpacing, counterAxisSpacing }) => {
|
|
2605
|
-
try {
|
|
2606
|
-
const params = { nodeId };
|
|
2607
|
-
if (itemSpacing !== void 0) params.itemSpacing = itemSpacing;
|
|
2608
|
-
if (counterAxisSpacing !== void 0) params.counterAxisSpacing = counterAxisSpacing;
|
|
2609
|
-
const result = await sendCommandToFigma("set_item_spacing", params);
|
|
2610
|
-
const typedResult = result;
|
|
2611
|
-
let message = `Updated spacing for frame "${typedResult.name}":`;
|
|
2612
|
-
if (itemSpacing !== void 0) message += ` itemSpacing=${itemSpacing}`;
|
|
2613
|
-
if (counterAxisSpacing !== void 0) message += ` counterAxisSpacing=${counterAxisSpacing}`;
|
|
2614
|
-
return {
|
|
2615
|
-
content: [
|
|
2616
|
-
{
|
|
2617
|
-
type: "text",
|
|
2618
|
-
text: message
|
|
2619
|
-
}
|
|
2620
|
-
]
|
|
2621
|
-
};
|
|
2622
|
-
} catch (error) {
|
|
2623
|
-
return {
|
|
2624
|
-
content: [
|
|
2625
|
-
{
|
|
2626
|
-
type: "text",
|
|
2627
|
-
text: `Error setting spacing: ${error instanceof Error ? error.message : String(error)}`
|
|
2628
|
-
}
|
|
2629
|
-
]
|
|
2630
|
-
};
|
|
2631
|
-
}
|
|
2632
|
-
}
|
|
2633
|
-
);
|
|
2634
|
-
server.tool(
|
|
2635
|
-
"get_reactions",
|
|
2636
|
-
"Get Figma Prototyping Reactions from multiple nodes. CRITICAL: The output MUST be processed using the 'reaction_to_connector_strategy' prompt IMMEDIATELY to generate parameters for connector lines via the 'create_connections' tool.",
|
|
2637
|
-
{
|
|
2638
|
-
nodeIds: import_zod.z.array(import_zod.z.string()).describe("Array of node IDs to get reactions from")
|
|
2639
|
-
},
|
|
2640
|
-
async ({ nodeIds }) => {
|
|
2641
|
-
try {
|
|
2642
|
-
const result = await sendCommandToFigma("get_reactions", { nodeIds });
|
|
2643
|
-
return {
|
|
2644
|
-
content: [
|
|
2645
|
-
{
|
|
2646
|
-
type: "text",
|
|
2647
|
-
text: JSON.stringify(result)
|
|
2648
|
-
},
|
|
2649
|
-
{
|
|
2650
|
-
type: "text",
|
|
2651
|
-
text: "IMPORTANT: You MUST now use the reaction data above and follow the `reaction_to_connector_strategy` prompt to prepare the parameters for the `create_connections` tool call. This is a required next step."
|
|
2652
|
-
}
|
|
2653
|
-
],
|
|
2654
|
-
followUp: {
|
|
2655
|
-
type: "prompt",
|
|
2656
|
-
prompt: "reaction_to_connector_strategy"
|
|
2657
|
-
}
|
|
2658
|
-
};
|
|
2659
|
-
} catch (error) {
|
|
2660
|
-
return {
|
|
2661
|
-
content: [
|
|
2662
|
-
{
|
|
2663
|
-
type: "text",
|
|
2664
|
-
text: `Error getting reactions: ${error instanceof Error ? error.message : String(error)}`
|
|
2665
|
-
}
|
|
2666
|
-
]
|
|
2667
|
-
};
|
|
2668
|
-
}
|
|
2669
|
-
}
|
|
2670
|
-
);
|
|
2671
|
-
server.tool(
|
|
2672
|
-
"set_default_connector",
|
|
2673
|
-
"Set a copied connector node as the default connector",
|
|
2674
|
-
{
|
|
2675
|
-
connectorId: import_zod.z.string().optional().describe("The ID of the connector node to set as default")
|
|
2676
|
-
},
|
|
2677
|
-
async ({ connectorId }) => {
|
|
2678
|
-
try {
|
|
2679
|
-
const result = await sendCommandToFigma("set_default_connector", {
|
|
2680
|
-
connectorId
|
|
2681
|
-
});
|
|
2682
|
-
return {
|
|
2683
|
-
content: [
|
|
2684
|
-
{
|
|
2685
|
-
type: "text",
|
|
2686
|
-
text: `Default connector set: ${JSON.stringify(result)}`
|
|
2687
|
-
}
|
|
2688
|
-
]
|
|
2689
|
-
};
|
|
2690
|
-
} catch (error) {
|
|
2691
|
-
return {
|
|
2692
|
-
content: [
|
|
2693
|
-
{
|
|
2694
|
-
type: "text",
|
|
2695
|
-
text: `Error setting default connector: ${error instanceof Error ? error.message : String(error)}`
|
|
2696
|
-
}
|
|
2697
|
-
]
|
|
2698
|
-
};
|
|
2699
|
-
}
|
|
2700
|
-
}
|
|
2701
|
-
);
|
|
2702
|
-
server.tool(
|
|
2703
|
-
"create_connections",
|
|
2704
|
-
"Create connections between nodes using the default connector style",
|
|
2705
|
-
{
|
|
2706
|
-
connections: import_zod.z.array(import_zod.z.object({
|
|
2707
|
-
startNodeId: import_zod.z.string().describe("ID of the starting node"),
|
|
2708
|
-
endNodeId: import_zod.z.string().describe("ID of the ending node"),
|
|
2709
|
-
text: import_zod.z.string().optional().describe("Optional text to display on the connector")
|
|
2710
|
-
})).describe("Array of node connections to create")
|
|
2711
|
-
},
|
|
2712
|
-
async ({ connections }) => {
|
|
2713
|
-
try {
|
|
2714
|
-
if (!connections || connections.length === 0) {
|
|
2715
|
-
return {
|
|
2716
|
-
content: [
|
|
2717
|
-
{
|
|
2718
|
-
type: "text",
|
|
2719
|
-
text: "No connections provided"
|
|
2720
|
-
}
|
|
2721
|
-
]
|
|
2722
|
-
};
|
|
2723
|
-
}
|
|
2724
|
-
const result = await sendCommandToFigma("create_connections", {
|
|
2725
|
-
connections
|
|
2726
|
-
});
|
|
2727
|
-
return {
|
|
2728
|
-
content: [
|
|
2729
|
-
{
|
|
2730
|
-
type: "text",
|
|
2731
|
-
text: `Created ${connections.length} connections: ${JSON.stringify(result)}`
|
|
2732
|
-
}
|
|
2733
|
-
]
|
|
2734
|
-
};
|
|
2735
|
-
} catch (error) {
|
|
2736
|
-
return {
|
|
2737
|
-
content: [
|
|
2738
|
-
{
|
|
2739
|
-
type: "text",
|
|
2740
|
-
text: `Error creating connections: ${error instanceof Error ? error.message : String(error)}`
|
|
2741
|
-
}
|
|
2742
|
-
]
|
|
2743
|
-
};
|
|
2744
|
-
}
|
|
2745
|
-
}
|
|
2746
|
-
);
|
|
2747
|
-
server.tool(
|
|
2748
|
-
"set_focus",
|
|
2749
|
-
"Set focus on a specific node in Figma by selecting it and scrolling viewport to it",
|
|
2750
|
-
{
|
|
2751
|
-
nodeId: import_zod.z.string().describe("The ID of the node to focus on")
|
|
2752
|
-
},
|
|
2753
|
-
async ({ nodeId }) => {
|
|
2754
|
-
try {
|
|
2755
|
-
const result = await sendCommandToFigma("set_focus", { nodeId });
|
|
2756
|
-
const typedResult = result;
|
|
2757
|
-
return {
|
|
2758
|
-
content: [
|
|
2759
|
-
{
|
|
2760
|
-
type: "text",
|
|
2761
|
-
text: `Focused on node "${typedResult.name}" (ID: ${typedResult.id})`
|
|
2762
|
-
}
|
|
2763
|
-
]
|
|
2764
|
-
};
|
|
2765
|
-
} catch (error) {
|
|
2766
|
-
return {
|
|
2767
|
-
content: [
|
|
2768
|
-
{
|
|
2769
|
-
type: "text",
|
|
2770
|
-
text: `Error setting focus: ${error instanceof Error ? error.message : String(error)}`
|
|
2771
|
-
}
|
|
2772
|
-
]
|
|
2773
|
-
};
|
|
2774
|
-
}
|
|
2775
|
-
}
|
|
2776
|
-
);
|
|
2777
|
-
server.tool(
|
|
2778
|
-
"set_selections",
|
|
2779
|
-
"Set selection to multiple nodes in Figma and scroll viewport to show them",
|
|
2780
|
-
{
|
|
2781
|
-
nodeIds: import_zod.z.array(import_zod.z.string()).describe("Array of node IDs to select")
|
|
2782
|
-
},
|
|
2783
|
-
async ({ nodeIds }) => {
|
|
2784
|
-
try {
|
|
2785
|
-
const result = await sendCommandToFigma("set_selections", { nodeIds });
|
|
2786
|
-
const typedResult = result;
|
|
2787
|
-
return {
|
|
2788
|
-
content: [
|
|
2789
|
-
{
|
|
2790
|
-
type: "text",
|
|
2791
|
-
text: `Selected ${typedResult.count} nodes: ${typedResult.selectedNodes.map((node) => `"${node.name}" (${node.id})`).join(", ")}`
|
|
2792
|
-
}
|
|
2793
|
-
]
|
|
2794
|
-
};
|
|
2795
|
-
} catch (error) {
|
|
2796
|
-
return {
|
|
2797
|
-
content: [
|
|
2798
|
-
{
|
|
2799
|
-
type: "text",
|
|
2800
|
-
text: `Error setting selections: ${error instanceof Error ? error.message : String(error)}`
|
|
2801
|
-
}
|
|
2802
|
-
]
|
|
2803
|
-
};
|
|
2804
|
-
}
|
|
2805
|
-
}
|
|
2806
|
-
);
|
|
2807
|
-
server.prompt(
|
|
2808
|
-
"reaction_to_connector_strategy",
|
|
2809
|
-
"Strategy for converting Figma prototype reactions to connector lines using the output of 'get_reactions'",
|
|
2810
|
-
(extra) => {
|
|
2811
|
-
return {
|
|
2812
|
-
messages: [
|
|
2813
|
-
{
|
|
2814
|
-
role: "assistant",
|
|
2815
|
-
content: {
|
|
2816
|
-
type: "text",
|
|
2817
|
-
text: `# Strategy: Convert Figma Prototype Reactions to Connector Lines
|
|
2818
|
-
|
|
2819
|
-
## Goal
|
|
2820
|
-
Process the JSON output from the \`get_reactions\` tool to generate an array of connection objects suitable for the \`create_connections\` tool. This visually represents prototype flows as connector lines on the Figma canvas.
|
|
2821
|
-
|
|
2822
|
-
## Input Data
|
|
2823
|
-
You will receive JSON data from the \`get_reactions\` tool. This data contains an array of nodes, each with potential reactions. A typical reaction object looks like this:
|
|
2824
|
-
\`\`\`json
|
|
2825
|
-
{
|
|
2826
|
-
"trigger": { "type": "ON_CLICK" },
|
|
2827
|
-
"action": {
|
|
2828
|
-
"type": "NAVIGATE",
|
|
2829
|
-
"destinationId": "destination-node-id",
|
|
2830
|
-
"navigationTransition": { ... },
|
|
2831
|
-
"preserveScrollPosition": false
|
|
2832
|
-
}
|
|
2833
|
-
}
|
|
2834
|
-
\`\`\`
|
|
2835
|
-
|
|
2836
|
-
## Step-by-Step Process
|
|
2837
|
-
|
|
2838
|
-
### 1. Preparation & Context Gathering
|
|
2839
|
-
- **Action:** Call \`read_my_design\` on the relevant node(s) to get context about the nodes involved (names, types, etc.). This helps in generating meaningful connector labels later.
|
|
2840
|
-
- **Action:** Call \`set_default_connector\` **without** the \`connectorId\` parameter.
|
|
2841
|
-
- **Check Result:** Analyze the response from \`set_default_connector\`.
|
|
2842
|
-
- If it confirms a default connector is already set (e.g., "Default connector is already set"), proceed to Step 2.
|
|
2843
|
-
- If it indicates no default connector is set (e.g., "No default connector set..."), you **cannot** proceed with \`create_connections\` yet. Inform the user they need to manually copy a connector from FigJam, paste it onto the current page, select it, and then you can run \`set_default_connector({ connectorId: "SELECTED_NODE_ID" })\` before attempting \`create_connections\`. **Do not proceed to Step 2 until a default connector is confirmed.**
|
|
2844
|
-
|
|
2845
|
-
### 2. Filter and Transform Reactions from \`get_reactions\` Output
|
|
2846
|
-
- **Iterate:** Go through the JSON array provided by \`get_reactions\`. For each node in the array:
|
|
2847
|
-
- Iterate through its \`reactions\` array.
|
|
2848
|
-
- **Filter:** Keep only reactions where the \`action\` meets these criteria:
|
|
2849
|
-
- Has a \`type\` that implies a connection (e.g., \`NAVIGATE\`, \`OPEN_OVERLAY\`, \`SWAP_OVERLAY\`). **Ignore** types like \`CHANGE_TO\`, \`CLOSE_OVERLAY\`, etc.
|
|
2850
|
-
- Has a valid \`destinationId\` property.
|
|
2851
|
-
- **Extract:** For each valid reaction, extract the following information:
|
|
2852
|
-
- \`sourceNodeId\`: The ID of the node the reaction belongs to (from the outer loop).
|
|
2853
|
-
- \`destinationNodeId\`: The value of \`action.destinationId\`.
|
|
2854
|
-
- \`actionType\`: The value of \`action.type\`.
|
|
2855
|
-
- \`triggerType\`: The value of \`trigger.type\`.
|
|
2856
|
-
|
|
2857
|
-
### 3. Generate Connector Text Labels
|
|
2858
|
-
- **For each extracted connection:** Create a concise, descriptive text label string.
|
|
2859
|
-
- **Combine Information:** Use the \`actionType\`, \`triggerType\`, and potentially the names of the source/destination nodes (obtained from Step 1's \`read_my_design\` or by calling \`get_node_info\` if necessary) to generate the label.
|
|
2860
|
-
- **Example Labels:**
|
|
2861
|
-
- If \`triggerType\` is "ON_CLICK" and \`actionType\` is "NAVIGATE": "On click, navigate to [Destination Node Name]"
|
|
2862
|
-
- If \`triggerType\` is "ON_DRAG" and \`actionType\` is "OPEN_OVERLAY": "On drag, open [Destination Node Name] overlay"
|
|
2863
|
-
- **Keep it brief and informative.** Let this generated string be \`generatedText\`.
|
|
2864
|
-
|
|
2865
|
-
### 4. Prepare the \`connections\` Array for \`create_connections\`
|
|
2866
|
-
- **Structure:** Create a JSON array where each element is an object representing a connection.
|
|
2867
|
-
- **Format:** Each object in the array must have the following structure:
|
|
2868
|
-
\`\`\`json
|
|
2869
|
-
{
|
|
2870
|
-
"startNodeId": "sourceNodeId_from_step_2",
|
|
2871
|
-
"endNodeId": "destinationNodeId_from_step_2",
|
|
2872
|
-
"text": "generatedText_from_step_3"
|
|
2873
|
-
}
|
|
2874
|
-
\`\`\`
|
|
2875
|
-
- **Result:** This final array is the value you will pass to the \`connections\` parameter when calling the \`create_connections\` tool.
|
|
2876
|
-
|
|
2877
|
-
### 5. Execute Connection Creation
|
|
2878
|
-
- **Action:** Call the \`create_connections\` tool, passing the array generated in Step 4 as the \`connections\` argument.
|
|
2879
|
-
- **Verify:** Check the response from \`create_connections\` to confirm success or failure.
|
|
2880
|
-
|
|
2881
|
-
This detailed process ensures you correctly interpret the reaction data, prepare the necessary information, and use the appropriate tools to create the connector lines.`
|
|
2882
|
-
}
|
|
2883
|
-
}
|
|
2884
|
-
],
|
|
2885
|
-
description: "Strategy for converting Figma prototype reactions to connector lines using the output of 'get_reactions'"
|
|
2886
|
-
};
|
|
2887
|
-
}
|
|
2888
|
-
);
|
|
2889
|
-
server.tool(
|
|
2890
|
-
"join_channel",
|
|
2891
|
-
"Join a specific channel to communicate with Figma",
|
|
2892
|
-
{
|
|
2893
|
-
channel: import_zod.z.string().describe("The name of the channel to join").default("")
|
|
2894
|
-
},
|
|
2895
|
-
async ({ channel }) => {
|
|
2896
|
-
try {
|
|
2897
|
-
if (!channel) {
|
|
2898
|
-
return {
|
|
2899
|
-
content: [
|
|
2900
|
-
{
|
|
2901
|
-
type: "text",
|
|
2902
|
-
text: "Please provide a channel name to join:"
|
|
2903
|
-
}
|
|
2904
|
-
],
|
|
2905
|
-
followUp: {
|
|
2906
|
-
tool: "join_channel",
|
|
2907
|
-
description: "Join the specified channel"
|
|
2908
|
-
}
|
|
2909
|
-
};
|
|
2910
|
-
}
|
|
2911
|
-
await joinChannel(channel);
|
|
2912
|
-
return {
|
|
2913
|
-
content: [
|
|
2914
|
-
{
|
|
2915
|
-
type: "text",
|
|
2916
|
-
text: `Successfully joined channel: ${channel}`
|
|
2917
|
-
}
|
|
2918
|
-
]
|
|
2919
|
-
};
|
|
2920
|
-
} catch (error) {
|
|
2921
|
-
return {
|
|
2922
|
-
content: [
|
|
2923
|
-
{
|
|
2924
|
-
type: "text",
|
|
2925
|
-
text: `Error joining channel: ${error instanceof Error ? error.message : String(error)}`
|
|
2926
|
-
}
|
|
2927
|
-
]
|
|
2928
|
-
};
|
|
2929
|
-
}
|
|
2930
|
-
}
|
|
2931
|
-
);
|
|
2932
|
-
main().catch((error) => {
|
|
2933
|
-
logger.error(`Error starting FigmaMCP server: ${error instanceof Error ? error.message : String(error)}`);
|
|
2934
|
-
process.exit(1);
|
|
2935
|
-
});
|
|
2936
|
-
}
|
|
2937
|
-
});
|
|
2938
|
-
|
|
2939
|
-
// src/cli.ts
|
|
2940
|
-
if (process.argv.includes("--relay")) {
|
|
2941
|
-
Promise.resolve().then(() => (init_relay(), relay_exports)).then((m) => m.startRelay());
|
|
2942
|
-
} else {
|
|
2943
|
-
Promise.resolve().then(() => init_server());
|
|
2944
|
-
}
|
|
2945
|
-
//# sourceMappingURL=cli.cjs.map
|
|
394
|
+
- Preserve component relationships by using instance overrides rather than direct text manipulation`}}],description:"Strategy for transferring overrides between component instances in Figma"}));c.tool("set_layout_mode","Set the layout mode and wrap behavior of a frame in Figma",{nodeId:r.z.string().describe("The ID of the frame to modify"),layoutMode:r.z.enum(["NONE","HORIZONTAL","VERTICAL"]).describe("Layout mode for the frame"),layoutWrap:r.z.enum(["NO_WRAP","WRAP"]).optional().describe("Whether the auto-layout frame wraps its children")},async({nodeId:e,layoutMode:t,layoutWrap:o})=>{try{return{content:[{type:"text",text:`Set layout mode of frame "${(await l("set_layout_mode",{nodeId:e,layoutMode:t,layoutWrap:o||"NO_WRAP"})).name}" to ${t}${o?` with ${o}`:""}`}]}}catch(n){return{content:[{type:"text",text:`Error setting layout mode: ${n instanceof Error?n.message:String(n)}`}]}}});c.tool("set_padding","Set padding values for an auto-layout frame in Figma",{nodeId:r.z.string().describe("The ID of the frame to modify"),paddingTop:r.z.number().optional().describe("Top padding value"),paddingRight:r.z.number().optional().describe("Right padding value"),paddingBottom:r.z.number().optional().describe("Bottom padding value"),paddingLeft:r.z.number().optional().describe("Left padding value")},async({nodeId:e,paddingTop:t,paddingRight:o,paddingBottom:n,paddingLeft:a})=>{try{let i=await l("set_padding",{nodeId:e,paddingTop:t,paddingRight:o,paddingBottom:n,paddingLeft:a}),d=[];return t!==void 0&&d.push(`top: ${t}`),o!==void 0&&d.push(`right: ${o}`),n!==void 0&&d.push(`bottom: ${n}`),a!==void 0&&d.push(`left: ${a}`),{content:[{type:"text",text:`Set ${d.length>0?`padding (${d.join(", ")})`:"padding"} for frame "${i.name}"`}]}}catch(s){return{content:[{type:"text",text:`Error setting padding: ${s instanceof Error?s.message:String(s)}`}]}}});c.tool("set_axis_align","Set primary and counter axis alignment for an auto-layout frame in Figma",{nodeId:r.z.string().describe("The ID of the frame to modify"),primaryAxisAlignItems:r.z.enum(["MIN","MAX","CENTER","SPACE_BETWEEN"]).optional().describe("Primary axis alignment (MIN/MAX = left/right in horizontal, top/bottom in vertical). Note: When set to SPACE_BETWEEN, itemSpacing will be ignored as children will be evenly spaced."),counterAxisAlignItems:r.z.enum(["MIN","MAX","CENTER","BASELINE"]).optional().describe("Counter axis alignment (MIN/MAX = top/bottom in horizontal, left/right in vertical)")},async({nodeId:e,primaryAxisAlignItems:t,counterAxisAlignItems:o})=>{try{let a=await l("set_axis_align",{nodeId:e,primaryAxisAlignItems:t,counterAxisAlignItems:o}),s=[];return t!==void 0&&s.push(`primary: ${t}`),o!==void 0&&s.push(`counter: ${o}`),{content:[{type:"text",text:`Set ${s.length>0?`axis alignment (${s.join(", ")})`:"axis alignment"} for frame "${a.name}"`}]}}catch(n){return{content:[{type:"text",text:`Error setting axis alignment: ${n instanceof Error?n.message:String(n)}`}]}}});c.tool("set_layout_sizing","Set horizontal and vertical sizing modes for an auto-layout frame in Figma",{nodeId:r.z.string().describe("The ID of the frame to modify"),layoutSizingHorizontal:r.z.enum(["FIXED","HUG","FILL"]).optional().describe("Horizontal sizing mode (HUG for frames/text only, FILL for auto-layout children only)"),layoutSizingVertical:r.z.enum(["FIXED","HUG","FILL"]).optional().describe("Vertical sizing mode (HUG for frames/text only, FILL for auto-layout children only)")},async({nodeId:e,layoutSizingHorizontal:t,layoutSizingVertical:o})=>{try{let a=await l("set_layout_sizing",{nodeId:e,layoutSizingHorizontal:t,layoutSizingVertical:o}),s=[];return t!==void 0&&s.push(`horizontal: ${t}`),o!==void 0&&s.push(`vertical: ${o}`),{content:[{type:"text",text:`Set ${s.length>0?`layout sizing (${s.join(", ")})`:"layout sizing"} for frame "${a.name}"`}]}}catch(n){return{content:[{type:"text",text:`Error setting layout sizing: ${n instanceof Error?n.message:String(n)}`}]}}});c.tool("set_item_spacing","Set distance between children in an auto-layout frame",{nodeId:r.z.string().describe("The ID of the frame to modify"),itemSpacing:r.z.number().optional().describe("Distance between children. Note: This value will be ignored if primaryAxisAlignItems is set to SPACE_BETWEEN."),counterAxisSpacing:r.z.number().optional().describe("Distance between wrapped rows/columns. Only works when layoutWrap is set to WRAP.")},async({nodeId:e,itemSpacing:t,counterAxisSpacing:o})=>{try{let n={nodeId:e};t!==void 0&&(n.itemSpacing=t),o!==void 0&&(n.counterAxisSpacing=o);let i=`Updated spacing for frame "${(await l("set_item_spacing",n)).name}":`;return t!==void 0&&(i+=` itemSpacing=${t}`),o!==void 0&&(i+=` counterAxisSpacing=${o}`),{content:[{type:"text",text:i}]}}catch(n){return{content:[{type:"text",text:`Error setting spacing: ${n instanceof Error?n.message:String(n)}`}]}}});c.tool("get_reactions","Get Figma Prototyping Reactions from multiple nodes. CRITICAL: The output MUST be processed using the 'reaction_to_connector_strategy' prompt IMMEDIATELY to generate parameters for connector lines via the 'create_connections' tool.",{nodeIds:r.z.array(r.z.string()).describe("Array of node IDs to get reactions from")},async({nodeIds:e})=>{try{let t=await l("get_reactions",{nodeIds:e});return{content:[{type:"text",text:JSON.stringify(t)},{type:"text",text:"IMPORTANT: You MUST now use the reaction data above and follow the `reaction_to_connector_strategy` prompt to prepare the parameters for the `create_connections` tool call. This is a required next step."}],followUp:{type:"prompt",prompt:"reaction_to_connector_strategy"}}}catch(t){return{content:[{type:"text",text:`Error getting reactions: ${t instanceof Error?t.message:String(t)}`}]}}});c.tool("set_default_connector","Set a copied connector node as the default connector",{connectorId:r.z.string().optional().describe("The ID of the connector node to set as default")},async({connectorId:e})=>{try{let t=await l("set_default_connector",{connectorId:e});return{content:[{type:"text",text:`Default connector set: ${JSON.stringify(t)}`}]}}catch(t){return{content:[{type:"text",text:`Error setting default connector: ${t instanceof Error?t.message:String(t)}`}]}}});c.tool("create_connections","Create connections between nodes using the default connector style",{connections:r.z.array(r.z.object({startNodeId:r.z.string().describe("ID of the starting node"),endNodeId:r.z.string().describe("ID of the ending node"),text:r.z.string().optional().describe("Optional text to display on the connector")})).describe("Array of node connections to create")},async({connections:e})=>{try{if(!e||e.length===0)return{content:[{type:"text",text:"No connections provided"}]};let t=await l("create_connections",{connections:e});return{content:[{type:"text",text:`Created ${e.length} connections: ${JSON.stringify(t)}`}]}}catch(t){return{content:[{type:"text",text:`Error creating connections: ${t instanceof Error?t.message:String(t)}`}]}}});c.tool("set_focus","Set focus on a specific node in Figma by selecting it and scrolling viewport to it",{nodeId:r.z.string().describe("The ID of the node to focus on")},async({nodeId:e})=>{try{let o=await l("set_focus",{nodeId:e});return{content:[{type:"text",text:`Focused on node "${o.name}" (ID: ${o.id})`}]}}catch(t){return{content:[{type:"text",text:`Error setting focus: ${t instanceof Error?t.message:String(t)}`}]}}});c.tool("set_selections","Set selection to multiple nodes in Figma and scroll viewport to show them",{nodeIds:r.z.array(r.z.string()).describe("Array of node IDs to select")},async({nodeIds:e})=>{try{let o=await l("set_selections",{nodeIds:e});return{content:[{type:"text",text:`Selected ${o.count} nodes: ${o.selectedNodes.map(n=>`"${n.name}" (${n.id})`).join(", ")}`}]}}catch(t){return{content:[{type:"text",text:`Error setting selections: ${t instanceof Error?t.message:String(t)}`}]}}});c.prompt("reaction_to_connector_strategy","Strategy for converting Figma prototype reactions to connector lines using the output of 'get_reactions'",e=>({messages:[{role:"assistant",content:{type:"text",text:'# Strategy: Convert Figma Prototype Reactions to Connector Lines\n\n## Goal\nProcess the JSON output from the `get_reactions` tool to generate an array of connection objects suitable for the `create_connections` tool. This visually represents prototype flows as connector lines on the Figma canvas.\n\n## Input Data\nYou will receive JSON data from the `get_reactions` tool. This data contains an array of nodes, each with potential reactions. A typical reaction object looks like this:\n```json\n{\n "trigger": { "type": "ON_CLICK" },\n "action": {\n "type": "NAVIGATE",\n "destinationId": "destination-node-id",\n "navigationTransition": { ... },\n "preserveScrollPosition": false\n }\n}\n```\n\n## Step-by-Step Process\n\n### 1. Preparation & Context Gathering\n - **Action:** Call `read_my_design` on the relevant node(s) to get context about the nodes involved (names, types, etc.). This helps in generating meaningful connector labels later.\n - **Action:** Call `set_default_connector` **without** the `connectorId` parameter.\n - **Check Result:** Analyze the response from `set_default_connector`.\n - If it confirms a default connector is already set (e.g., "Default connector is already set"), proceed to Step 2.\n - If it indicates no default connector is set (e.g., "No default connector set..."), you **cannot** proceed with `create_connections` yet. Inform the user they need to manually copy a connector from FigJam, paste it onto the current page, select it, and then you can run `set_default_connector({ connectorId: "SELECTED_NODE_ID" })` before attempting `create_connections`. **Do not proceed to Step 2 until a default connector is confirmed.**\n\n### 2. Filter and Transform Reactions from `get_reactions` Output\n - **Iterate:** Go through the JSON array provided by `get_reactions`. For each node in the array:\n - Iterate through its `reactions` array.\n - **Filter:** Keep only reactions where the `action` meets these criteria:\n - Has a `type` that implies a connection (e.g., `NAVIGATE`, `OPEN_OVERLAY`, `SWAP_OVERLAY`). **Ignore** types like `CHANGE_TO`, `CLOSE_OVERLAY`, etc.\n - Has a valid `destinationId` property.\n - **Extract:** For each valid reaction, extract the following information:\n - `sourceNodeId`: The ID of the node the reaction belongs to (from the outer loop).\n - `destinationNodeId`: The value of `action.destinationId`.\n - `actionType`: The value of `action.type`.\n - `triggerType`: The value of `trigger.type`.\n\n### 3. Generate Connector Text Labels\n - **For each extracted connection:** Create a concise, descriptive text label string.\n - **Combine Information:** Use the `actionType`, `triggerType`, and potentially the names of the source/destination nodes (obtained from Step 1\'s `read_my_design` or by calling `get_node_info` if necessary) to generate the label.\n - **Example Labels:**\n - If `triggerType` is "ON_CLICK" and `actionType` is "NAVIGATE": "On click, navigate to [Destination Node Name]"\n - If `triggerType` is "ON_DRAG" and `actionType` is "OPEN_OVERLAY": "On drag, open [Destination Node Name] overlay"\n - **Keep it brief and informative.** Let this generated string be `generatedText`.\n\n### 4. Prepare the `connections` Array for `create_connections`\n - **Structure:** Create a JSON array where each element is an object representing a connection.\n - **Format:** Each object in the array must have the following structure:\n ```json\n {\n "startNodeId": "sourceNodeId_from_step_2",\n "endNodeId": "destinationNodeId_from_step_2",\n "text": "generatedText_from_step_3"\n }\n ```\n - **Result:** This final array is the value you will pass to the `connections` parameter when calling the `create_connections` tool.\n\n### 5. Execute Connection Creation\n - **Action:** Call the `create_connections` tool, passing the array generated in Step 4 as the `connections` argument.\n - **Verify:** Check the response from `create_connections` to confirm success or failure.\n\nThis detailed process ensures you correctly interpret the reaction data, prepare the necessary information, and use the appropriate tools to create the connector lines.'}}],description:"Strategy for converting Figma prototype reactions to connector lines using the output of 'get_reactions'"}));c.tool("join_channel","Join a specific channel to communicate with Figma",{channel:r.z.string().describe("The name of the channel to join").default("")},async({channel:e})=>{try{return e?(await re(e),{content:[{type:"text",text:`Successfully joined channel: ${e}`}]}):{content:[{type:"text",text:"Please provide a channel name to join:"}],followUp:{tool:"join_channel",description:"Join the specified channel"}}}catch(t){return{content:[{type:"text",text:`Error joining channel: ${t instanceof Error?t.message:String(t)}`}]}}});oe().catch(e=>{g.error(`Error starting FigmaMCP server: ${e instanceof Error?e.message:String(e)}`),process.exit(1)})});process.argv.includes("--relay")?Promise.resolve().then(()=>(F(),k)).then(e=>e.startRelay()):Promise.resolve().then(()=>(G(),ae));
|