@atezer/figma-mcp-bridge 1.7.25 → 1.7.26
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/CHANGELOG.md +37 -0
- package/agents/ds-auditor.md +6 -0
- package/agents/screen-builder.md +6 -0
- package/agents/token-syncer.md +6 -0
- package/dist/core/audit-log.d.ts +4 -0
- package/dist/core/audit-log.d.ts.map +1 -1
- package/dist/core/audit-log.js +12 -0
- package/dist/core/audit-log.js.map +1 -1
- package/dist/core/config.d.ts +1 -1
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +6 -2
- package/dist/core/config.js.map +1 -1
- package/dist/core/plugin-bridge-connector.d.ts +16 -16
- package/dist/core/plugin-bridge-connector.d.ts.map +1 -1
- package/dist/core/plugin-bridge-connector.js +2 -1
- package/dist/core/plugin-bridge-connector.js.map +1 -1
- package/dist/core/plugin-bridge-server.d.ts +5 -3
- package/dist/core/plugin-bridge-server.d.ts.map +1 -1
- package/dist/core/plugin-bridge-server.js +34 -22
- package/dist/core/plugin-bridge-server.js.map +1 -1
- package/dist/core/response-cache.d.ts +16 -0
- package/dist/core/response-cache.d.ts.map +1 -0
- package/dist/core/response-cache.js +46 -0
- package/dist/core/response-cache.js.map +1 -0
- package/dist/core/response-guard.d.ts.map +1 -1
- package/dist/core/response-guard.js +19 -13
- package/dist/core/response-guard.js.map +1 -1
- package/dist/core/types/figma.d.ts +6 -0
- package/dist/core/types/figma.d.ts.map +1 -1
- package/dist/core/version.d.ts +1 -1
- package/dist/core/version.js +1 -1
- package/dist/local-plugin-only.d.ts.map +1 -1
- package/dist/local-plugin-only.js +202 -140
- package/dist/local-plugin-only.js.map +1 -1
- package/f-mcp-plugin/manifest.json +1 -1
- package/package.json +1 -1
- package/skills/ai-handoff-export/SKILL.md +8 -0
- package/skills/component-documentation/SKILL.md +8 -0
- package/skills/figjam-diagram-builder/SKILL.md +8 -0
- package/skills/figma-screen-analyzer/SKILL.md +8 -0
- package/skills/ux-copy-guidance/SKILL.md +8 -0
- package/skills/visual-qa-compare/SKILL.md +8 -0
|
@@ -18,6 +18,9 @@ import { PluginBridgeServer } from "./core/plugin-bridge-server.js";
|
|
|
18
18
|
import { PluginBridgeConnector } from "./core/plugin-bridge-connector.js";
|
|
19
19
|
import { parseFigmaUrl } from "./core/figma-url.js";
|
|
20
20
|
import { truncateRestResponse } from "./core/response-guard.js";
|
|
21
|
+
import { closeAuditLog } from "./core/audit-log.js";
|
|
22
|
+
import { FMCP_VERSION } from "./core/version.js";
|
|
23
|
+
import { ResponseCache } from "./core/response-cache.js";
|
|
21
24
|
const logger = createChildLogger({ component: "plugin-only-mcp" });
|
|
22
25
|
/** Resolve fileKey from figmaUrl (parse) or explicit fileKey. Returns undefined if neither yields a key. */
|
|
23
26
|
function resolveFileKey(figmaUrl, explicitFileKey) {
|
|
@@ -67,6 +70,21 @@ function normalizeForCompare(s) {
|
|
|
67
70
|
return s.replace(/\s/g, "");
|
|
68
71
|
}
|
|
69
72
|
const PLUGIN_NOT_CONNECTED = "F-MCP ATezer Bridge plugin not connected. Open Figma → Plugins → Development → F-MCP ATezer Bridge, wait for 'ready'.";
|
|
73
|
+
/** Wrap a tool handler with try-catch to prevent unhandled rejections. */
|
|
74
|
+
function safeToolHandler(handler) {
|
|
75
|
+
return async (params) => {
|
|
76
|
+
try {
|
|
77
|
+
return await handler(params);
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
81
|
+
return {
|
|
82
|
+
content: [{ type: "text", text: JSON.stringify({ success: false, error: msg }) }],
|
|
83
|
+
isError: true,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
}
|
|
70
88
|
function getConnector(bridge, fileKey) {
|
|
71
89
|
if (!bridge.isConnected(fileKey)) {
|
|
72
90
|
if (fileKey) {
|
|
@@ -87,9 +105,12 @@ export async function main() {
|
|
|
87
105
|
const auditLogPath = config.local?.auditLogPath;
|
|
88
106
|
const bridge = new PluginBridgeServer(port, { auditLogPath });
|
|
89
107
|
bridge.start();
|
|
108
|
+
const cache = new ResponseCache();
|
|
109
|
+
/** Invalidate cache after any mutating operation. */
|
|
110
|
+
function invalidateCache() { cache.invalidate(); }
|
|
90
111
|
const server = new McpServer({
|
|
91
112
|
name: "F-MCP ATezer Bridge (Plugin-only)",
|
|
92
|
-
version:
|
|
113
|
+
version: FMCP_VERSION,
|
|
93
114
|
});
|
|
94
115
|
// ---- figma_list_connected_files (multi-client discovery) ----
|
|
95
116
|
server.registerTool("figma_list_connected_files", {
|
|
@@ -108,7 +129,7 @@ export async function main() {
|
|
|
108
129
|
message: files.length === 0
|
|
109
130
|
? "No plugins connected. Open Figma and run the F-MCP ATezer Bridge plugin."
|
|
110
131
|
: `${files.length} plugin(s) connected. Use fileKey parameter in other tools to target a specific file.`,
|
|
111
|
-
}
|
|
132
|
+
}),
|
|
112
133
|
}],
|
|
113
134
|
};
|
|
114
135
|
});
|
|
@@ -132,7 +153,7 @@ export async function main() {
|
|
|
132
153
|
const resolvedKey = resolveFileKey(figmaUrl, fileKey);
|
|
133
154
|
if (figmaUrl && !resolvedKey) {
|
|
134
155
|
return {
|
|
135
|
-
content: [{ type: "text", text: JSON.stringify({ success: false, error: "Invalid Figma/FigJam URL: could not extract file key." }
|
|
156
|
+
content: [{ type: "text", text: JSON.stringify({ success: false, error: "Invalid Figma/FigJam URL: could not extract file key." }) }],
|
|
136
157
|
isError: true,
|
|
137
158
|
};
|
|
138
159
|
}
|
|
@@ -149,13 +170,13 @@ export async function main() {
|
|
|
149
170
|
? JSON.stringify({ success: false, error: "No data from plugin" })
|
|
150
171
|
: typeof data === "string"
|
|
151
172
|
? data
|
|
152
|
-
: JSON.stringify(data
|
|
173
|
+
: JSON.stringify(data);
|
|
153
174
|
return { content: [{ type: "text", text }] };
|
|
154
175
|
}
|
|
155
176
|
catch (err) {
|
|
156
177
|
const msg = err instanceof Error ? err.message : String(err);
|
|
157
178
|
return {
|
|
158
|
-
content: [{ type: "text", text: JSON.stringify({ success: false, error: msg }
|
|
179
|
+
content: [{ type: "text", text: JSON.stringify({ success: false, error: msg }) }],
|
|
159
180
|
isError: true,
|
|
160
181
|
};
|
|
161
182
|
}
|
|
@@ -182,7 +203,7 @@ export async function main() {
|
|
|
182
203
|
const { fileKey: resolvedKey, nodeId: resolvedNodeId } = resolveDesignContextParams({ figmaUrl, fileKey, nodeId });
|
|
183
204
|
if (figmaUrl && !resolvedKey) {
|
|
184
205
|
return {
|
|
185
|
-
content: [{ type: "text", text: JSON.stringify({ success: false, error: "Invalid Figma/FigJam URL: could not extract file key." }
|
|
206
|
+
content: [{ type: "text", text: JSON.stringify({ success: false, error: "Invalid Figma/FigJam URL: could not extract file key." }) }],
|
|
186
207
|
isError: true,
|
|
187
208
|
};
|
|
188
209
|
}
|
|
@@ -203,13 +224,13 @@ export async function main() {
|
|
|
203
224
|
? JSON.stringify({ success: false, error: "No data from plugin" })
|
|
204
225
|
: typeof data === "string"
|
|
205
226
|
? data
|
|
206
|
-
: JSON.stringify(data
|
|
227
|
+
: JSON.stringify(data);
|
|
207
228
|
return { content: [{ type: "text", text }] };
|
|
208
229
|
}
|
|
209
230
|
catch (err) {
|
|
210
231
|
const msg = err instanceof Error ? err.message : String(err);
|
|
211
232
|
return {
|
|
212
|
-
content: [{ type: "text", text: JSON.stringify({ success: false, error: msg }
|
|
233
|
+
content: [{ type: "text", text: JSON.stringify({ success: false, error: msg }) }],
|
|
213
234
|
isError: true,
|
|
214
235
|
};
|
|
215
236
|
}
|
|
@@ -223,7 +244,7 @@ export async function main() {
|
|
|
223
244
|
verbosity: z.enum(["inventory", "summary", "standard", "full"]).optional().default("summary"),
|
|
224
245
|
},
|
|
225
246
|
annotations: { readOnlyHint: true },
|
|
226
|
-
}, async ({ figmaUrl, fileKey, verbosity }) => {
|
|
247
|
+
}, safeToolHandler(async ({ figmaUrl, fileKey, verbosity }) => {
|
|
227
248
|
const conn = getConnector(bridge, resolveFileKey(figmaUrl, fileKey));
|
|
228
249
|
const raw = await conn.getVariablesFromPluginUI();
|
|
229
250
|
if (!raw || !raw.variables) {
|
|
@@ -247,8 +268,8 @@ export async function main() {
|
|
|
247
268
|
valuesByMode: v.valuesByMode,
|
|
248
269
|
}));
|
|
249
270
|
}
|
|
250
|
-
return { content: [{ type: "text", text: JSON.stringify(out
|
|
251
|
-
});
|
|
271
|
+
return { content: [{ type: "text", text: JSON.stringify(out) }] };
|
|
272
|
+
}));
|
|
252
273
|
// ---- figma_get_component ----
|
|
253
274
|
server.registerTool("figma_get_component", {
|
|
254
275
|
description: "Get component metadata by node ID from the open Figma file. No REST API. Use fileKey or figmaUrl to target a specific file.",
|
|
@@ -258,11 +279,11 @@ export async function main() {
|
|
|
258
279
|
nodeId: z.string(),
|
|
259
280
|
},
|
|
260
281
|
annotations: { readOnlyHint: true },
|
|
261
|
-
}, async ({ figmaUrl, fileKey, nodeId }) => {
|
|
282
|
+
}, safeToolHandler(async ({ figmaUrl, fileKey, nodeId }) => {
|
|
262
283
|
const conn = getConnector(bridge, resolveFileKey(figmaUrl, fileKey));
|
|
263
284
|
const result = await conn.getComponentFromPluginUI(nodeId);
|
|
264
|
-
return { content: [{ type: "text", text: JSON.stringify(result
|
|
265
|
-
});
|
|
285
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
286
|
+
}));
|
|
266
287
|
// ---- figma_get_styles (plugin only) ----
|
|
267
288
|
server.registerTool("figma_get_styles", {
|
|
268
289
|
description: "Get local paint, text, and effect styles from the open Figma file. No REST API. Use fileKey or figmaUrl to target a specific file.",
|
|
@@ -272,11 +293,11 @@ export async function main() {
|
|
|
272
293
|
verbosity: z.enum(["summary", "full"]).optional().default("summary"),
|
|
273
294
|
},
|
|
274
295
|
annotations: { readOnlyHint: true },
|
|
275
|
-
}, async ({ figmaUrl, fileKey, verbosity }) => {
|
|
296
|
+
}, safeToolHandler(async ({ figmaUrl, fileKey, verbosity }) => {
|
|
276
297
|
const conn = getConnector(bridge, resolveFileKey(figmaUrl, fileKey));
|
|
277
298
|
const data = await conn.getLocalStyles(verbosity);
|
|
278
|
-
return { content: [{ type: "text", text: JSON.stringify(data || {}
|
|
279
|
-
});
|
|
299
|
+
return { content: [{ type: "text", text: JSON.stringify(data || {}) }] };
|
|
300
|
+
}));
|
|
280
301
|
// ---- figma_execute ----
|
|
281
302
|
server.registerTool("figma_execute", {
|
|
282
303
|
description: "Run JavaScript in the Figma plugin context. Full Plugin API available. Use fileKey or figmaUrl to target a specific file.",
|
|
@@ -287,11 +308,18 @@ export async function main() {
|
|
|
287
308
|
timeout: z.number().optional().default(5000),
|
|
288
309
|
},
|
|
289
310
|
annotations: { destructiveHint: true },
|
|
290
|
-
}, async ({ figmaUrl, fileKey, code, timeout }) => {
|
|
311
|
+
}, safeToolHandler(async ({ figmaUrl, fileKey, code, timeout }) => {
|
|
312
|
+
if (code.length > 50000) {
|
|
313
|
+
return {
|
|
314
|
+
content: [{ type: "text", text: JSON.stringify({ success: false, error: "Code too long (max 50,000 characters). Break into smaller pieces." }) }],
|
|
315
|
+
isError: true,
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
invalidateCache();
|
|
291
319
|
const conn = getConnector(bridge, resolveFileKey(figmaUrl, fileKey));
|
|
292
320
|
const result = await conn.executeCodeViaUI(code, timeout);
|
|
293
|
-
return { content: [{ type: "text", text: JSON.stringify(result
|
|
294
|
-
});
|
|
321
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
322
|
+
}));
|
|
295
323
|
// ---- figma_capture_screenshot ----
|
|
296
324
|
server.registerTool("figma_capture_screenshot", {
|
|
297
325
|
description: "Capture screenshot of a node or current view from the plugin. No REST API. Use fileKey or figmaUrl to target a specific file.",
|
|
@@ -303,11 +331,11 @@ export async function main() {
|
|
|
303
331
|
scale: z.number().optional().default(2),
|
|
304
332
|
},
|
|
305
333
|
annotations: { readOnlyHint: true },
|
|
306
|
-
}, async ({ figmaUrl, fileKey, nodeId, format, scale }) => {
|
|
334
|
+
}, safeToolHandler(async ({ figmaUrl, fileKey, nodeId, format, scale }) => {
|
|
307
335
|
const conn = getConnector(bridge, resolveFileKey(figmaUrl, fileKey));
|
|
308
336
|
const result = await conn.captureScreenshot(nodeId ?? null, { format, scale });
|
|
309
|
-
return { content: [{ type: "text", text: JSON.stringify(result
|
|
310
|
-
});
|
|
337
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
338
|
+
}));
|
|
311
339
|
// ---- figma_set_instance_properties ----
|
|
312
340
|
server.registerTool("figma_set_instance_properties", {
|
|
313
341
|
description: "Set component instance properties (TEXT, BOOLEAN, VARIANT, etc.). Use fileKey or figmaUrl to target a specific file.",
|
|
@@ -318,11 +346,12 @@ export async function main() {
|
|
|
318
346
|
properties: z.record(z.union([z.string(), z.boolean()])),
|
|
319
347
|
},
|
|
320
348
|
annotations: { destructiveHint: true },
|
|
321
|
-
}, async ({ figmaUrl, fileKey, nodeId, properties }) => {
|
|
349
|
+
}, safeToolHandler(async ({ figmaUrl, fileKey, nodeId, properties }) => {
|
|
350
|
+
invalidateCache();
|
|
322
351
|
const conn = getConnector(bridge, resolveFileKey(figmaUrl, fileKey));
|
|
323
352
|
const result = await conn.setInstanceProperties(nodeId, properties);
|
|
324
|
-
return { content: [{ type: "text", text: JSON.stringify(result
|
|
325
|
-
});
|
|
353
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
354
|
+
}));
|
|
326
355
|
// ---- Variable CRUD ----
|
|
327
356
|
server.registerTool("figma_update_variable", {
|
|
328
357
|
description: "Update a variable value in a mode. Get IDs from figma_get_variables.",
|
|
@@ -332,11 +361,12 @@ export async function main() {
|
|
|
332
361
|
value: z.union([z.string(), z.number(), z.boolean()]),
|
|
333
362
|
},
|
|
334
363
|
annotations: { destructiveHint: true },
|
|
335
|
-
}, async (p) => {
|
|
364
|
+
}, safeToolHandler(async (p) => {
|
|
365
|
+
invalidateCache();
|
|
336
366
|
const conn = getConnector(bridge);
|
|
337
367
|
const result = await conn.updateVariable(p.variableId, p.modeId, p.value);
|
|
338
|
-
return { content: [{ type: "text", text: JSON.stringify(result
|
|
339
|
-
});
|
|
368
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
369
|
+
}));
|
|
340
370
|
server.registerTool("figma_create_variable", {
|
|
341
371
|
description: "Create a variable in a collection. Get collectionId from figma_get_variables.",
|
|
342
372
|
inputSchema: {
|
|
@@ -346,65 +376,72 @@ export async function main() {
|
|
|
346
376
|
options: z.record(z.any()).optional(),
|
|
347
377
|
},
|
|
348
378
|
annotations: { destructiveHint: true },
|
|
349
|
-
}, async (p) => {
|
|
379
|
+
}, safeToolHandler(async (p) => {
|
|
380
|
+
invalidateCache();
|
|
350
381
|
const conn = getConnector(bridge);
|
|
351
382
|
const result = await conn.createVariable(p.name, p.collectionId, p.resolvedType, p.options);
|
|
352
|
-
return { content: [{ type: "text", text: JSON.stringify(result
|
|
353
|
-
});
|
|
383
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
384
|
+
}));
|
|
354
385
|
server.registerTool("figma_create_variable_collection", {
|
|
355
386
|
description: "Create a variable collection.",
|
|
356
387
|
inputSchema: { name: z.string(), options: z.record(z.any()).optional() },
|
|
357
388
|
annotations: { destructiveHint: true },
|
|
358
|
-
}, async (p) => {
|
|
389
|
+
}, safeToolHandler(async (p) => {
|
|
390
|
+
invalidateCache();
|
|
359
391
|
const conn = getConnector(bridge);
|
|
360
392
|
const result = await conn.createVariableCollection(p.name, p.options);
|
|
361
|
-
return { content: [{ type: "text", text: JSON.stringify(result
|
|
362
|
-
});
|
|
393
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
394
|
+
}));
|
|
363
395
|
server.registerTool("figma_delete_variable", {
|
|
364
396
|
description: "Delete a variable.",
|
|
365
397
|
inputSchema: { variableId: z.string() },
|
|
366
398
|
annotations: { destructiveHint: true },
|
|
367
|
-
}, async (p) => {
|
|
399
|
+
}, safeToolHandler(async (p) => {
|
|
400
|
+
invalidateCache();
|
|
368
401
|
const conn = getConnector(bridge);
|
|
369
402
|
const result = await conn.deleteVariable(p.variableId);
|
|
370
|
-
return { content: [{ type: "text", text: JSON.stringify(result
|
|
371
|
-
});
|
|
403
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
404
|
+
}));
|
|
372
405
|
server.registerTool("figma_delete_variable_collection", {
|
|
373
406
|
description: "Delete a variable collection.",
|
|
374
407
|
inputSchema: { collectionId: z.string() },
|
|
375
408
|
annotations: { destructiveHint: true },
|
|
376
|
-
}, async (p) => {
|
|
409
|
+
}, safeToolHandler(async (p) => {
|
|
410
|
+
invalidateCache();
|
|
377
411
|
const conn = getConnector(bridge);
|
|
378
412
|
const result = await conn.deleteVariableCollection(p.collectionId);
|
|
379
|
-
return { content: [{ type: "text", text: JSON.stringify(result
|
|
380
|
-
});
|
|
413
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
414
|
+
}));
|
|
381
415
|
server.registerTool("figma_rename_variable", {
|
|
382
416
|
description: "Rename a variable.",
|
|
383
417
|
inputSchema: { variableId: z.string(), newName: z.string() },
|
|
384
418
|
annotations: { destructiveHint: true },
|
|
385
|
-
}, async (p) => {
|
|
419
|
+
}, safeToolHandler(async (p) => {
|
|
420
|
+
invalidateCache();
|
|
386
421
|
const conn = getConnector(bridge);
|
|
387
422
|
const result = await conn.renameVariable(p.variableId, p.newName);
|
|
388
|
-
return { content: [{ type: "text", text: JSON.stringify(result
|
|
389
|
-
});
|
|
423
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
424
|
+
}));
|
|
390
425
|
server.registerTool("figma_add_mode", {
|
|
391
426
|
description: "Add a mode to a collection.",
|
|
392
427
|
inputSchema: { collectionId: z.string(), modeName: z.string() },
|
|
393
428
|
annotations: { destructiveHint: true },
|
|
394
|
-
}, async (p) => {
|
|
429
|
+
}, safeToolHandler(async (p) => {
|
|
430
|
+
invalidateCache();
|
|
395
431
|
const conn = getConnector(bridge);
|
|
396
432
|
const result = await conn.addMode(p.collectionId, p.modeName);
|
|
397
|
-
return { content: [{ type: "text", text: JSON.stringify(result
|
|
398
|
-
});
|
|
433
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
434
|
+
}));
|
|
399
435
|
server.registerTool("figma_rename_mode", {
|
|
400
436
|
description: "Rename a mode in a collection.",
|
|
401
437
|
inputSchema: { collectionId: z.string(), modeId: z.string(), newName: z.string() },
|
|
402
438
|
annotations: { destructiveHint: true },
|
|
403
|
-
}, async (p) => {
|
|
439
|
+
}, safeToolHandler(async (p) => {
|
|
440
|
+
invalidateCache();
|
|
404
441
|
const conn = getConnector(bridge);
|
|
405
442
|
const result = await conn.renameMode(p.collectionId, p.modeId, p.newName);
|
|
406
|
-
return { content: [{ type: "text", text: JSON.stringify(result
|
|
407
|
-
});
|
|
443
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
444
|
+
}));
|
|
408
445
|
// ---- Design system summary (minimal tokens) ----
|
|
409
446
|
server.registerTool("figma_get_design_system_summary", {
|
|
410
447
|
description: "Get a compact overview: variable collection names and component counts. Minimal tokens. Use fileKey or figmaUrl to target a specific file.",
|
|
@@ -415,7 +452,7 @@ export async function main() {
|
|
|
415
452
|
limit: z.number().min(0).optional(),
|
|
416
453
|
},
|
|
417
454
|
annotations: { readOnlyHint: true },
|
|
418
|
-
}, async ({ figmaUrl, fileKey, currentPageOnly, limit }) => {
|
|
455
|
+
}, safeToolHandler(async ({ figmaUrl, fileKey, currentPageOnly, limit }) => {
|
|
419
456
|
const conn = getConnector(bridge, resolveFileKey(figmaUrl, fileKey));
|
|
420
457
|
const [vars, components] = await Promise.all([
|
|
421
458
|
conn.getVariablesFromPluginUI(),
|
|
@@ -430,8 +467,8 @@ export async function main() {
|
|
|
430
467
|
components: compData?.totalComponents ?? 0,
|
|
431
468
|
componentSets: compData?.totalComponentSets ?? 0,
|
|
432
469
|
};
|
|
433
|
-
return { content: [{ type: "text", text: JSON.stringify(out
|
|
434
|
-
});
|
|
470
|
+
return { content: [{ type: "text", text: JSON.stringify(out) }] };
|
|
471
|
+
}));
|
|
435
472
|
// ---- figma_search_components ----
|
|
436
473
|
server.registerTool("figma_search_components", {
|
|
437
474
|
description: "Search local components by name. Returns nodeIds and names. No REST API. Use fileKey or figmaUrl to target a specific file.",
|
|
@@ -443,7 +480,7 @@ export async function main() {
|
|
|
443
480
|
limit: z.number().min(0).optional(),
|
|
444
481
|
},
|
|
445
482
|
annotations: { readOnlyHint: true },
|
|
446
|
-
}, async ({ figmaUrl, fileKey, query, currentPageOnly, limit }) => {
|
|
483
|
+
}, safeToolHandler(async ({ figmaUrl, fileKey, query, currentPageOnly, limit }) => {
|
|
447
484
|
const conn = getConnector(bridge, resolveFileKey(figmaUrl, fileKey));
|
|
448
485
|
const result = (await conn.getLocalComponents({ currentPageOnly, limit }));
|
|
449
486
|
const data = result?.data;
|
|
@@ -456,8 +493,8 @@ export async function main() {
|
|
|
456
493
|
list = list.filter((c) => (c.name || "").toLowerCase().includes(q));
|
|
457
494
|
}
|
|
458
495
|
const summary = list.map((c) => ({ id: c.id, name: c.name, key: c.key, type: c.type }));
|
|
459
|
-
return { content: [{ type: "text", text: JSON.stringify({ success: true, components: summary }
|
|
460
|
-
});
|
|
496
|
+
return { content: [{ type: "text", text: JSON.stringify({ success: true, components: summary }) }] };
|
|
497
|
+
}));
|
|
461
498
|
// ---- Node operations (short list) ----
|
|
462
499
|
server.registerTool("figma_instantiate_component", {
|
|
463
500
|
description: "Create a component instance. Use componentKey from figma_search_components or nodeId for local components.",
|
|
@@ -473,63 +510,80 @@ export async function main() {
|
|
|
473
510
|
.optional(),
|
|
474
511
|
},
|
|
475
512
|
annotations: { destructiveHint: true },
|
|
476
|
-
}, async (p) => {
|
|
513
|
+
}, safeToolHandler(async (p) => {
|
|
514
|
+
invalidateCache();
|
|
477
515
|
const conn = getConnector(bridge);
|
|
478
516
|
const result = await conn.instantiateComponent(p.componentKey, p.options || {});
|
|
479
|
-
return { content: [{ type: "text", text: JSON.stringify(result
|
|
480
|
-
});
|
|
517
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
518
|
+
}));
|
|
481
519
|
server.registerTool("figma_refresh_variables", {
|
|
482
520
|
description: "Refresh variables from the file.",
|
|
483
521
|
inputSchema: {},
|
|
484
522
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
485
|
-
}, async () => {
|
|
523
|
+
}, safeToolHandler(async () => {
|
|
486
524
|
const conn = getConnector(bridge);
|
|
487
525
|
const result = await conn.refreshVariables();
|
|
488
|
-
return { content: [{ type: "text", text: JSON.stringify(result
|
|
489
|
-
});
|
|
526
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
527
|
+
}));
|
|
490
528
|
// ---- Console (plugin buffer, no CDP) ----
|
|
491
529
|
server.registerTool("figma_get_console_logs", {
|
|
492
530
|
description: "Get plugin console logs (log/warn/error) from the F-MCP plugin buffer. No CDP. Limit default 50.",
|
|
493
531
|
inputSchema: { limit: z.number().min(1).max(200).optional().default(50) },
|
|
494
532
|
annotations: { readOnlyHint: true },
|
|
495
|
-
}, async ({ limit }) => {
|
|
533
|
+
}, safeToolHandler(async ({ limit }) => {
|
|
496
534
|
const conn = getConnector(bridge);
|
|
497
535
|
const data = await conn.getConsoleLogs(limit);
|
|
498
|
-
return { content: [{ type: "text", text: JSON.stringify({ success: true, ...data }
|
|
499
|
-
});
|
|
536
|
+
return { content: [{ type: "text", text: JSON.stringify({ success: true, ...data }) }] };
|
|
537
|
+
}));
|
|
500
538
|
server.registerTool("figma_watch_console", {
|
|
501
539
|
description: "Stream new plugin console logs until timeout. Polls the plugin buffer. Timeout default 30s.",
|
|
502
540
|
inputSchema: { timeoutSeconds: z.number().min(1).max(120).optional().default(30) },
|
|
503
541
|
annotations: { readOnlyHint: true },
|
|
504
|
-
}, async ({ timeoutSeconds }) => {
|
|
542
|
+
}, safeToolHandler(async ({ timeoutSeconds }) => {
|
|
505
543
|
const conn = getConnector(bridge);
|
|
506
544
|
const deadline = Date.now() + timeoutSeconds * 1000;
|
|
507
|
-
|
|
545
|
+
let lastSeenTime = 0;
|
|
508
546
|
const stream = [];
|
|
547
|
+
let pollIntervalMs = 1000;
|
|
548
|
+
let consecutiveEmptyPolls = 0;
|
|
509
549
|
while (Date.now() < deadline) {
|
|
510
550
|
const { logs } = await conn.getConsoleLogs(200);
|
|
551
|
+
let newCount = 0;
|
|
511
552
|
for (const entry of logs) {
|
|
512
|
-
|
|
513
|
-
if (!seen.has(key)) {
|
|
514
|
-
seen.add(key);
|
|
553
|
+
if (entry.time > lastSeenTime) {
|
|
515
554
|
stream.push(entry);
|
|
555
|
+
newCount++;
|
|
556
|
+
if (entry.time > lastSeenTime)
|
|
557
|
+
lastSeenTime = entry.time;
|
|
516
558
|
}
|
|
517
559
|
}
|
|
518
|
-
|
|
560
|
+
if (newCount > 0) {
|
|
561
|
+
consecutiveEmptyPolls = 0;
|
|
562
|
+
pollIntervalMs = 1000;
|
|
563
|
+
}
|
|
564
|
+
else {
|
|
565
|
+
consecutiveEmptyPolls++;
|
|
566
|
+
if (consecutiveEmptyPolls >= 10)
|
|
567
|
+
break;
|
|
568
|
+
if (consecutiveEmptyPolls >= 3) {
|
|
569
|
+
pollIntervalMs = Math.min(pollIntervalMs * 2, 5000);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
await new Promise((r) => setTimeout(r, pollIntervalMs));
|
|
519
573
|
}
|
|
520
574
|
return {
|
|
521
|
-
content: [{ type: "text", text: JSON.stringify({ success: true, stream, count: stream.length }
|
|
575
|
+
content: [{ type: "text", text: JSON.stringify({ success: true, stream, count: stream.length }) }],
|
|
522
576
|
};
|
|
523
|
-
});
|
|
577
|
+
}));
|
|
524
578
|
server.registerTool("figma_clear_console", {
|
|
525
579
|
description: "Clear the plugin console log buffer.",
|
|
526
580
|
inputSchema: {},
|
|
527
581
|
annotations: { destructiveHint: true },
|
|
528
|
-
}, async () => {
|
|
582
|
+
}, safeToolHandler(async () => {
|
|
529
583
|
const conn = getConnector(bridge);
|
|
530
584
|
await conn.clearConsole();
|
|
531
|
-
return { content: [{ type: "text", text: JSON.stringify({ success: true, message: "Console cleared" }
|
|
532
|
-
});
|
|
585
|
+
return { content: [{ type: "text", text: JSON.stringify({ success: true, message: "Console cleared" }) }] };
|
|
586
|
+
}));
|
|
533
587
|
// ---- set_description, get_component_image, get_component_for_development ----
|
|
534
588
|
server.registerTool("figma_set_description", {
|
|
535
589
|
description: "Set description on a component, component set, or style node. Supports markdown (descriptionMarkdown).",
|
|
@@ -539,11 +593,12 @@ export async function main() {
|
|
|
539
593
|
descriptionMarkdown: z.string().optional(),
|
|
540
594
|
},
|
|
541
595
|
annotations: { destructiveHint: true },
|
|
542
|
-
}, async (p) => {
|
|
596
|
+
}, safeToolHandler(async (p) => {
|
|
597
|
+
invalidateCache();
|
|
543
598
|
const conn = getConnector(bridge);
|
|
544
599
|
const result = await conn.setNodeDescription(p.nodeId, p.description, p.descriptionMarkdown);
|
|
545
|
-
return { content: [{ type: "text", text: JSON.stringify(result
|
|
546
|
-
});
|
|
600
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
601
|
+
}));
|
|
547
602
|
server.registerTool("figma_get_component_image", {
|
|
548
603
|
description: "Get screenshot of a node (component/frame). Returns base64 image. No REST API.",
|
|
549
604
|
inputSchema: {
|
|
@@ -552,11 +607,11 @@ export async function main() {
|
|
|
552
607
|
format: z.enum(["PNG", "JPG"]).optional().default("PNG"),
|
|
553
608
|
},
|
|
554
609
|
annotations: { readOnlyHint: true },
|
|
555
|
-
}, async ({ nodeId, scale, format }) => {
|
|
610
|
+
}, safeToolHandler(async ({ nodeId, scale, format }) => {
|
|
556
611
|
const conn = getConnector(bridge);
|
|
557
612
|
const result = await conn.captureScreenshot(nodeId, { scale, format });
|
|
558
|
-
return { content: [{ type: "text", text: JSON.stringify(result
|
|
559
|
-
});
|
|
613
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
614
|
+
}));
|
|
560
615
|
server.registerTool("figma_get_component_for_development", {
|
|
561
616
|
description: "Get component metadata plus base64 screenshot in one call. For design-to-code workflows.",
|
|
562
617
|
inputSchema: {
|
|
@@ -565,7 +620,7 @@ export async function main() {
|
|
|
565
620
|
format: z.enum(["PNG", "JPG"]).optional().default("PNG"),
|
|
566
621
|
},
|
|
567
622
|
annotations: { readOnlyHint: true },
|
|
568
|
-
}, async ({ nodeId, scale, format }) => {
|
|
623
|
+
}, safeToolHandler(async ({ nodeId, scale, format }) => {
|
|
569
624
|
const conn = getConnector(bridge);
|
|
570
625
|
const [component, screenshot] = await Promise.all([
|
|
571
626
|
conn.getComponentFromPluginUI(nodeId),
|
|
@@ -573,8 +628,8 @@ export async function main() {
|
|
|
573
628
|
]);
|
|
574
629
|
const comp = component?.component ?? component;
|
|
575
630
|
const out = { success: true, component: comp, image: screenshot?.image ?? screenshot?.data };
|
|
576
|
-
return { content: [{ type: "text", text: JSON.stringify(out
|
|
577
|
-
});
|
|
631
|
+
return { content: [{ type: "text", text: JSON.stringify(out) }] };
|
|
632
|
+
}));
|
|
578
633
|
// ---- Batch variables & setup_design_tokens & arrange_component_set ----
|
|
579
634
|
server.registerTool("figma_batch_create_variables", {
|
|
580
635
|
description: "Create up to 100 variables in one call. Each item: collectionId, name, resolvedType (COLOR/FLOAT/STRING/BOOLEAN), value, modeId. Returns created and failed lists.",
|
|
@@ -589,11 +644,12 @@ export async function main() {
|
|
|
589
644
|
})).max(100),
|
|
590
645
|
},
|
|
591
646
|
annotations: { destructiveHint: true },
|
|
592
|
-
}, async ({ items }) => {
|
|
647
|
+
}, safeToolHandler(async ({ items }) => {
|
|
648
|
+
invalidateCache();
|
|
593
649
|
const conn = getConnector(bridge);
|
|
594
650
|
const result = await conn.batchCreateVariables(items);
|
|
595
|
-
return { content: [{ type: "text", text: JSON.stringify({ success: true, ...result }
|
|
596
|
-
});
|
|
651
|
+
return { content: [{ type: "text", text: JSON.stringify({ success: true, ...result }) }] };
|
|
652
|
+
}));
|
|
597
653
|
server.registerTool("figma_batch_update_variables", {
|
|
598
654
|
description: "Update up to 100 variables. Each item: variableId, modeId, value. Returns updated and failed lists.",
|
|
599
655
|
inputSchema: {
|
|
@@ -604,11 +660,12 @@ export async function main() {
|
|
|
604
660
|
})).max(100),
|
|
605
661
|
},
|
|
606
662
|
annotations: { destructiveHint: true },
|
|
607
|
-
}, async ({ items }) => {
|
|
663
|
+
}, safeToolHandler(async ({ items }) => {
|
|
664
|
+
invalidateCache();
|
|
608
665
|
const conn = getConnector(bridge);
|
|
609
666
|
const result = await conn.batchUpdateVariables(items);
|
|
610
|
-
return { content: [{ type: "text", text: JSON.stringify({ success: true, ...result }
|
|
611
|
-
});
|
|
667
|
+
return { content: [{ type: "text", text: JSON.stringify({ success: true, ...result }) }] };
|
|
668
|
+
}));
|
|
612
669
|
server.registerTool("figma_setup_design_tokens", {
|
|
613
670
|
description: "Atomically create a variable collection + modes + variables. Rollback on any error. Params: collectionName, modes (array), tokens (array of { name, type?, value? or values? }).",
|
|
614
671
|
inputSchema: {
|
|
@@ -622,20 +679,22 @@ export async function main() {
|
|
|
622
679
|
})),
|
|
623
680
|
},
|
|
624
681
|
annotations: { destructiveHint: true },
|
|
625
|
-
}, async (p) => {
|
|
682
|
+
}, safeToolHandler(async (p) => {
|
|
683
|
+
invalidateCache();
|
|
626
684
|
const conn = getConnector(bridge);
|
|
627
685
|
const result = await conn.setupDesignTokens(p);
|
|
628
|
-
return { content: [{ type: "text", text: JSON.stringify(result
|
|
629
|
-
});
|
|
686
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
687
|
+
}));
|
|
630
688
|
server.registerTool("figma_arrange_component_set", {
|
|
631
689
|
description: "Combine multiple component nodes into one Figma component set (combineAsVariants). Params: nodeIds (array of at least 2 component node IDs). Returns new component set nodeId.",
|
|
632
690
|
inputSchema: { nodeIds: z.array(z.string()).min(2) },
|
|
633
691
|
annotations: { destructiveHint: true },
|
|
634
|
-
}, async ({ nodeIds }) => {
|
|
692
|
+
}, safeToolHandler(async ({ nodeIds }) => {
|
|
693
|
+
invalidateCache();
|
|
635
694
|
const conn = getConnector(bridge);
|
|
636
695
|
const result = await conn.arrangeComponentSet(nodeIds);
|
|
637
|
-
return { content: [{ type: "text", text: JSON.stringify({ success: true, ...result }
|
|
638
|
-
});
|
|
696
|
+
return { content: [{ type: "text", text: JSON.stringify({ success: true, ...result }) }] };
|
|
697
|
+
}));
|
|
639
698
|
// ---- figma_check_design_parity (design–code gap analysis) ----
|
|
640
699
|
server.registerTool("figma_check_design_parity", {
|
|
641
700
|
description: "Compare Figma design tokens (variables + styles) with code-side tokens. Critical for design-code gap analysis. Returns matching, inFigmaOnly, inCodeOnly, and divergent (same name, different value). Optional codeTokens: JSON string of expected tokens, e.g. {\"primary\": \"#0066cc\", \"spacing.md\": 16} or {\"primary\": {\"value\": \"#0066cc\"}}.",
|
|
@@ -714,7 +773,7 @@ export async function main() {
|
|
|
714
773
|
content: [
|
|
715
774
|
{
|
|
716
775
|
type: "text",
|
|
717
|
-
text: JSON.stringify({ success: false, error: "codeTokens must be valid JSON" }
|
|
776
|
+
text: JSON.stringify({ success: false, error: "codeTokens must be valid JSON" }),
|
|
718
777
|
},
|
|
719
778
|
],
|
|
720
779
|
isError: true,
|
|
@@ -729,16 +788,18 @@ export async function main() {
|
|
|
729
788
|
if (codeVal === undefined) {
|
|
730
789
|
inFigmaOnly.push({ name, value: figVal });
|
|
731
790
|
}
|
|
732
|
-
else if (normalizeForCompare(figVal) === normalizeForCompare(codeVal)) {
|
|
733
|
-
matching.push({ name, value: figVal });
|
|
734
|
-
}
|
|
735
791
|
else {
|
|
736
|
-
|
|
792
|
+
codeMap.delete(name);
|
|
793
|
+
if (normalizeForCompare(figVal) === normalizeForCompare(codeVal)) {
|
|
794
|
+
matching.push({ name, value: figVal });
|
|
795
|
+
}
|
|
796
|
+
else {
|
|
797
|
+
divergent.push({ name, figmaValue: figVal, codeValue: codeVal });
|
|
798
|
+
}
|
|
737
799
|
}
|
|
738
800
|
}
|
|
739
801
|
for (const [name, codeVal] of codeMap) {
|
|
740
|
-
|
|
741
|
-
inCodeOnly.push({ name, value: codeVal });
|
|
802
|
+
inCodeOnly.push({ name, value: codeVal });
|
|
742
803
|
}
|
|
743
804
|
const out = {
|
|
744
805
|
success: true,
|
|
@@ -753,12 +814,12 @@ export async function main() {
|
|
|
753
814
|
inFigmaOnly,
|
|
754
815
|
inCodeOnly,
|
|
755
816
|
};
|
|
756
|
-
return { content: [{ type: "text", text: JSON.stringify(out
|
|
817
|
+
return { content: [{ type: "text", text: JSON.stringify(out) }] };
|
|
757
818
|
}
|
|
758
819
|
catch (err) {
|
|
759
820
|
const msg = err instanceof Error ? err.message : String(err);
|
|
760
821
|
return {
|
|
761
|
-
content: [{ type: "text", text: JSON.stringify({ success: false, error: msg }
|
|
822
|
+
content: [{ type: "text", text: JSON.stringify({ success: false, error: msg }) }],
|
|
762
823
|
isError: true,
|
|
763
824
|
};
|
|
764
825
|
}
|
|
@@ -822,12 +883,12 @@ export async function main() {
|
|
|
822
883
|
: { id: s.id, name: s.name, fontSize: s.fontSize ?? s.style?.fontSize, fontName: s.fontName ?? s.style?.fontName }),
|
|
823
884
|
},
|
|
824
885
|
};
|
|
825
|
-
return { content: [{ type: "text", text: JSON.stringify(out
|
|
886
|
+
return { content: [{ type: "text", text: JSON.stringify(out) }] };
|
|
826
887
|
}
|
|
827
888
|
catch (err) {
|
|
828
889
|
const msg = err instanceof Error ? err.message : String(err);
|
|
829
890
|
return {
|
|
830
|
-
content: [{ type: "text", text: JSON.stringify({ success: false, error: msg }
|
|
891
|
+
content: [{ type: "text", text: JSON.stringify({ success: false, error: msg }) }],
|
|
831
892
|
isError: true,
|
|
832
893
|
};
|
|
833
894
|
}
|
|
@@ -871,7 +932,7 @@ export async function main() {
|
|
|
871
932
|
message: msg,
|
|
872
933
|
...(startError && { startError }),
|
|
873
934
|
...(portHint && { portHint }),
|
|
874
|
-
}
|
|
935
|
+
}),
|
|
875
936
|
}],
|
|
876
937
|
};
|
|
877
938
|
});
|
|
@@ -913,10 +974,10 @@ export async function main() {
|
|
|
913
974
|
return { id: frame.id, name: frame.name, width: frame.width, height: frame.height, x: frame.x, y: frame.y };
|
|
914
975
|
`;
|
|
915
976
|
const result = await conn.executeCodeViaUI(code, 10000);
|
|
916
|
-
return { content: [{ type: "text", text: JSON.stringify({ success: true, ...result }
|
|
977
|
+
return { content: [{ type: "text", text: JSON.stringify({ success: true, ...result }) }] };
|
|
917
978
|
}
|
|
918
979
|
catch (err) {
|
|
919
|
-
return { content: [{ type: "text", text: JSON.stringify({ success: false, error: err instanceof Error ? err.message : String(err) }
|
|
980
|
+
return { content: [{ type: "text", text: JSON.stringify({ success: false, error: err instanceof Error ? err.message : String(err) }) }], isError: true };
|
|
920
981
|
}
|
|
921
982
|
});
|
|
922
983
|
server.registerTool("figma_create_text", {
|
|
@@ -947,10 +1008,10 @@ export async function main() {
|
|
|
947
1008
|
return { id: node.id, name: node.name, characters: node.characters };
|
|
948
1009
|
`;
|
|
949
1010
|
const result = await conn.executeCodeViaUI(code, 10000);
|
|
950
|
-
return { content: [{ type: "text", text: JSON.stringify({ success: true, ...result }
|
|
1011
|
+
return { content: [{ type: "text", text: JSON.stringify({ success: true, ...result }) }] };
|
|
951
1012
|
}
|
|
952
1013
|
catch (err) {
|
|
953
|
-
return { content: [{ type: "text", text: JSON.stringify({ success: false, error: err instanceof Error ? err.message : String(err) }
|
|
1014
|
+
return { content: [{ type: "text", text: JSON.stringify({ success: false, error: err instanceof Error ? err.message : String(err) }) }], isError: true };
|
|
954
1015
|
}
|
|
955
1016
|
});
|
|
956
1017
|
server.registerTool("figma_create_rectangle", {
|
|
@@ -979,10 +1040,10 @@ export async function main() {
|
|
|
979
1040
|
return { id: rect.id, name: rect.name };
|
|
980
1041
|
`;
|
|
981
1042
|
const result = await conn.executeCodeViaUI(code, 10000);
|
|
982
|
-
return { content: [{ type: "text", text: JSON.stringify({ success: true, ...result }
|
|
1043
|
+
return { content: [{ type: "text", text: JSON.stringify({ success: true, ...result }) }] };
|
|
983
1044
|
}
|
|
984
1045
|
catch (err) {
|
|
985
|
-
return { content: [{ type: "text", text: JSON.stringify({ success: false, error: err instanceof Error ? err.message : String(err) }
|
|
1046
|
+
return { content: [{ type: "text", text: JSON.stringify({ success: false, error: err instanceof Error ? err.message : String(err) }) }], isError: true };
|
|
986
1047
|
}
|
|
987
1048
|
});
|
|
988
1049
|
server.registerTool("figma_create_group", {
|
|
@@ -1006,10 +1067,10 @@ export async function main() {
|
|
|
1006
1067
|
return { id: group.id, name: group.name, childCount: group.children.length };
|
|
1007
1068
|
`;
|
|
1008
1069
|
const result = await conn.executeCodeViaUI(code, 10000);
|
|
1009
|
-
return { content: [{ type: "text", text: JSON.stringify({ success: true, ...result }
|
|
1070
|
+
return { content: [{ type: "text", text: JSON.stringify({ success: true, ...result }) }] };
|
|
1010
1071
|
}
|
|
1011
1072
|
catch (err) {
|
|
1012
|
-
return { content: [{ type: "text", text: JSON.stringify({ success: false, error: err instanceof Error ? err.message : String(err) }
|
|
1073
|
+
return { content: [{ type: "text", text: JSON.stringify({ success: false, error: err instanceof Error ? err.message : String(err) }) }], isError: true };
|
|
1013
1074
|
}
|
|
1014
1075
|
});
|
|
1015
1076
|
// ---- figma_export_nodes (batch SVG/PNG/JPG/PDF export) ----
|
|
@@ -1062,13 +1123,13 @@ export async function main() {
|
|
|
1062
1123
|
...(r.base64 && { base64: r.base64 }),
|
|
1063
1124
|
...(r.error && { error: r.error }),
|
|
1064
1125
|
})),
|
|
1065
|
-
}
|
|
1126
|
+
}),
|
|
1066
1127
|
});
|
|
1067
1128
|
return { content: contentBlocks };
|
|
1068
1129
|
}
|
|
1069
1130
|
catch (err) {
|
|
1070
1131
|
return {
|
|
1071
|
-
content: [{ type: "text", text: JSON.stringify({ success: false, error: err instanceof Error ? err.message : String(err) }
|
|
1132
|
+
content: [{ type: "text", text: JSON.stringify({ success: false, error: err instanceof Error ? err.message : String(err) }) }],
|
|
1072
1133
|
isError: true,
|
|
1073
1134
|
};
|
|
1074
1135
|
}
|
|
@@ -1087,7 +1148,7 @@ export async function main() {
|
|
|
1087
1148
|
const code = `
|
|
1088
1149
|
if (!figma.teamLibrary) return { success: false, error: "teamLibrary API not available" };
|
|
1089
1150
|
const availableLibs = await figma.teamLibrary.getAvailableLibraryVariableCollectionsAsync();
|
|
1090
|
-
const availableComps =
|
|
1151
|
+
const availableComps = typeof figma.teamLibrary.getAvailableLibraryComponentsAsync === 'function' ? await figma.teamLibrary.getAvailableLibraryComponentsAsync() : [];
|
|
1091
1152
|
return {
|
|
1092
1153
|
variableCollections: availableLibs.map(c => ({ name: c.name, key: c.key, libraryName: c.libraryName })),
|
|
1093
1154
|
note: "Use figma_search_components for file-local components. Team library component search requires REST API (figma_rest_api)."
|
|
@@ -1099,10 +1160,10 @@ export async function main() {
|
|
|
1099
1160
|
const q = query.toLowerCase();
|
|
1100
1161
|
data.variableCollections = data.variableCollections.filter((c) => (c.name || "").toLowerCase().includes(q) || (c.libraryName || "").toLowerCase().includes(q));
|
|
1101
1162
|
}
|
|
1102
|
-
return { content: [{ type: "text", text: JSON.stringify({ success: true, ...data }
|
|
1163
|
+
return { content: [{ type: "text", text: JSON.stringify({ success: true, ...data }) }] };
|
|
1103
1164
|
}
|
|
1104
1165
|
catch (err) {
|
|
1105
|
-
return { content: [{ type: "text", text: JSON.stringify({ success: false, error: err instanceof Error ? err.message : String(err) }
|
|
1166
|
+
return { content: [{ type: "text", text: JSON.stringify({ success: false, error: err instanceof Error ? err.message : String(err) }) }], isError: true };
|
|
1106
1167
|
}
|
|
1107
1168
|
});
|
|
1108
1169
|
// ---- figma_plugin_diagnostics ----
|
|
@@ -1132,7 +1193,7 @@ export async function main() {
|
|
|
1132
1193
|
hasRestToken: !!tokenInfo,
|
|
1133
1194
|
rateLimit: tokenInfo?.rateLimit || null,
|
|
1134
1195
|
nodeVersion: process.version,
|
|
1135
|
-
}
|
|
1196
|
+
}),
|
|
1136
1197
|
}],
|
|
1137
1198
|
};
|
|
1138
1199
|
});
|
|
@@ -1154,7 +1215,7 @@ export async function main() {
|
|
|
1154
1215
|
text: JSON.stringify({
|
|
1155
1216
|
success: false,
|
|
1156
1217
|
error: "Port değişikliği zaten devam ediyor. Lütfen tamamlanmasını bekleyin.",
|
|
1157
|
-
}
|
|
1218
|
+
}),
|
|
1158
1219
|
}],
|
|
1159
1220
|
isError: true,
|
|
1160
1221
|
};
|
|
@@ -1173,7 +1234,7 @@ export async function main() {
|
|
|
1173
1234
|
previousPort: oldPort,
|
|
1174
1235
|
newPort: result.port,
|
|
1175
1236
|
message: `Bridge restarted on port ${result.port}. Figma plugin'de Port: ${result.port} ayarlayın ve bağlanmasını bekleyin.`,
|
|
1176
|
-
}
|
|
1237
|
+
}),
|
|
1177
1238
|
}],
|
|
1178
1239
|
};
|
|
1179
1240
|
}
|
|
@@ -1187,7 +1248,7 @@ export async function main() {
|
|
|
1187
1248
|
attemptedPort: newPort,
|
|
1188
1249
|
error: result.error || "Port bind failed",
|
|
1189
1250
|
message: `Port ${newPort} bağlanamadı. Başka bir port deneyin (5454–5470).`,
|
|
1190
|
-
}
|
|
1251
|
+
}),
|
|
1191
1252
|
}],
|
|
1192
1253
|
isError: true,
|
|
1193
1254
|
};
|
|
@@ -1208,7 +1269,7 @@ export async function main() {
|
|
|
1208
1269
|
}, async ({ token }) => {
|
|
1209
1270
|
if (!token.startsWith("figd_")) {
|
|
1210
1271
|
return {
|
|
1211
|
-
content: [{ type: "text", text: JSON.stringify({ success: false, error: "Token must start with 'figd_'" }
|
|
1272
|
+
content: [{ type: "text", text: JSON.stringify({ success: false, error: "Token must start with 'figd_'" }) }],
|
|
1212
1273
|
isError: true,
|
|
1213
1274
|
};
|
|
1214
1275
|
}
|
|
@@ -1223,7 +1284,7 @@ export async function main() {
|
|
|
1223
1284
|
clearTimeout(timeout);
|
|
1224
1285
|
if (!res.ok) {
|
|
1225
1286
|
return {
|
|
1226
|
-
content: [{ type: "text", text: JSON.stringify({ success: false, error: `Token validation failed: ${res.status} ${res.statusText}` }
|
|
1287
|
+
content: [{ type: "text", text: JSON.stringify({ success: false, error: `Token validation failed: ${res.status} ${res.statusText}` }) }],
|
|
1227
1288
|
isError: true,
|
|
1228
1289
|
};
|
|
1229
1290
|
}
|
|
@@ -1241,12 +1302,12 @@ export async function main() {
|
|
|
1241
1302
|
user: me.handle || me.email || "unknown",
|
|
1242
1303
|
message: "Token set. REST API tools are now available.",
|
|
1243
1304
|
rateLimit: limit > 0 ? { remaining, limit, resetAt } : undefined,
|
|
1244
|
-
}
|
|
1305
|
+
}) }],
|
|
1245
1306
|
};
|
|
1246
1307
|
}
|
|
1247
1308
|
catch (err) {
|
|
1248
1309
|
return {
|
|
1249
|
-
content: [{ type: "text", text: JSON.stringify({ success: false, error: `Token validation error: ${err instanceof Error ? err.message : String(err)}` }
|
|
1310
|
+
content: [{ type: "text", text: JSON.stringify({ success: false, error: `Token validation error: ${err instanceof Error ? err.message : String(err)}` }) }],
|
|
1250
1311
|
isError: true,
|
|
1251
1312
|
};
|
|
1252
1313
|
}
|
|
@@ -1256,7 +1317,7 @@ export async function main() {
|
|
|
1256
1317
|
inputSchema: {},
|
|
1257
1318
|
}, async () => {
|
|
1258
1319
|
bridge.clearFigmaRestToken();
|
|
1259
|
-
return { content: [{ type: "text", text: JSON.stringify({ success: true, message: "Token cleared." }
|
|
1320
|
+
return { content: [{ type: "text", text: JSON.stringify({ success: true, message: "Token cleared." }) }] };
|
|
1260
1321
|
});
|
|
1261
1322
|
server.registerTool("figma_rest_api", {
|
|
1262
1323
|
description: "Call Figma REST API directly. Requires a token set via figma_set_rest_token. " +
|
|
@@ -1276,7 +1337,7 @@ export async function main() {
|
|
|
1276
1337
|
content: [{ type: "text", text: JSON.stringify({
|
|
1277
1338
|
success: false,
|
|
1278
1339
|
error: "No Figma REST API token set. Use figma_set_rest_token first. Or enter token in Figma plugin Advanced panel.",
|
|
1279
|
-
}
|
|
1340
|
+
}) }],
|
|
1280
1341
|
isError: true,
|
|
1281
1342
|
};
|
|
1282
1343
|
}
|
|
@@ -1289,7 +1350,7 @@ export async function main() {
|
|
|
1289
1350
|
success: false,
|
|
1290
1351
|
error: `API rate limit exhausted (0/${rl.limit}). Resets at ${resetDate.toISOString()}. Wait and retry.`,
|
|
1291
1352
|
rateLimit: rl,
|
|
1292
|
-
}
|
|
1353
|
+
}) }],
|
|
1293
1354
|
isError: true,
|
|
1294
1355
|
};
|
|
1295
1356
|
}
|
|
@@ -1332,7 +1393,7 @@ export async function main() {
|
|
|
1332
1393
|
success: false, status: 429,
|
|
1333
1394
|
error: `Rate limited. ${attempt + 1} attempts, total ${Math.round(elapsed / 1000)}s. Retry later.`,
|
|
1334
1395
|
rateLimit: limit > 0 ? { remaining, limit, resetAt } : undefined,
|
|
1335
|
-
}
|
|
1396
|
+
}) }],
|
|
1336
1397
|
isError: true,
|
|
1337
1398
|
};
|
|
1338
1399
|
}
|
|
@@ -1354,7 +1415,7 @@ export async function main() {
|
|
|
1354
1415
|
success: false, status: res.status, statusText: res.statusText,
|
|
1355
1416
|
error: responseData,
|
|
1356
1417
|
rateLimit: limit > 0 ? { remaining, limit, resetAt } : undefined,
|
|
1357
|
-
}
|
|
1418
|
+
}) }],
|
|
1358
1419
|
isError: true,
|
|
1359
1420
|
};
|
|
1360
1421
|
}
|
|
@@ -1381,7 +1442,7 @@ export async function main() {
|
|
|
1381
1442
|
data: result.data,
|
|
1382
1443
|
...(result.wasTruncated && { _responseGuard: { originalSizeKB: Math.round(result.originalSizeKB), truncatedSizeKB: Math.round(result.truncatedSizeKB) } }),
|
|
1383
1444
|
rateLimit: limit > 0 ? { remaining, limit, resetAt } : undefined,
|
|
1384
|
-
}
|
|
1445
|
+
}),
|
|
1385
1446
|
});
|
|
1386
1447
|
return { content: contentBlocks };
|
|
1387
1448
|
}
|
|
@@ -1396,14 +1457,14 @@ export async function main() {
|
|
|
1396
1457
|
content: [{ type: "text", text: JSON.stringify({
|
|
1397
1458
|
success: false,
|
|
1398
1459
|
error: `REST API call failed after ${attempt + 1} attempts: ${err instanceof Error ? err.message : String(err)}`,
|
|
1399
|
-
}
|
|
1460
|
+
}) }],
|
|
1400
1461
|
isError: true,
|
|
1401
1462
|
};
|
|
1402
1463
|
}
|
|
1403
1464
|
}
|
|
1404
1465
|
// Should not reach here
|
|
1405
1466
|
return {
|
|
1406
|
-
content: [{ type: "text", text: JSON.stringify({ success: false, error: "Unexpected: all retries exhausted" }
|
|
1467
|
+
content: [{ type: "text", text: JSON.stringify({ success: false, error: "Unexpected: all retries exhausted" }) }],
|
|
1407
1468
|
isError: true,
|
|
1408
1469
|
};
|
|
1409
1470
|
});
|
|
@@ -1418,7 +1479,7 @@ export async function main() {
|
|
|
1418
1479
|
content: [{ type: "text", text: JSON.stringify({
|
|
1419
1480
|
hasToken: false,
|
|
1420
1481
|
message: "No token set. Use figma_set_rest_token to add one.",
|
|
1421
|
-
}
|
|
1482
|
+
}) }],
|
|
1422
1483
|
};
|
|
1423
1484
|
}
|
|
1424
1485
|
const rl = tokenInfo.rateLimit;
|
|
@@ -1439,11 +1500,12 @@ export async function main() {
|
|
|
1439
1500
|
rateLimit: rl || null,
|
|
1440
1501
|
...(warning && { warning }),
|
|
1441
1502
|
message: warning || "Token is set. REST API tools are available.",
|
|
1442
|
-
}
|
|
1503
|
+
}) }],
|
|
1443
1504
|
};
|
|
1444
1505
|
});
|
|
1445
1506
|
const shutdown = () => {
|
|
1446
1507
|
logger.info("Shutting down plugin-only MCP server...");
|
|
1508
|
+
closeAuditLog();
|
|
1447
1509
|
try {
|
|
1448
1510
|
bridge.stop();
|
|
1449
1511
|
}
|