@autobest-ui/agent 1.0.1 → 1.0.2
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/README.md +3 -3
- package/mcp/azurepr-mcp-bridge/azure-devops.js +82 -112
- package/mcp/azurepr-mcp-bridge/index.js +21 -25
- package/mcp/azurepr-mcp-bridge/index.test.js +50 -59
- package/mcp/figma-mcp-bridge/src/tools/index.js +782 -361
- package/mcp/figma-mcp-bridge/src/tools/mutations.js +3566 -2160
- package/mcp/figma-mcp-bridge/src/tools/nodes.js +53 -32
- package/mcp/figma-mcp-bridge/src/tools/pages.js +38 -23
- package/mcp/rag-mcp-bridge/README.md +2 -2
- package/mcp/rag-mcp-bridge/config.toml.example +1 -1
- package/mcp/rag-mcp-bridge/index.js +80 -125
- package/mcp/rag-mcp-bridge/index.test.js +21 -25
- package/package.json +1 -1
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
* Tool registry - registers all Figma tools with the MCP server
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { z } from 'zod';
|
|
6
5
|
import { readFileSync } from 'fs';
|
|
7
6
|
import { fileURLToPath } from 'url';
|
|
8
7
|
import { dirname, join } from 'path';
|
|
8
|
+
import { z } from 'zod';
|
|
9
9
|
import { handleGetContext } from './context.js';
|
|
10
10
|
import { handleListPages } from './pages.js';
|
|
11
11
|
import { handleGetNodes } from './nodes.js';
|
|
@@ -42,14 +42,9 @@ import {
|
|
|
42
42
|
handleCreateLine,
|
|
43
43
|
handleSetConstraints,
|
|
44
44
|
// Phase 4 commands
|
|
45
|
-
handleCreatePolygon,
|
|
46
|
-
handleBooleanOperation,
|
|
47
|
-
handleZoomToNode,
|
|
48
|
-
handleSetBlendMode,
|
|
49
45
|
handleDetachInstance,
|
|
50
46
|
// Phase 5 commands
|
|
51
47
|
handleSetLayoutAlign,
|
|
52
|
-
handleCreateVector,
|
|
53
48
|
handleRenameNode,
|
|
54
49
|
handleReorderNode,
|
|
55
50
|
// Smart Query commands
|
|
@@ -75,7 +70,6 @@ import {
|
|
|
75
70
|
handleCreatePage,
|
|
76
71
|
handleRenamePage,
|
|
77
72
|
handleDeletePage,
|
|
78
|
-
handleReorderPage,
|
|
79
73
|
// Node Structure commands
|
|
80
74
|
handleReparentNodes,
|
|
81
75
|
handleMoveToPage,
|
|
@@ -84,7 +78,6 @@ import {
|
|
|
84
78
|
// Additional commands
|
|
85
79
|
handleDuplicatePage,
|
|
86
80
|
handleSetRotation,
|
|
87
|
-
handleSetLayoutGrids,
|
|
88
81
|
handleCombineAsVariants,
|
|
89
82
|
// FigJam commands
|
|
90
83
|
handleCreateSticky,
|
|
@@ -150,39 +143,88 @@ const colorSchema = z.union([
|
|
|
150
143
|
// ============================================================
|
|
151
144
|
|
|
152
145
|
const SHAPE_TYPES = [
|
|
153
|
-
'SQUARE',
|
|
154
|
-
'
|
|
155
|
-
'
|
|
156
|
-
'
|
|
157
|
-
'
|
|
158
|
-
'
|
|
159
|
-
'
|
|
160
|
-
'
|
|
161
|
-
'
|
|
162
|
-
'
|
|
146
|
+
'SQUARE',
|
|
147
|
+
'ELLIPSE',
|
|
148
|
+
'ROUNDED_RECTANGLE',
|
|
149
|
+
'DIAMOND',
|
|
150
|
+
'TRIANGLE_UP',
|
|
151
|
+
'TRIANGLE_DOWN',
|
|
152
|
+
'PARALLELOGRAM_RIGHT',
|
|
153
|
+
'PARALLELOGRAM_LEFT',
|
|
154
|
+
'ENG_DATABASE',
|
|
155
|
+
'ENG_QUEUE',
|
|
156
|
+
'ENG_FILE',
|
|
157
|
+
'ENG_FOLDER',
|
|
158
|
+
'TRAPEZOID',
|
|
159
|
+
'PREDEFINED_PROCESS',
|
|
160
|
+
'SHIELD',
|
|
161
|
+
'DOCUMENT_SINGLE',
|
|
162
|
+
'DOCUMENT_MULTIPLE',
|
|
163
|
+
'MANUAL_INPUT',
|
|
164
|
+
'HEXAGON',
|
|
165
|
+
'CHEVRON',
|
|
166
|
+
'PENTAGON',
|
|
167
|
+
'OCTAGON',
|
|
168
|
+
'STAR',
|
|
169
|
+
'PLUS',
|
|
170
|
+
'ARROW_LEFT',
|
|
171
|
+
'ARROW_RIGHT',
|
|
172
|
+
'SUMMING_JUNCTION',
|
|
173
|
+
'OR',
|
|
174
|
+
'SPEECH_BUBBLE',
|
|
175
|
+
'INTERNAL_STORAGE'
|
|
163
176
|
];
|
|
164
177
|
|
|
165
178
|
const CONNECTOR_LINE_TYPES = ['ELBOWED', 'STRAIGHT', 'CURVED'];
|
|
166
179
|
const CONNECTOR_STROKE_CAPS = [
|
|
167
|
-
'NONE',
|
|
168
|
-
'
|
|
180
|
+
'NONE',
|
|
181
|
+
'ARROW_EQUILATERAL',
|
|
182
|
+
'ARROW_LINES',
|
|
183
|
+
'TRIANGLE_FILLED',
|
|
184
|
+
'CIRCLE_FILLED',
|
|
185
|
+
'DIAMOND_FILLED'
|
|
169
186
|
];
|
|
170
187
|
const MAGNETS = ['NONE', 'AUTO', 'TOP', 'LEFT', 'BOTTOM', 'RIGHT', 'CENTER'];
|
|
171
188
|
const CODE_LANGUAGES = [
|
|
172
|
-
'TYPESCRIPT',
|
|
173
|
-
'
|
|
174
|
-
'
|
|
189
|
+
'TYPESCRIPT',
|
|
190
|
+
'CPP',
|
|
191
|
+
'RUBY',
|
|
192
|
+
'CSS',
|
|
193
|
+
'JAVASCRIPT',
|
|
194
|
+
'HTML',
|
|
195
|
+
'JSON',
|
|
196
|
+
'GRAPHQL',
|
|
197
|
+
'PYTHON',
|
|
198
|
+
'GO',
|
|
199
|
+
'SQL',
|
|
200
|
+
'SWIFT',
|
|
201
|
+
'KOTLIN',
|
|
202
|
+
'RUST',
|
|
203
|
+
'BASH',
|
|
204
|
+
'PLAINTEXT',
|
|
205
|
+
'DART'
|
|
175
206
|
];
|
|
176
207
|
|
|
177
208
|
// Endpoint spec for figma_create_connector / figma_set_connector.
|
|
178
209
|
// One of: { nodeId, magnet }, { nodeId, position }, { position }.
|
|
179
210
|
const connectorEndpointSchema = z.object({
|
|
180
|
-
nodeId: z
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
211
|
+
nodeId: z
|
|
212
|
+
.string()
|
|
213
|
+
.optional()
|
|
214
|
+
.describe('ID of the node this endpoint attaches to. Omit for a free-floating endpoint.'),
|
|
215
|
+
magnet: z
|
|
216
|
+
.enum(MAGNETS)
|
|
217
|
+
.optional()
|
|
218
|
+
.describe(
|
|
219
|
+
'Where on the target node the connector attaches. AUTO is recommended; STRAIGHT lines only support CENTER or NONE.'
|
|
220
|
+
),
|
|
221
|
+
position: z
|
|
222
|
+
.object({
|
|
223
|
+
x: z.number(),
|
|
224
|
+
y: z.number()
|
|
225
|
+
})
|
|
226
|
+
.optional()
|
|
227
|
+
.describe('Fixed position. Relative to the target node when nodeId is set; absolute canvas coordinates otherwise.')
|
|
186
228
|
});
|
|
187
229
|
|
|
188
230
|
/**
|
|
@@ -201,15 +243,21 @@ export function registerTools(server, bridge) {
|
|
|
201
243
|
'Get information about the MCP server: package version, WebSocket port, connection state, and connected document info.',
|
|
202
244
|
{},
|
|
203
245
|
async () => ({
|
|
204
|
-
content: [
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
246
|
+
content: [
|
|
247
|
+
{
|
|
248
|
+
type: 'text',
|
|
249
|
+
text: JSON.stringify(
|
|
250
|
+
{
|
|
251
|
+
version: pkgVersion,
|
|
252
|
+
port: bridge.port,
|
|
253
|
+
connected: bridge.isConnected(),
|
|
254
|
+
documentInfo: bridge.getDocumentInfo()
|
|
255
|
+
},
|
|
256
|
+
null,
|
|
257
|
+
2
|
|
258
|
+
)
|
|
259
|
+
}
|
|
260
|
+
]
|
|
213
261
|
})
|
|
214
262
|
);
|
|
215
263
|
|
|
@@ -239,9 +287,15 @@ export function registerTools(server, bridge) {
|
|
|
239
287
|
'Get detailed information about specific Figma nodes by their IDs. Returns node properties including type, position, size, fills, strokes (strokeWeight reads "MIXED" plus the four per-side weights when sides differ), auto-layout (including layoutWrap and counterAxisSpacing), clipsContent, node-level boundVariables (which properties are bound to which variables), explicitVariableModes (variable modes pinned on the node), and more. Composite instance-sublayer IDs (the "I<instanceId>;<childId>" form) resolve reliably — if the direct lookup misses, the instance root is resolved and its subtree searched. IDs that genuinely do not exist come back in notFound with an explanation in notFoundDetails. TIP: Use figma_search_nodes or figma_get_children FIRST to find node IDs efficiently, then use this tool only for nodes you need full details on.',
|
|
240
288
|
{
|
|
241
289
|
nodeIds: z.array(z.string()).describe('Array of Figma node IDs (e.g., ["1:23", "4:56"])'),
|
|
242
|
-
depth: z
|
|
290
|
+
depth: z
|
|
291
|
+
.enum(['minimal', 'compact', 'full'])
|
|
292
|
+
.optional()
|
|
293
|
+
.default('full')
|
|
294
|
+
.describe(
|
|
295
|
+
'Detail level: "minimal" (~5 props: id, name, type, childIds), "compact" (~10 props: + x/y/width/height + childIds), "full" (all ~40 props). Use minimal/compact for tree traversal to reduce tokens.'
|
|
296
|
+
)
|
|
243
297
|
},
|
|
244
|
-
async
|
|
298
|
+
async args => handleGetNodes(bridge, args)
|
|
245
299
|
);
|
|
246
300
|
|
|
247
301
|
// ============================================================
|
|
@@ -256,7 +310,7 @@ export function registerTools(server, bridge) {
|
|
|
256
310
|
nodeId: z.string().describe('The node ID to modify'),
|
|
257
311
|
fills: colorSchema.describe('Fill color - use { color: "#RRGGBB" } for simple colors')
|
|
258
312
|
},
|
|
259
|
-
async
|
|
313
|
+
async args => handleSetFills(bridge, args)
|
|
260
314
|
);
|
|
261
315
|
|
|
262
316
|
// figma_set_strokes - Set stroke colors and weights on a node
|
|
@@ -265,14 +319,35 @@ export function registerTools(server, bridge) {
|
|
|
265
319
|
'Set stroke color and/or weight. Accepts hex shorthand or strokes array. Supports per-side weights (strokeTopWeight etc.) for border-top-only style dividers — no need to fake them with 1px rectangles. Omit `strokes` to change weights only.',
|
|
266
320
|
{
|
|
267
321
|
nodeId: z.string().describe('The node ID to modify'),
|
|
268
|
-
strokes: colorSchema
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
322
|
+
strokes: colorSchema
|
|
323
|
+
.optional()
|
|
324
|
+
.describe(
|
|
325
|
+
'Stroke color - use { color: "#RRGGBB" } for simple colors. Omit to leave existing stroke colors untouched.'
|
|
326
|
+
),
|
|
327
|
+
strokeWeight: z
|
|
328
|
+
.number()
|
|
329
|
+
.optional()
|
|
330
|
+
.describe('Uniform stroke weight in pixels (applied before any per-side weights)'),
|
|
331
|
+
strokeTopWeight: z
|
|
332
|
+
.number()
|
|
333
|
+
.optional()
|
|
334
|
+
.describe(
|
|
335
|
+
'Top stroke weight in pixels. RECTANGLE / FRAME / COMPONENT / COMPONENT_SET / INSTANCE / SLOT / SLIDE only — errors on other types.'
|
|
336
|
+
),
|
|
337
|
+
strokeRightWeight: z
|
|
338
|
+
.number()
|
|
339
|
+
.optional()
|
|
340
|
+
.describe('Right stroke weight in pixels (same node-type restriction as strokeTopWeight)'),
|
|
341
|
+
strokeBottomWeight: z
|
|
342
|
+
.number()
|
|
343
|
+
.optional()
|
|
344
|
+
.describe('Bottom stroke weight in pixels (same node-type restriction as strokeTopWeight)'),
|
|
345
|
+
strokeLeftWeight: z
|
|
346
|
+
.number()
|
|
347
|
+
.optional()
|
|
348
|
+
.describe('Left stroke weight in pixels (same node-type restriction as strokeTopWeight)')
|
|
349
|
+
},
|
|
350
|
+
async args => handleSetStrokes(bridge, args)
|
|
276
351
|
);
|
|
277
352
|
|
|
278
353
|
// figma_create_rectangle - Create a new rectangle
|
|
@@ -288,7 +363,7 @@ export function registerTools(server, bridge) {
|
|
|
288
363
|
fills: colorSchema.optional().describe('Fill color'),
|
|
289
364
|
parentId: z.string().optional().describe('Parent node ID (defaults to current page)')
|
|
290
365
|
},
|
|
291
|
-
async
|
|
366
|
+
async args => handleCreateRectangle(bridge, args)
|
|
292
367
|
);
|
|
293
368
|
|
|
294
369
|
// figma_set_text - Set text content on a text node
|
|
@@ -299,7 +374,7 @@ export function registerTools(server, bridge) {
|
|
|
299
374
|
nodeId: z.string().describe('The text node ID to modify'),
|
|
300
375
|
text: z.string().describe('The new text content')
|
|
301
376
|
},
|
|
302
|
-
async
|
|
377
|
+
async args => handleSetText(bridge, args)
|
|
303
378
|
);
|
|
304
379
|
|
|
305
380
|
// figma_clone_nodes - Clone/duplicate nodes
|
|
@@ -309,12 +384,15 @@ export function registerTools(server, bridge) {
|
|
|
309
384
|
{
|
|
310
385
|
nodeIds: z.array(z.string()).describe('Array of node IDs to clone'),
|
|
311
386
|
parentId: z.string().optional().describe('Parent node ID for clones (optional)'),
|
|
312
|
-
offset: z
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
387
|
+
offset: z
|
|
388
|
+
.object({
|
|
389
|
+
x: z.number().optional().default(20).describe('X offset from original'),
|
|
390
|
+
y: z.number().optional().default(20).describe('Y offset from original')
|
|
391
|
+
})
|
|
392
|
+
.optional()
|
|
393
|
+
.describe('Position offset for cloned nodes')
|
|
316
394
|
},
|
|
317
|
-
async
|
|
395
|
+
async args => handleCloneNodes(bridge, args)
|
|
318
396
|
);
|
|
319
397
|
|
|
320
398
|
// ============================================================
|
|
@@ -328,7 +406,7 @@ export function registerTools(server, bridge) {
|
|
|
328
406
|
{
|
|
329
407
|
nodeIds: z.array(z.string()).describe('Array of node IDs to delete')
|
|
330
408
|
},
|
|
331
|
-
async
|
|
409
|
+
async args => handleDeleteNodes(bridge, args)
|
|
332
410
|
);
|
|
333
411
|
|
|
334
412
|
// figma_move_nodes - Move nodes
|
|
@@ -341,47 +419,71 @@ export function registerTools(server, bridge) {
|
|
|
341
419
|
y: z.number().optional().describe('Y position (absolute) or offset (if relative=true)'),
|
|
342
420
|
relative: z.boolean().optional().default(false).describe('If true, x/y are offsets from current position')
|
|
343
421
|
},
|
|
344
|
-
async
|
|
422
|
+
async args => handleMoveNodes(bridge, args)
|
|
345
423
|
);
|
|
346
424
|
|
|
347
425
|
// figma_resize_nodes - Resize nodes
|
|
348
426
|
server.tool(
|
|
349
427
|
'figma_resize_nodes',
|
|
350
428
|
'Resize one or more nodes to an explicit pixel size. At least one dimension (width or height) must be provided. ' +
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
{
|
|
360
|
-
nodeIds: z
|
|
429
|
+
'PREFER figma_set_layout_align with STRETCH when the goal is "size this child to its parent" — STRETCH works ' +
|
|
430
|
+
'inside instances, survives breakpoint changes, and PRESERVES width/height variable binds; an explicit resize does not. ' +
|
|
431
|
+
'Safety behavior of this tool: (1) resizing an instance sublayer is rejected up front with INSTANCE_SUBLAYER_RESTRICTED ' +
|
|
432
|
+
'(Figma silently ignores it); (2) width/height/min/max variable binds are captured before the resize and re-applied ' +
|
|
433
|
+
'afterwards — recovered binds are listed in "rebound", destroyed ones that could not be recovered are named in "warnings"; ' +
|
|
434
|
+
'(3) the resulting size is read back and compared to the request — a no-op returns success: false with a RESIZE_NO_OP ' +
|
|
435
|
+
'error, and a clamped result (min/max limits, auto-layout sizing) is reported in "warnings". Each node echoes ' +
|
|
436
|
+
'"requested" and "actual" sizes.',
|
|
437
|
+
{
|
|
438
|
+
nodeIds: z
|
|
439
|
+
.array(z.string())
|
|
440
|
+
.describe(
|
|
441
|
+
'Array of node IDs to resize. Instance sublayers are rejected — resize the component master instead.'
|
|
442
|
+
),
|
|
361
443
|
width: z.number().optional().describe('New width in pixels'),
|
|
362
444
|
height: z.number().optional().describe('New height in pixels')
|
|
363
445
|
},
|
|
364
|
-
async
|
|
446
|
+
async args => handleResizeNodes(bridge, args)
|
|
365
447
|
);
|
|
366
448
|
|
|
367
449
|
// figma_set_size_limits - Set or clear min/max width/height
|
|
368
450
|
server.tool(
|
|
369
451
|
'figma_set_size_limits',
|
|
370
452
|
'Set or CLEAR the min/max size limits (minWidth, maxWidth, minHeight, maxHeight) on one or more nodes. ' +
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
453
|
+
'Pass a positive number to set a limit, or explicit null to remove it — null is the documented way to clear a limit, ' +
|
|
454
|
+
'so max-width is no longer a one-way door. Limits apply to auto-layout frames and their direct children; a node that ' +
|
|
455
|
+
'is neither gets a warning because Figma may ignore the value. Every write is read back and verified: a limit that ' +
|
|
456
|
+
'did not apply returns success: false with LIMIT_NOT_APPLIED, and one that would not clear returns LIMIT_NOT_CLEARED. ' +
|
|
457
|
+
'If the field is variable-bound the bound value wins over the literal — the response warns and points at ' +
|
|
458
|
+
'figma_unbind_variable, which also clears the residual literal. The response echoes all four limits for each node.',
|
|
377
459
|
{
|
|
378
460
|
nodeIds: z.array(z.string()).describe('Array of node IDs to update'),
|
|
379
|
-
minWidth: z
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
461
|
+
minWidth: z
|
|
462
|
+
.number()
|
|
463
|
+
.positive()
|
|
464
|
+
.nullable()
|
|
465
|
+
.optional()
|
|
466
|
+
.describe('Minimum width in pixels. null clears the limit. Omit to leave unchanged.'),
|
|
467
|
+
maxWidth: z
|
|
468
|
+
.number()
|
|
469
|
+
.positive()
|
|
470
|
+
.nullable()
|
|
471
|
+
.optional()
|
|
472
|
+
.describe('Maximum width in pixels. null clears the limit. Omit to leave unchanged.'),
|
|
473
|
+
minHeight: z
|
|
474
|
+
.number()
|
|
475
|
+
.positive()
|
|
476
|
+
.nullable()
|
|
477
|
+
.optional()
|
|
478
|
+
.describe('Minimum height in pixels. null clears the limit. Omit to leave unchanged.'),
|
|
479
|
+
maxHeight: z
|
|
480
|
+
.number()
|
|
481
|
+
.positive()
|
|
482
|
+
.nullable()
|
|
483
|
+
.optional()
|
|
484
|
+
.describe('Maximum height in pixels. null clears the limit. Omit to leave unchanged.')
|
|
485
|
+
},
|
|
486
|
+
async args => handleSetSizeLimits(bridge, args)
|
|
385
487
|
);
|
|
386
488
|
|
|
387
489
|
// figma_set_opacity - Set node opacity
|
|
@@ -392,18 +494,18 @@ export function registerTools(server, bridge) {
|
|
|
392
494
|
nodeId: z.string().describe('The node ID to modify'),
|
|
393
495
|
opacity: z.number().min(0).max(1).describe('Opacity value from 0 (transparent) to 1 (opaque)')
|
|
394
496
|
},
|
|
395
|
-
async
|
|
497
|
+
async args => handleSetOpacity(bridge, args)
|
|
396
498
|
);
|
|
397
499
|
|
|
398
500
|
// figma_set_visible - Show or hide nodes
|
|
399
501
|
server.tool(
|
|
400
502
|
'figma_set_visible',
|
|
401
|
-
|
|
503
|
+
"Show or hide nodes. Sets node.visible directly — use this instead of binding a BOOLEAN variable or setting opacity to 0 just to hide something. Response echoes each node's resulting visibility.",
|
|
402
504
|
{
|
|
403
505
|
nodeIds: z.array(z.string()).describe('Array of node IDs to show or hide'),
|
|
404
506
|
visible: z.boolean().describe('true to show, false to hide')
|
|
405
507
|
},
|
|
406
|
-
async
|
|
508
|
+
async args => handleSetVisible(bridge, args)
|
|
407
509
|
);
|
|
408
510
|
|
|
409
511
|
// figma_set_clips_content - Toggle content clipping on frame-like nodes
|
|
@@ -414,7 +516,7 @@ export function registerTools(server, bridge) {
|
|
|
414
516
|
nodeIds: z.array(z.string()).describe('Array of node IDs to modify'),
|
|
415
517
|
clipsContent: z.boolean().describe('true to clip children to the frame bounds, false to let them overflow')
|
|
416
518
|
},
|
|
417
|
-
async
|
|
519
|
+
async args => handleSetClipsContent(bridge, args)
|
|
418
520
|
);
|
|
419
521
|
|
|
420
522
|
// figma_set_corner_radius - Set corner radius
|
|
@@ -429,7 +531,7 @@ export function registerTools(server, bridge) {
|
|
|
429
531
|
bottomLeft: z.number().optional().describe('Bottom-left corner radius'),
|
|
430
532
|
bottomRight: z.number().optional().describe('Bottom-right corner radius')
|
|
431
533
|
},
|
|
432
|
-
async
|
|
534
|
+
async args => handleSetCornerRadius(bridge, args)
|
|
433
535
|
);
|
|
434
536
|
|
|
435
537
|
// figma_group_nodes - Group nodes
|
|
@@ -440,7 +542,7 @@ export function registerTools(server, bridge) {
|
|
|
440
542
|
nodeIds: z.array(z.string()).describe('Array of node IDs to group together'),
|
|
441
543
|
name: z.string().optional().default('Group').describe('Name for the new group')
|
|
442
544
|
},
|
|
443
|
-
async
|
|
545
|
+
async args => handleGroupNodes(bridge, args)
|
|
444
546
|
);
|
|
445
547
|
|
|
446
548
|
// figma_ungroup_nodes - Ungroup nodes
|
|
@@ -450,7 +552,7 @@ export function registerTools(server, bridge) {
|
|
|
450
552
|
{
|
|
451
553
|
nodeIds: z.array(z.string()).describe('Array of group node IDs to ungroup')
|
|
452
554
|
},
|
|
453
|
-
async
|
|
555
|
+
async args => handleUngroupNodes(bridge, args)
|
|
454
556
|
);
|
|
455
557
|
|
|
456
558
|
// ============================================================
|
|
@@ -470,7 +572,7 @@ export function registerTools(server, bridge) {
|
|
|
470
572
|
fills: colorSchema.optional().describe('Fill color'),
|
|
471
573
|
parentId: z.string().optional().describe('Parent node ID (defaults to current page)')
|
|
472
574
|
},
|
|
473
|
-
async
|
|
575
|
+
async args => handleCreateFrame(bridge, args)
|
|
474
576
|
);
|
|
475
577
|
|
|
476
578
|
// figma_create_text - Create a new text node
|
|
@@ -488,7 +590,7 @@ export function registerTools(server, bridge) {
|
|
|
488
590
|
name: z.string().optional().default('Text').describe('Node name'),
|
|
489
591
|
parentId: z.string().optional().describe('Parent node ID (defaults to current page)')
|
|
490
592
|
},
|
|
491
|
-
async
|
|
593
|
+
async args => handleCreateText(bridge, args)
|
|
492
594
|
);
|
|
493
595
|
|
|
494
596
|
// ============================================================
|
|
@@ -502,7 +604,7 @@ export function registerTools(server, bridge) {
|
|
|
502
604
|
{
|
|
503
605
|
nodeIds: z.array(z.string()).describe('Array of node IDs to select (empty array to clear)')
|
|
504
606
|
},
|
|
505
|
-
async
|
|
607
|
+
async args => handleSetSelection(bridge, args)
|
|
506
608
|
);
|
|
507
609
|
|
|
508
610
|
// figma_set_current_page - Switch to a different page
|
|
@@ -512,7 +614,7 @@ export function registerTools(server, bridge) {
|
|
|
512
614
|
{
|
|
513
615
|
pageId: z.string().describe('The page ID to switch to')
|
|
514
616
|
},
|
|
515
|
-
async
|
|
617
|
+
async args => handleSetCurrentPage(bridge, args)
|
|
516
618
|
);
|
|
517
619
|
|
|
518
620
|
// ============================================================
|
|
@@ -523,19 +625,36 @@ export function registerTools(server, bridge) {
|
|
|
523
625
|
server.tool(
|
|
524
626
|
'figma_export_node',
|
|
525
627
|
'Export a node as an image (PNG, SVG, JPG, or PDF). The image is WRITTEN TO DISK and the file path is returned — ' +
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
628
|
+
'read that file to actually view the render (inline base64 cannot be viewed, which is why this is file-first). ' +
|
|
629
|
+
'Response is { success, nodeId, path, format, scale, bytes } with no inline image data. ' +
|
|
630
|
+
'Pass outputPath to choose the destination; omit it and the file lands in the OS temp dir under figma-mcp-bridge/. ' +
|
|
631
|
+
'Set returnBase64: true only if you genuinely need the raw data inline instead of a file. ' +
|
|
632
|
+
'Exporting and LOOKING at the render is the only way to catch composition problems that property readback cannot see.',
|
|
531
633
|
{
|
|
532
634
|
nodeId: z.string().describe('The node ID to export'),
|
|
533
635
|
format: z.enum(['PNG', 'SVG', 'JPG', 'PDF']).optional().default('PNG').describe('Export format'),
|
|
534
|
-
scale: z
|
|
535
|
-
|
|
536
|
-
|
|
636
|
+
scale: z
|
|
637
|
+
.number()
|
|
638
|
+
.optional()
|
|
639
|
+
.default(1)
|
|
640
|
+
.describe(
|
|
641
|
+
'Export scale (1 = 100%, 2 = 200%, etc.). No need to inflate this to make the image viewable — the file on disk is viewable at any size.'
|
|
642
|
+
),
|
|
643
|
+
outputPath: z
|
|
644
|
+
.string()
|
|
645
|
+
.optional()
|
|
646
|
+
.describe(
|
|
647
|
+
'Absolute file path to write the image to. Parent directories are created. Omit for an auto-named file in the OS temp dir.'
|
|
648
|
+
),
|
|
649
|
+
returnBase64: z
|
|
650
|
+
.boolean()
|
|
651
|
+
.optional()
|
|
652
|
+
.default(false)
|
|
653
|
+
.describe(
|
|
654
|
+
'Return base64 image data inline instead of writing a file. Rarely what you want — the inline data cannot be viewed.'
|
|
655
|
+
)
|
|
537
656
|
},
|
|
538
|
-
async
|
|
657
|
+
async args => handleExportNode(bridge, args)
|
|
539
658
|
);
|
|
540
659
|
|
|
541
660
|
// ============================================================
|
|
@@ -554,13 +673,26 @@ export function registerTools(server, bridge) {
|
|
|
554
673
|
name: z.string().optional().default('Ellipse').describe('Node name'),
|
|
555
674
|
fills: colorSchema.optional().describe('Fill color'),
|
|
556
675
|
parentId: z.string().optional().describe('Parent node ID (defaults to current page)'),
|
|
557
|
-
arcData: z
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
676
|
+
arcData: z
|
|
677
|
+
.object({
|
|
678
|
+
startingAngle: z
|
|
679
|
+
.number()
|
|
680
|
+
.min(0)
|
|
681
|
+
.max(6.28319)
|
|
682
|
+
.optional()
|
|
683
|
+
.describe("Starting angle in radians (0 = 3 o'clock)"),
|
|
684
|
+
endingAngle: z
|
|
685
|
+
.number()
|
|
686
|
+
.min(0)
|
|
687
|
+
.max(6.28319)
|
|
688
|
+
.optional()
|
|
689
|
+
.describe('Ending angle in radians (2*PI = full circle)'),
|
|
690
|
+
innerRadius: z.number().min(0).max(1).optional().describe('Inner radius ratio (0 = solid, 0.5 = 50% hole)')
|
|
691
|
+
})
|
|
692
|
+
.optional()
|
|
693
|
+
.describe('Arc data for partial ellipses or rings')
|
|
562
694
|
},
|
|
563
|
-
async
|
|
695
|
+
async args => handleCreateEllipse(bridge, args)
|
|
564
696
|
);
|
|
565
697
|
|
|
566
698
|
// figma_set_effects - Set effects (shadows, blurs)
|
|
@@ -569,43 +701,65 @@ export function registerTools(server, bridge) {
|
|
|
569
701
|
'Set effects. Replaces existing.',
|
|
570
702
|
{
|
|
571
703
|
nodeId: z.string().describe('The node ID to modify'),
|
|
572
|
-
effects: z
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
704
|
+
effects: z
|
|
705
|
+
.array(
|
|
706
|
+
z.union([
|
|
707
|
+
z.object({
|
|
708
|
+
type: z.enum(['DROP_SHADOW', 'INNER_SHADOW']).describe('Shadow type'),
|
|
709
|
+
color: colorSchema.optional().describe('Shadow color'),
|
|
710
|
+
offset: z
|
|
711
|
+
.object({
|
|
712
|
+
x: z.number().describe('Horizontal offset'),
|
|
713
|
+
y: z.number().describe('Vertical offset')
|
|
714
|
+
})
|
|
715
|
+
.optional()
|
|
716
|
+
.describe('Shadow offset'),
|
|
717
|
+
radius: z.number().min(0).optional().describe('Blur radius'),
|
|
718
|
+
spread: z.number().optional().describe('Spread radius'),
|
|
719
|
+
visible: z.boolean().optional().describe('Whether effect is visible'),
|
|
720
|
+
blendMode: z.string().optional().describe('Blend mode')
|
|
721
|
+
}),
|
|
722
|
+
z.object({
|
|
723
|
+
type: z.enum(['LAYER_BLUR', 'BACKGROUND_BLUR']).describe('Blur type'),
|
|
724
|
+
radius: z.number().min(0).describe('Blur radius'),
|
|
725
|
+
visible: z.boolean().optional().describe('Whether effect is visible')
|
|
726
|
+
})
|
|
727
|
+
])
|
|
728
|
+
)
|
|
729
|
+
.describe('Array of effects to apply')
|
|
730
|
+
},
|
|
731
|
+
async args => handleSetEffects(bridge, args)
|
|
593
732
|
);
|
|
594
733
|
|
|
595
734
|
// figma_set_auto_layout - Configure auto-layout
|
|
596
735
|
server.tool(
|
|
597
736
|
'figma_set_auto_layout',
|
|
598
737
|
'Configure auto-layout on a frame. Enables responsive layouts with automatic spacing and alignment. ' +
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
738
|
+
"Changing layoutMode or primaryAxisSizingMode can clear a width/height variable bind, so the node's size binds are " +
|
|
739
|
+
'captured before the change and re-applied after: recovered binds are listed in "rebound", and any bind that could ' +
|
|
740
|
+
'not be restored is named in "warnings" rather than silently lost.',
|
|
602
741
|
{
|
|
603
742
|
nodeId: z.string().describe('The frame node ID to configure'),
|
|
604
|
-
layoutMode: z
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
743
|
+
layoutMode: z
|
|
744
|
+
.enum(['NONE', 'HORIZONTAL', 'VERTICAL'])
|
|
745
|
+
.optional()
|
|
746
|
+
.describe('Layout direction: NONE (disable), HORIZONTAL (row), or VERTICAL (column)'),
|
|
747
|
+
primaryAxisSizingMode: z
|
|
748
|
+
.enum(['FIXED', 'AUTO'])
|
|
749
|
+
.optional()
|
|
750
|
+
.describe('How the frame sizes along the primary axis'),
|
|
751
|
+
counterAxisSizingMode: z
|
|
752
|
+
.enum(['FIXED', 'AUTO'])
|
|
753
|
+
.optional()
|
|
754
|
+
.describe('How the frame sizes along the counter axis'),
|
|
755
|
+
primaryAxisAlignItems: z
|
|
756
|
+
.enum(['MIN', 'CENTER', 'MAX', 'SPACE_BETWEEN'])
|
|
757
|
+
.optional()
|
|
758
|
+
.describe('Alignment of children along primary axis'),
|
|
759
|
+
counterAxisAlignItems: z
|
|
760
|
+
.enum(['MIN', 'CENTER', 'MAX', 'BASELINE'])
|
|
761
|
+
.optional()
|
|
762
|
+
.describe('Alignment of children along counter axis'),
|
|
609
763
|
paddingTop: z.number().min(0).optional().describe('Top padding in pixels'),
|
|
610
764
|
paddingRight: z.number().min(0).optional().describe('Right padding in pixels'),
|
|
611
765
|
paddingBottom: z.number().min(0).optional().describe('Bottom padding in pixels'),
|
|
@@ -614,7 +768,7 @@ export function registerTools(server, bridge) {
|
|
|
614
768
|
counterAxisSpacing: z.number().min(0).optional().describe('Space between rows when wrapped'),
|
|
615
769
|
layoutWrap: z.enum(['NO_WRAP', 'WRAP']).optional().describe('Whether to wrap items to new rows/columns')
|
|
616
770
|
},
|
|
617
|
-
async
|
|
771
|
+
async args => handleSetAutoLayout(bridge, args)
|
|
618
772
|
);
|
|
619
773
|
|
|
620
774
|
// figma_get_local_styles - List local styles
|
|
@@ -622,25 +776,29 @@ export function registerTools(server, bridge) {
|
|
|
622
776
|
'figma_get_local_styles',
|
|
623
777
|
'List all local styles defined in the document (colors, text, effects, grids). TIP: Use figma_search_styles instead when looking for specific styles by name - it returns compact results and reduces token usage.',
|
|
624
778
|
{
|
|
625
|
-
type: z
|
|
779
|
+
type: z
|
|
780
|
+
.enum(['PAINT', 'TEXT', 'EFFECT', 'GRID', 'ALL'])
|
|
781
|
+
.optional()
|
|
782
|
+
.default('ALL')
|
|
783
|
+
.describe('Filter by style type')
|
|
626
784
|
},
|
|
627
|
-
async
|
|
785
|
+
async args => handleGetLocalStyles(bridge, args)
|
|
628
786
|
);
|
|
629
787
|
|
|
630
788
|
// figma_apply_style - Apply a style to a node
|
|
631
789
|
server.tool(
|
|
632
790
|
'figma_apply_style',
|
|
633
791
|
'Apply a local style to a node. Styles provide consistent, reusable design tokens. Works for all five style ' +
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
792
|
+
'properties including text — the plugin uses the async setters (setTextStyleIdAsync / setFillStyleIdAsync / ' +
|
|
793
|
+
'setStrokeStyleIdAsync / setEffectStyleIdAsync / setGridStyleIdAsync) that are mandatory under ' +
|
|
794
|
+
'documentAccess: "dynamic-page". The applied style ID is read back off the node and returned as appliedStyleId ' +
|
|
795
|
+
'with verified: true; if the readback does not match, the call fails with STYLE_NOT_APPLIED instead of reporting success.',
|
|
638
796
|
{
|
|
639
797
|
nodeId: z.string().describe('The node ID to apply the style to'),
|
|
640
798
|
styleId: z.string().describe('The style ID to apply'),
|
|
641
799
|
property: z.enum(['fills', 'strokes', 'text', 'effects', 'grid']).describe('Which property to apply the style to')
|
|
642
800
|
},
|
|
643
|
-
async
|
|
801
|
+
async args => handleApplyStyle(bridge, args)
|
|
644
802
|
);
|
|
645
803
|
|
|
646
804
|
// figma_create_component - Create a component
|
|
@@ -658,7 +816,7 @@ export function registerTools(server, bridge) {
|
|
|
658
816
|
parentId: z.string().optional().describe('Parent node ID (defaults to current page)'),
|
|
659
817
|
description: z.string().optional().describe('Component description')
|
|
660
818
|
},
|
|
661
|
-
async
|
|
819
|
+
async args => handleCreateComponent(bridge, args)
|
|
662
820
|
);
|
|
663
821
|
|
|
664
822
|
// figma_create_instance - Create an instance of a component
|
|
@@ -672,7 +830,7 @@ export function registerTools(server, bridge) {
|
|
|
672
830
|
parentId: z.string().optional().describe('Parent node ID (defaults to current page)'),
|
|
673
831
|
name: z.string().optional().describe('Instance name (defaults to component name)')
|
|
674
832
|
},
|
|
675
|
-
async
|
|
833
|
+
async args => handleCreateInstance(bridge, args)
|
|
676
834
|
);
|
|
677
835
|
|
|
678
836
|
// ============================================================
|
|
@@ -685,9 +843,13 @@ export function registerTools(server, bridge) {
|
|
|
685
843
|
'figma_get_local_variables',
|
|
686
844
|
'Get all local variables and variable collections from the Figma document. Returns variables with their types (COLOR, FLOAT, STRING, BOOLEAN), modes, and values. WARNING: Can return 25k+ tokens and may be truncated. Use figma_search_variables instead when looking for specific variables.',
|
|
687
845
|
{
|
|
688
|
-
type: z
|
|
846
|
+
type: z
|
|
847
|
+
.enum(['COLOR', 'FLOAT', 'STRING', 'BOOLEAN', 'ALL'])
|
|
848
|
+
.optional()
|
|
849
|
+
.default('ALL')
|
|
850
|
+
.describe('Filter by variable type')
|
|
689
851
|
},
|
|
690
|
-
async
|
|
852
|
+
async args => handleGetLocalVariables(bridge, args)
|
|
691
853
|
);
|
|
692
854
|
|
|
693
855
|
// figma_search_variables - Search variables with filtering (optimized for reduced token usage)
|
|
@@ -696,14 +858,32 @@ export function registerTools(server, bridge) {
|
|
|
696
858
|
'figma_search_variables',
|
|
697
859
|
'Search for variables by name pattern. More efficient than get_local_variables - use this when looking for specific variables like "tailwind/orange/*" or "*primary*". Returns compact results to reduce token usage. PREFERRED over figma_get_local_variables for efficiency (~500 tokens vs 25k+).',
|
|
698
860
|
{
|
|
699
|
-
namePattern: z
|
|
700
|
-
|
|
701
|
-
|
|
861
|
+
namePattern: z
|
|
862
|
+
.string()
|
|
863
|
+
.optional()
|
|
864
|
+
.describe(
|
|
865
|
+
'Filter by name pattern with wildcards. Examples: "tailwind/orange/*", "*primary*", "spacing/*". Use * for any characters.'
|
|
866
|
+
),
|
|
867
|
+
nameContains: z
|
|
868
|
+
.string()
|
|
869
|
+
.optional()
|
|
870
|
+
.describe(
|
|
871
|
+
'Simple filter: find variables where name contains this string (case-insensitive). Example: "orange" matches "tailwind/orange/500"'
|
|
872
|
+
),
|
|
873
|
+
type: z
|
|
874
|
+
.enum(['COLOR', 'FLOAT', 'STRING', 'BOOLEAN', 'ALL'])
|
|
875
|
+
.optional()
|
|
876
|
+
.default('ALL')
|
|
877
|
+
.describe('Filter by variable type'),
|
|
702
878
|
collectionName: z.string().optional().describe('Filter by collection name (exact match or partial)'),
|
|
703
|
-
compact: z
|
|
879
|
+
compact: z
|
|
880
|
+
.boolean()
|
|
881
|
+
.optional()
|
|
882
|
+
.default(true)
|
|
883
|
+
.describe('Return minimal data (id, name, hex/value only). Set false for full metadata.'),
|
|
704
884
|
limit: z.number().optional().default(50).describe('Maximum number of variables to return')
|
|
705
885
|
},
|
|
706
|
-
async
|
|
886
|
+
async args => handleSearchVariables(bridge, args)
|
|
707
887
|
);
|
|
708
888
|
|
|
709
889
|
// ============================================================
|
|
@@ -716,14 +896,28 @@ export function registerTools(server, bridge) {
|
|
|
716
896
|
'Search for nodes by name within a scope. PREFERRED for finding specific frames, sections, or elements. Requires parentId to scope search. Returns compact results (~50 tokens/node vs ~500 for full).',
|
|
717
897
|
{
|
|
718
898
|
parentId: z.string().describe('Scope to search (page/frame/section ID). REQUIRED to prevent runaway queries.'),
|
|
719
|
-
nameContains: z
|
|
899
|
+
nameContains: z
|
|
900
|
+
.string()
|
|
901
|
+
.optional()
|
|
902
|
+
.describe('Case-insensitive substring match. Example: "color scale" matches "Color Scale Section"'),
|
|
720
903
|
namePattern: z.string().optional().describe('Glob pattern with wildcards. Examples: "*button*", "Header/*"'),
|
|
721
|
-
types: z
|
|
722
|
-
|
|
723
|
-
|
|
904
|
+
types: z
|
|
905
|
+
.array(z.string())
|
|
906
|
+
.optional()
|
|
907
|
+
.describe('Filter by node types: FRAME, TEXT, SECTION, COMPONENT, INSTANCE, GROUP, etc.'),
|
|
908
|
+
maxDepth: z
|
|
909
|
+
.number()
|
|
910
|
+
.optional()
|
|
911
|
+
.default(-1)
|
|
912
|
+
.describe('How deep to search (-1 = unlimited, 1 = immediate children only)'),
|
|
913
|
+
compact: z
|
|
914
|
+
.boolean()
|
|
915
|
+
.optional()
|
|
916
|
+
.default(true)
|
|
917
|
+
.describe('Return minimal data (id, name, type, parentId, childCount)'),
|
|
724
918
|
limit: z.number().optional().default(50).describe('Maximum number of results')
|
|
725
919
|
},
|
|
726
|
-
async
|
|
920
|
+
async args => handleSearchNodes(bridge, args)
|
|
727
921
|
);
|
|
728
922
|
|
|
729
923
|
// figma_search_components - Search local components by name
|
|
@@ -733,11 +927,15 @@ export function registerTools(server, bridge) {
|
|
|
733
927
|
{
|
|
734
928
|
nameContains: z.string().optional().describe('Case-insensitive substring match'),
|
|
735
929
|
namePattern: z.string().optional().describe('Glob pattern with wildcards'),
|
|
736
|
-
includeVariants: z
|
|
930
|
+
includeVariants: z
|
|
931
|
+
.boolean()
|
|
932
|
+
.optional()
|
|
933
|
+
.default(false)
|
|
934
|
+
.describe('Include individual variants from component sets'),
|
|
737
935
|
compact: z.boolean().optional().default(true).describe('Return minimal data'),
|
|
738
936
|
limit: z.number().optional().default(50).describe('Maximum number of results')
|
|
739
937
|
},
|
|
740
|
-
async
|
|
938
|
+
async args => handleSearchComponents(bridge, args)
|
|
741
939
|
);
|
|
742
940
|
|
|
743
941
|
// figma_search_styles - Search local styles by name
|
|
@@ -746,11 +944,15 @@ export function registerTools(server, bridge) {
|
|
|
746
944
|
'Search local styles by name. More efficient than figma_get_local_styles when looking for specific styles.',
|
|
747
945
|
{
|
|
748
946
|
nameContains: z.string().optional().describe('Case-insensitive substring match'),
|
|
749
|
-
type: z
|
|
947
|
+
type: z
|
|
948
|
+
.enum(['PAINT', 'TEXT', 'EFFECT', 'GRID', 'ALL'])
|
|
949
|
+
.optional()
|
|
950
|
+
.default('ALL')
|
|
951
|
+
.describe('Filter by style type'),
|
|
750
952
|
compact: z.boolean().optional().default(true).describe('Return minimal data'),
|
|
751
953
|
limit: z.number().optional().default(50).describe('Maximum number of results')
|
|
752
954
|
},
|
|
753
|
-
async
|
|
955
|
+
async args => handleSearchStyles(bridge, args)
|
|
754
956
|
);
|
|
755
957
|
|
|
756
958
|
// figma_get_children - Get immediate children of a node
|
|
@@ -759,40 +961,68 @@ export function registerTools(server, bridge) {
|
|
|
759
961
|
'Get immediate children of a node. Use for browsing hierarchy one level at a time. More efficient than figma_get_nodes for exploring structure. Compact results include x/y, so they can be used to measure layout (e.g. which children share a row after wrapping). Composite instance-sublayer parent IDs ("I<instanceId>;<childId>") resolve here too.',
|
|
760
962
|
{
|
|
761
963
|
parentId: z.string().describe('Node ID to get children of. REQUIRED.'),
|
|
762
|
-
compact: z
|
|
964
|
+
compact: z
|
|
965
|
+
.boolean()
|
|
966
|
+
.optional()
|
|
967
|
+
.default(true)
|
|
968
|
+
.describe(
|
|
969
|
+
'Return minimal data (id, name, type, x, y, parentId, childCount). Set false for the full ~40-property serialization.'
|
|
970
|
+
)
|
|
763
971
|
},
|
|
764
|
-
async
|
|
972
|
+
async args => handleGetChildren(bridge, args)
|
|
765
973
|
);
|
|
766
974
|
|
|
767
975
|
// figma_set_variable - Set variable value or bind to node
|
|
768
976
|
server.tool(
|
|
769
977
|
'figma_set_variable',
|
|
770
978
|
'Set the value of an existing variable for a specific mode, or bind a variable to a node property OR to a local style. Styles are supported: pass styleId (or a style ID as nodeId) with field. TEXT styles bind fontFamily, fontSize, fontStyle, fontWeight, letterSpacing, lineHeight, paragraphSpacing, paragraphIndent; PAINT styles bind their color via field "paints". Style binds echo the style\'s boundVariables so the bind is verifiable in the same call. ' +
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
979
|
+
'Node binds are VERIFIED: after binding, node.boundVariables is re-read and the response returns it with verified: true. ' +
|
|
980
|
+
"If the bind did not land, the call fails with BIND_NOT_APPLIED rather than reporting success — this catches Figma's " +
|
|
981
|
+
'silent no-ops. Binding "width" or "height" on an instance sublayer is rejected up front with ' +
|
|
982
|
+
'INSTANCE_SUBLAYER_RESTRICTED: Figma does not allow that override, so bind it on the component master instead (or size ' +
|
|
983
|
+
'the sublayer with figma_set_layout_align: STRETCH).',
|
|
776
984
|
{
|
|
777
985
|
variableId: z.string().describe('The variable ID to set or bind'),
|
|
778
986
|
modeId: z.string().optional().describe('Mode ID to set value for (required when setting value)'),
|
|
779
|
-
value: z
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
987
|
+
value: z
|
|
988
|
+
.union([
|
|
989
|
+
z.number(),
|
|
990
|
+
z.string(),
|
|
991
|
+
z.boolean(),
|
|
992
|
+
z.object({
|
|
993
|
+
r: z.number().min(0).max(1).describe('Red (0-1)'),
|
|
994
|
+
g: z.number().min(0).max(1).describe('Green (0-1)'),
|
|
995
|
+
b: z.number().min(0).max(1).describe('Blue (0-1)'),
|
|
996
|
+
a: z.number().min(0).max(1).optional().describe('Alpha (0-1)')
|
|
997
|
+
})
|
|
998
|
+
])
|
|
999
|
+
.optional()
|
|
1000
|
+
.describe('The value to set (number, string, boolean, or color object)'),
|
|
1001
|
+
nodeId: z
|
|
1002
|
+
.string()
|
|
1003
|
+
.optional()
|
|
1004
|
+
.describe(
|
|
1005
|
+
'Node ID to bind variable to (for binding operation). A style ID passed here is routed to the style path.'
|
|
1006
|
+
),
|
|
1007
|
+
styleId: z
|
|
1008
|
+
.string()
|
|
1009
|
+
.optional()
|
|
1010
|
+
.describe(
|
|
1011
|
+
'Local style ID to bind variable to (e.g., "S:abc123..."). Use instead of nodeId to bind a TEXT or PAINT style.'
|
|
1012
|
+
),
|
|
1013
|
+
field: z
|
|
1014
|
+
.string()
|
|
1015
|
+
.optional()
|
|
1016
|
+
.describe(
|
|
1017
|
+
'Field to bind. Nodes: "opacity", "cornerRadius", "fills", "strokes", etc. Text styles: "fontSize", "lineHeight", "letterSpacing", "paragraphSpacing", "paragraphIndent", "fontFamily", "fontStyle", "fontWeight". Paint styles: "paints".'
|
|
1018
|
+
),
|
|
1019
|
+
paintIndex: z
|
|
1020
|
+
.number()
|
|
1021
|
+
.optional()
|
|
1022
|
+
.default(0)
|
|
1023
|
+
.describe('Paint array index when binding to fills, strokes, or a paint style')
|
|
794
1024
|
},
|
|
795
|
-
async
|
|
1025
|
+
async args => handleSetVariable(bridge, args)
|
|
796
1026
|
);
|
|
797
1027
|
|
|
798
1028
|
// figma_create_line - Create a line
|
|
@@ -807,10 +1037,14 @@ export function registerTools(server, bridge) {
|
|
|
807
1037
|
name: z.string().optional().default('Line').describe('Node name'),
|
|
808
1038
|
strokeWeight: z.number().optional().default(1).describe('Stroke weight in pixels'),
|
|
809
1039
|
strokes: colorSchema.optional().describe('Stroke color'),
|
|
810
|
-
strokeCap: z
|
|
1040
|
+
strokeCap: z
|
|
1041
|
+
.enum(['NONE', 'ROUND', 'SQUARE', 'ARROW_LINES', 'ARROW_EQUILATERAL'])
|
|
1042
|
+
.optional()
|
|
1043
|
+
.default('NONE')
|
|
1044
|
+
.describe('Stroke cap style (ARROW_LINES/ARROW_EQUILATERAL for arrows)'),
|
|
811
1045
|
parentId: z.string().optional().describe('Parent node ID (defaults to current page)')
|
|
812
1046
|
},
|
|
813
|
-
async
|
|
1047
|
+
async args => handleCreateLine(bridge, args)
|
|
814
1048
|
);
|
|
815
1049
|
|
|
816
1050
|
// figma_set_constraints - Set resize constraints
|
|
@@ -819,10 +1053,16 @@ export function registerTools(server, bridge) {
|
|
|
819
1053
|
'Set resize constraints on a node. Constraints control how a node resizes when its parent frame resizes. Only works on nodes inside frames (not auto-layout frames).',
|
|
820
1054
|
{
|
|
821
1055
|
nodeId: z.string().describe('The node ID to set constraints on'),
|
|
822
|
-
horizontal: z
|
|
823
|
-
|
|
1056
|
+
horizontal: z
|
|
1057
|
+
.enum(['MIN', 'CENTER', 'MAX', 'STRETCH', 'SCALE'])
|
|
1058
|
+
.optional()
|
|
1059
|
+
.describe('Horizontal constraint: MIN (left), CENTER, MAX (right), STRETCH (left+right), SCALE (proportional)'),
|
|
1060
|
+
vertical: z
|
|
1061
|
+
.enum(['MIN', 'CENTER', 'MAX', 'STRETCH', 'SCALE'])
|
|
1062
|
+
.optional()
|
|
1063
|
+
.describe('Vertical constraint: MIN (top), CENTER, MAX (bottom), STRETCH (top+bottom), SCALE (proportional)')
|
|
824
1064
|
},
|
|
825
|
-
async
|
|
1065
|
+
async args => handleSetConstraints(bridge, args)
|
|
826
1066
|
);
|
|
827
1067
|
|
|
828
1068
|
// ============================================================
|
|
@@ -895,7 +1135,7 @@ export function registerTools(server, bridge) {
|
|
|
895
1135
|
{
|
|
896
1136
|
nodeId: z.string().describe('The instance node ID to detach')
|
|
897
1137
|
},
|
|
898
|
-
async
|
|
1138
|
+
async args => handleDetachInstance(bridge, args)
|
|
899
1139
|
);
|
|
900
1140
|
|
|
901
1141
|
// ============================================================
|
|
@@ -908,11 +1148,22 @@ export function registerTools(server, bridge) {
|
|
|
908
1148
|
'Set how a child behaves within an auto-layout frame. Controls individual alignment (STRETCH), growth (fill container), and absolute positioning.',
|
|
909
1149
|
{
|
|
910
1150
|
nodeId: z.string().describe('The child node ID to modify'),
|
|
911
|
-
layoutAlign: z
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
1151
|
+
layoutAlign: z
|
|
1152
|
+
.enum(['MIN', 'CENTER', 'MAX', 'STRETCH', 'INHERIT'])
|
|
1153
|
+
.optional()
|
|
1154
|
+
.describe('Counter-axis alignment: STRETCH to fill width/height'),
|
|
1155
|
+
layoutGrow: z
|
|
1156
|
+
.number()
|
|
1157
|
+
.min(0)
|
|
1158
|
+
.max(1)
|
|
1159
|
+
.optional()
|
|
1160
|
+
.describe('Primary-axis growth: 0 = fixed size, 1 = fill available space'),
|
|
1161
|
+
layoutPositioning: z
|
|
1162
|
+
.enum(['AUTO', 'ABSOLUTE'])
|
|
1163
|
+
.optional()
|
|
1164
|
+
.describe('AUTO = follow auto-layout, ABSOLUTE = manually positioned')
|
|
1165
|
+
},
|
|
1166
|
+
async args => handleSetLayoutAlign(bridge, args)
|
|
916
1167
|
);
|
|
917
1168
|
|
|
918
1169
|
// DISABLED - Uncomment to enable custom vector paths
|
|
@@ -943,27 +1194,27 @@ export function registerTools(server, bridge) {
|
|
|
943
1194
|
nodeIds: z.array(z.string()).optional().describe('Array of node IDs to rename (batch)'),
|
|
944
1195
|
name: z.string().describe('The new name for the node(s)')
|
|
945
1196
|
},
|
|
946
|
-
async
|
|
1197
|
+
async args => handleRenameNode(bridge, args)
|
|
947
1198
|
);
|
|
948
1199
|
|
|
949
1200
|
// figma_reorder_node - Change z-order of a node
|
|
950
1201
|
server.tool(
|
|
951
1202
|
'figma_reorder_node',
|
|
952
1203
|
'Change the z-order (layer order) of a node among its siblings. A numeric position is the FINAL index the node ' +
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
1204
|
+
'ends up at — index 2 means the node is at index 2 when the call returns, not one off from it. Figma sorts children ' +
|
|
1205
|
+
'back-to-front, so 0 is the BOTTOM of the layer stack and childCount - 1 is the top; "back" is 0 and "front" is the ' +
|
|
1206
|
+
'last index. Out-of-range indices are clamped into range and the response reports clamped: true with a message. ' +
|
|
1207
|
+
'The final index is verified by reading it back — a mismatch fails with REORDER_FAILED rather than reporting success. ' +
|
|
1208
|
+
'Reordering children of an INSTANCE is blocked by Figma and returns INSTANCE_SUBLAYER_RESTRICTED; reorder on the master instead.',
|
|
958
1209
|
{
|
|
959
1210
|
nodeId: z.string().describe('The node ID to reorder'),
|
|
960
|
-
position: z
|
|
961
|
-
z.literal('front'),
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
1211
|
+
position: z
|
|
1212
|
+
.union([z.literal('front'), z.literal('back'), z.number()])
|
|
1213
|
+
.describe(
|
|
1214
|
+
'Final position: "front" (top of stack), "back" (bottom), or the final zero-based index among siblings (0 = bottom)'
|
|
1215
|
+
)
|
|
965
1216
|
},
|
|
966
|
-
async
|
|
1217
|
+
async args => handleReorderNode(bridge, args)
|
|
967
1218
|
);
|
|
968
1219
|
|
|
969
1220
|
// ============================================================
|
|
@@ -981,19 +1232,28 @@ export function registerTools(server, bridge) {
|
|
|
981
1232
|
fontStyle: z.string().optional().describe('Font style (e.g., "Bold", "Regular")'),
|
|
982
1233
|
textCase: z.enum(['ORIGINAL', 'UPPER', 'LOWER', 'TITLE']).optional().describe('Text case transformation'),
|
|
983
1234
|
textDecoration: z.enum(['NONE', 'UNDERLINE', 'STRIKETHROUGH']).optional().describe('Text decoration'),
|
|
984
|
-
lineHeight: z
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
1235
|
+
lineHeight: z
|
|
1236
|
+
.union([
|
|
1237
|
+
z.object({ unit: z.literal('AUTO') }),
|
|
1238
|
+
z.object({ unit: z.literal('PIXELS'), value: z.number() }),
|
|
1239
|
+
z.object({ unit: z.literal('PERCENT'), value: z.number() })
|
|
1240
|
+
])
|
|
1241
|
+
.optional()
|
|
1242
|
+
.describe('Line height (AUTO, or PIXELS/PERCENT with value)'),
|
|
1243
|
+
letterSpacing: z
|
|
1244
|
+
.union([
|
|
1245
|
+
z.object({ unit: z.literal('PIXELS'), value: z.number() }),
|
|
1246
|
+
z.object({ unit: z.literal('PERCENT'), value: z.number() })
|
|
1247
|
+
])
|
|
1248
|
+
.optional()
|
|
1249
|
+
.describe('Letter spacing (PIXELS or PERCENT with value)'),
|
|
1250
|
+
textAlignHorizontal: z
|
|
1251
|
+
.enum(['LEFT', 'CENTER', 'RIGHT', 'JUSTIFIED'])
|
|
1252
|
+
.optional()
|
|
1253
|
+
.describe('Horizontal text alignment'),
|
|
994
1254
|
textAlignVertical: z.enum(['TOP', 'CENTER', 'BOTTOM']).optional().describe('Vertical text alignment')
|
|
995
1255
|
},
|
|
996
|
-
async
|
|
1256
|
+
async args => handleSetTextStyle(bridge, args)
|
|
997
1257
|
);
|
|
998
1258
|
|
|
999
1259
|
// figma_create_paint_style - Create a local paint style
|
|
@@ -1005,7 +1265,7 @@ export function registerTools(server, bridge) {
|
|
|
1005
1265
|
fills: colorSchema.describe('Fill color - use { color: "#RRGGBB" } for simple colors'),
|
|
1006
1266
|
description: z.string().optional().describe('Style description')
|
|
1007
1267
|
},
|
|
1008
|
-
async
|
|
1268
|
+
async args => handleCreatePaintStyle(bridge, args)
|
|
1009
1269
|
);
|
|
1010
1270
|
|
|
1011
1271
|
// figma_create_text_style - Create a local text style
|
|
@@ -1017,20 +1277,26 @@ export function registerTools(server, bridge) {
|
|
|
1017
1277
|
fontFamily: z.string().optional().default('Inter').describe('Font family'),
|
|
1018
1278
|
fontStyle: z.string().optional().default('Regular').describe('Font style (Regular, Bold, etc.)'),
|
|
1019
1279
|
fontSize: z.number().optional().default(16).describe('Font size in pixels'),
|
|
1020
|
-
lineHeight: z
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1280
|
+
lineHeight: z
|
|
1281
|
+
.union([
|
|
1282
|
+
z.object({ unit: z.literal('AUTO') }),
|
|
1283
|
+
z.object({ unit: z.literal('PIXELS'), value: z.number() }),
|
|
1284
|
+
z.object({ unit: z.literal('PERCENT'), value: z.number() })
|
|
1285
|
+
])
|
|
1286
|
+
.optional()
|
|
1287
|
+
.describe('Line height'),
|
|
1288
|
+
letterSpacing: z
|
|
1289
|
+
.union([
|
|
1290
|
+
z.object({ unit: z.literal('PIXELS'), value: z.number() }),
|
|
1291
|
+
z.object({ unit: z.literal('PERCENT'), value: z.number() })
|
|
1292
|
+
])
|
|
1293
|
+
.optional()
|
|
1294
|
+
.describe('Letter spacing'),
|
|
1029
1295
|
textCase: z.enum(['ORIGINAL', 'UPPER', 'LOWER', 'TITLE']).optional().describe('Text case'),
|
|
1030
1296
|
textDecoration: z.enum(['NONE', 'UNDERLINE', 'STRIKETHROUGH']).optional().describe('Text decoration'),
|
|
1031
1297
|
description: z.string().optional().describe('Style description')
|
|
1032
1298
|
},
|
|
1033
|
-
async
|
|
1299
|
+
async args => handleCreateTextStyle(bridge, args)
|
|
1034
1300
|
);
|
|
1035
1301
|
|
|
1036
1302
|
// figma_delete_style - Delete a local style
|
|
@@ -1040,7 +1306,7 @@ export function registerTools(server, bridge) {
|
|
|
1040
1306
|
{
|
|
1041
1307
|
styleId: z.string().describe('The style ID to delete (e.g., "S:abc123...")')
|
|
1042
1308
|
},
|
|
1043
|
-
async
|
|
1309
|
+
async args => handleDeleteStyle(bridge, args)
|
|
1044
1310
|
);
|
|
1045
1311
|
|
|
1046
1312
|
// figma_create_variable_collection - Create a variable collection
|
|
@@ -1051,7 +1317,7 @@ export function registerTools(server, bridge) {
|
|
|
1051
1317
|
name: z.string().describe('Collection name'),
|
|
1052
1318
|
modes: z.array(z.string()).optional().describe('Mode names (defaults to ["Mode 1"])')
|
|
1053
1319
|
},
|
|
1054
|
-
async
|
|
1320
|
+
async args => handleCreateVariableCollection(bridge, args)
|
|
1055
1321
|
);
|
|
1056
1322
|
|
|
1057
1323
|
// figma_create_variable - Create a variable
|
|
@@ -1062,24 +1328,49 @@ export function registerTools(server, bridge) {
|
|
|
1062
1328
|
collectionId: z.string().describe('Variable collection ID'),
|
|
1063
1329
|
name: z.string().describe('Variable name (use "/" for groups, e.g., "colors/primary")'),
|
|
1064
1330
|
type: z.enum(['COLOR', 'FLOAT', 'STRING', 'BOOLEAN']).describe('Variable type'),
|
|
1065
|
-
value: z
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1331
|
+
value: z
|
|
1332
|
+
.union([
|
|
1333
|
+
z.string(),
|
|
1334
|
+
z.number(),
|
|
1335
|
+
z.boolean(),
|
|
1336
|
+
z.object({ r: z.number(), g: z.number(), b: z.number(), a: z.number().optional() }),
|
|
1337
|
+
z.object({ color: z.string() })
|
|
1338
|
+
])
|
|
1339
|
+
.optional()
|
|
1340
|
+
.describe('Initial value for default mode'),
|
|
1072
1341
|
aliasOf: z.string().optional().describe('Variable ID to alias (instead of direct value)'),
|
|
1073
1342
|
description: z.string().optional().describe('Variable description'),
|
|
1074
|
-
scopes: z
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1343
|
+
scopes: z
|
|
1344
|
+
.array(
|
|
1345
|
+
z.enum([
|
|
1346
|
+
'ALL_SCOPES',
|
|
1347
|
+
'TEXT_CONTENT',
|
|
1348
|
+
'CORNER_RADIUS',
|
|
1349
|
+
'WIDTH_HEIGHT',
|
|
1350
|
+
'GAP',
|
|
1351
|
+
'ALL_FILLS',
|
|
1352
|
+
'FRAME_FILL',
|
|
1353
|
+
'SHAPE_FILL',
|
|
1354
|
+
'TEXT_FILL',
|
|
1355
|
+
'STROKE_COLOR',
|
|
1356
|
+
'STROKE_FLOAT',
|
|
1357
|
+
'EFFECT_FLOAT',
|
|
1358
|
+
'EFFECT_COLOR',
|
|
1359
|
+
'OPACITY',
|
|
1360
|
+
'FONT_FAMILY',
|
|
1361
|
+
'FONT_STYLE',
|
|
1362
|
+
'FONT_WEIGHT',
|
|
1363
|
+
'FONT_SIZE',
|
|
1364
|
+
'LINE_HEIGHT',
|
|
1365
|
+
'LETTER_SPACING',
|
|
1366
|
+
'PARAGRAPH_SPACING',
|
|
1367
|
+
'PARAGRAPH_INDENT'
|
|
1368
|
+
])
|
|
1369
|
+
)
|
|
1370
|
+
.optional()
|
|
1371
|
+
.describe('Where this variable can be used')
|
|
1372
|
+
},
|
|
1373
|
+
async args => handleCreateVariable(bridge, args)
|
|
1083
1374
|
);
|
|
1084
1375
|
|
|
1085
1376
|
// figma_rename_variable - Rename an existing variable
|
|
@@ -1090,7 +1381,7 @@ export function registerTools(server, bridge) {
|
|
|
1090
1381
|
variableId: z.string().describe('The variable ID to rename'),
|
|
1091
1382
|
name: z.string().describe('The new name for the variable (use "/" for groups)')
|
|
1092
1383
|
},
|
|
1093
|
-
async
|
|
1384
|
+
async args => handleRenameVariable(bridge, args)
|
|
1094
1385
|
);
|
|
1095
1386
|
|
|
1096
1387
|
// figma_delete_variables - Delete one or more variables
|
|
@@ -1100,7 +1391,7 @@ export function registerTools(server, bridge) {
|
|
|
1100
1391
|
{
|
|
1101
1392
|
variableIds: z.array(z.string()).describe('Array of variable IDs to delete')
|
|
1102
1393
|
},
|
|
1103
|
-
async
|
|
1394
|
+
async args => handleDeleteVariables(bridge, args)
|
|
1104
1395
|
);
|
|
1105
1396
|
|
|
1106
1397
|
// figma_delete_variable_collection - Delete a variable collection
|
|
@@ -1110,7 +1401,7 @@ export function registerTools(server, bridge) {
|
|
|
1110
1401
|
{
|
|
1111
1402
|
collectionId: z.string().describe('The collection ID to delete')
|
|
1112
1403
|
},
|
|
1113
|
-
async
|
|
1404
|
+
async args => handleDeleteVariableCollection(bridge, args)
|
|
1114
1405
|
);
|
|
1115
1406
|
|
|
1116
1407
|
// figma_rename_variable_collection - Rename a variable collection
|
|
@@ -1121,7 +1412,7 @@ export function registerTools(server, bridge) {
|
|
|
1121
1412
|
collectionId: z.string().describe('The collection ID to rename'),
|
|
1122
1413
|
name: z.string().describe('The new name for the collection')
|
|
1123
1414
|
},
|
|
1124
|
-
async
|
|
1415
|
+
async args => handleRenameVariableCollection(bridge, args)
|
|
1125
1416
|
);
|
|
1126
1417
|
|
|
1127
1418
|
// figma_rename_mode - Rename a mode in a collection
|
|
@@ -1133,7 +1424,7 @@ export function registerTools(server, bridge) {
|
|
|
1133
1424
|
modeId: z.string().describe('The mode ID to rename'),
|
|
1134
1425
|
name: z.string().describe('The new name for the mode')
|
|
1135
1426
|
},
|
|
1136
|
-
async
|
|
1427
|
+
async args => handleRenameMode(bridge, args)
|
|
1137
1428
|
);
|
|
1138
1429
|
|
|
1139
1430
|
// figma_add_mode - Add a mode to a collection
|
|
@@ -1144,7 +1435,7 @@ export function registerTools(server, bridge) {
|
|
|
1144
1435
|
collectionId: z.string().describe('The collection ID to add mode to'),
|
|
1145
1436
|
name: z.string().describe('Name for the new mode')
|
|
1146
1437
|
},
|
|
1147
|
-
async
|
|
1438
|
+
async args => handleAddMode(bridge, args)
|
|
1148
1439
|
);
|
|
1149
1440
|
|
|
1150
1441
|
// figma_delete_mode - Delete a mode from a collection
|
|
@@ -1155,7 +1446,7 @@ export function registerTools(server, bridge) {
|
|
|
1155
1446
|
collectionId: z.string().describe('The collection ID containing the mode'),
|
|
1156
1447
|
modeId: z.string().describe('The mode ID to delete')
|
|
1157
1448
|
},
|
|
1158
|
-
async
|
|
1449
|
+
async args => handleDeleteMode(bridge, args)
|
|
1159
1450
|
);
|
|
1160
1451
|
|
|
1161
1452
|
// figma_set_variable_mode - Pin or unpin an explicit variable mode on nodes/pages
|
|
@@ -1165,26 +1456,37 @@ export function registerTools(server, bridge) {
|
|
|
1165
1456
|
{
|
|
1166
1457
|
nodeIds: z.array(z.string()).describe('Array of node IDs or page IDs to pin/unpin'),
|
|
1167
1458
|
collectionId: z.string().describe('Variable collection ID the pin applies to (pins are per-collection)'),
|
|
1168
|
-
modeId: z
|
|
1169
|
-
|
|
1459
|
+
modeId: z
|
|
1460
|
+
.string()
|
|
1461
|
+
.optional()
|
|
1462
|
+
.describe(
|
|
1463
|
+
'Mode ID to pin. Required unless clear is true. Must belong to collectionId — the error lists the valid modes if it does not.'
|
|
1464
|
+
),
|
|
1465
|
+
clear: z
|
|
1466
|
+
.boolean()
|
|
1467
|
+
.optional()
|
|
1468
|
+
.default(false)
|
|
1469
|
+
.describe("true to remove this collection's pin from the nodes instead of setting one")
|
|
1170
1470
|
},
|
|
1171
|
-
async
|
|
1471
|
+
async args => handleSetVariableMode(bridge, args)
|
|
1172
1472
|
);
|
|
1173
1473
|
|
|
1174
1474
|
// figma_unbind_variable - Remove variable binding from a node
|
|
1175
1475
|
server.tool(
|
|
1176
1476
|
'figma_unbind_variable',
|
|
1177
1477
|
'Remove a variable binding from a node property. The unbind is verified by readback (UNBIND_FAILED if the field is ' +
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1478
|
+
"still bound) and the response echoes the node's remaining boundVariables. " +
|
|
1479
|
+
'Special handling for minWidth / maxWidth / minHeight / maxHeight: unbinding one of these leaves the last resolved ' +
|
|
1480
|
+
'number behind as a hard literal clamp, so the literal is cleared to null too — the response reports previousLiteral ' +
|
|
1481
|
+
'and clearedLiteral. Set a new limit with figma_set_size_limits.',
|
|
1182
1482
|
{
|
|
1183
1483
|
nodeId: z.string().describe('The node ID to unbind from'),
|
|
1184
|
-
field: z
|
|
1484
|
+
field: z
|
|
1485
|
+
.string()
|
|
1486
|
+
.describe('The field to unbind (fills, strokes, opacity, cornerRadius, minWidth, maxWidth, etc.)'),
|
|
1185
1487
|
paintIndex: z.number().optional().default(0).describe('Paint array index for fills/strokes')
|
|
1186
1488
|
},
|
|
1187
|
-
async
|
|
1489
|
+
async args => handleUnbindVariable(bridge, args)
|
|
1188
1490
|
);
|
|
1189
1491
|
|
|
1190
1492
|
// ============================================================
|
|
@@ -1199,7 +1501,7 @@ export function registerTools(server, bridge) {
|
|
|
1199
1501
|
name: z.string().describe('Name for the new page'),
|
|
1200
1502
|
index: z.number().optional().describe('Position in the page list (0 = first). Defaults to end.')
|
|
1201
1503
|
},
|
|
1202
|
-
async
|
|
1504
|
+
async args => handleCreatePage(bridge, args)
|
|
1203
1505
|
);
|
|
1204
1506
|
|
|
1205
1507
|
// figma_rename_page - Rename a page
|
|
@@ -1210,7 +1512,7 @@ export function registerTools(server, bridge) {
|
|
|
1210
1512
|
pageId: z.string().describe('The page ID to rename'),
|
|
1211
1513
|
name: z.string().describe('The new name for the page')
|
|
1212
1514
|
},
|
|
1213
|
-
async
|
|
1515
|
+
async args => handleRenamePage(bridge, args)
|
|
1214
1516
|
);
|
|
1215
1517
|
|
|
1216
1518
|
// figma_delete_page - Delete a page
|
|
@@ -1220,7 +1522,7 @@ export function registerTools(server, bridge) {
|
|
|
1220
1522
|
{
|
|
1221
1523
|
pageId: z.string().describe('The page ID to delete')
|
|
1222
1524
|
},
|
|
1223
|
-
async
|
|
1525
|
+
async args => handleDeletePage(bridge, args)
|
|
1224
1526
|
);
|
|
1225
1527
|
|
|
1226
1528
|
// DISABLED - Uncomment to enable page reordering
|
|
@@ -1248,7 +1550,7 @@ export function registerTools(server, bridge) {
|
|
|
1248
1550
|
newParentId: z.string().describe('The new parent node ID (must be a frame, group, or page)'),
|
|
1249
1551
|
index: z.number().optional().describe('Position within the new parent (0 = bottom/back). Defaults to top/front.')
|
|
1250
1552
|
},
|
|
1251
|
-
async
|
|
1553
|
+
async args => handleReparentNodes(bridge, args)
|
|
1252
1554
|
);
|
|
1253
1555
|
|
|
1254
1556
|
// figma_move_to_page - Move nodes to a different page
|
|
@@ -1261,7 +1563,7 @@ export function registerTools(server, bridge) {
|
|
|
1261
1563
|
x: z.number().optional().describe('X position on the target page'),
|
|
1262
1564
|
y: z.number().optional().describe('Y position on the target page')
|
|
1263
1565
|
},
|
|
1264
|
-
async
|
|
1566
|
+
async args => handleMoveToPage(bridge, args)
|
|
1265
1567
|
);
|
|
1266
1568
|
|
|
1267
1569
|
// ============================================================
|
|
@@ -1276,7 +1578,7 @@ export function registerTools(server, bridge) {
|
|
|
1276
1578
|
instanceId: z.string().describe('The instance node ID to swap'),
|
|
1277
1579
|
newComponentId: z.string().describe('The component ID to swap to')
|
|
1278
1580
|
},
|
|
1279
|
-
async
|
|
1581
|
+
async args => handleSwapInstance(bridge, args)
|
|
1280
1582
|
);
|
|
1281
1583
|
|
|
1282
1584
|
// ============================================================
|
|
@@ -1291,26 +1593,32 @@ export function registerTools(server, bridge) {
|
|
|
1291
1593
|
pageId: z.string().describe('The page ID to duplicate'),
|
|
1292
1594
|
name: z.string().optional().describe('Name for the new page (defaults to "original name + copy")')
|
|
1293
1595
|
},
|
|
1294
|
-
async
|
|
1596
|
+
async args => handleDuplicatePage(bridge, args)
|
|
1295
1597
|
);
|
|
1296
1598
|
|
|
1297
1599
|
// figma_set_rotation - Set rotation on nodes
|
|
1298
1600
|
server.tool(
|
|
1299
1601
|
'figma_set_rotation',
|
|
1300
1602
|
'Set the rotation (in degrees) of one or more nodes. pivot defaults to "center", which keeps the node\'s visual ' +
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1603
|
+
'centre in place by writing relativeTransform. pivot: "top-left" is Figma\'s raw node.rotation behavior, which ' +
|
|
1604
|
+
'rotates about the top-left corner and therefore MOVES the visual centre. ' +
|
|
1605
|
+
"Limitation: an auto-layout parent computes its children's positions and ignores the translation part of " +
|
|
1606
|
+
'relativeTransform, so a centre pivot is impossible on a non-ABSOLUTE auto-layout child — those nodes get a ' +
|
|
1607
|
+
'top-left rotation plus an explicit warning naming the parent (set layoutPositioning: ABSOLUTE, or wrap the node ' +
|
|
1608
|
+
'in a plain frame, to get a true centre pivot). Each node echoes appliedPivot, its resulting rotation, and its ' +
|
|
1609
|
+
'absoluteBoundingBox so the pivot can be verified.',
|
|
1308
1610
|
{
|
|
1309
1611
|
nodeIds: z.array(z.string()).describe('Array of node IDs to rotate'),
|
|
1310
1612
|
rotation: z.number().min(-180).max(180).describe('Rotation in degrees (-180 to 180)'),
|
|
1311
|
-
pivot: z
|
|
1613
|
+
pivot: z
|
|
1614
|
+
.enum(['center', 'top-left'])
|
|
1615
|
+
.optional()
|
|
1616
|
+
.default('center')
|
|
1617
|
+
.describe(
|
|
1618
|
+
'Point to rotate about. "center" (default) preserves the node\'s visual centre; "top-left" is Figma\'s raw node.rotation behavior.'
|
|
1619
|
+
)
|
|
1312
1620
|
},
|
|
1313
|
-
async
|
|
1621
|
+
async args => handleSetRotation(bridge, args)
|
|
1314
1622
|
);
|
|
1315
1623
|
|
|
1316
1624
|
// figma_combine_as_variants - Combine components into a component set
|
|
@@ -1320,7 +1628,7 @@ export function registerTools(server, bridge) {
|
|
|
1320
1628
|
{
|
|
1321
1629
|
componentIds: z.array(z.string()).min(2).describe('Array of component IDs to combine (minimum 2)')
|
|
1322
1630
|
},
|
|
1323
|
-
async
|
|
1631
|
+
async args => handleCombineAsVariants(bridge, args)
|
|
1324
1632
|
);
|
|
1325
1633
|
|
|
1326
1634
|
// DISABLED - Uncomment to enable layout grids
|
|
@@ -1368,7 +1676,7 @@ export function registerTools(server, bridge) {
|
|
|
1368
1676
|
isWideWidth: z.boolean().optional().describe('Use the wide rectangular sticky variant'),
|
|
1369
1677
|
parentId: z.string().optional().describe('Parent node ID (defaults to current page)')
|
|
1370
1678
|
},
|
|
1371
|
-
async
|
|
1679
|
+
async args => handleCreateSticky(bridge, args)
|
|
1372
1680
|
);
|
|
1373
1681
|
|
|
1374
1682
|
// figma_set_sticky - Update a sticky's metadata
|
|
@@ -1379,7 +1687,7 @@ export function registerTools(server, bridge) {
|
|
|
1379
1687
|
nodeId: z.string().describe('The STICKY node ID'),
|
|
1380
1688
|
isWideWidth: z.boolean().optional().describe('Wide vs square sticky')
|
|
1381
1689
|
},
|
|
1382
|
-
async
|
|
1690
|
+
async args => handleSetSticky(bridge, args)
|
|
1383
1691
|
);
|
|
1384
1692
|
|
|
1385
1693
|
// figma_create_shape_with_text - Create a flowchart shape with embedded text
|
|
@@ -1391,14 +1699,18 @@ export function registerTools(server, bridge) {
|
|
|
1391
1699
|
y: z.number().optional().default(0).describe('Y position'),
|
|
1392
1700
|
width: z.number().optional().default(208).describe('Width in pixels'),
|
|
1393
1701
|
height: z.number().optional().default(208).describe('Height in pixels'),
|
|
1394
|
-
shapeType: z
|
|
1702
|
+
shapeType: z
|
|
1703
|
+
.enum(SHAPE_TYPES)
|
|
1704
|
+
.describe(
|
|
1705
|
+
'Shape variety: SQUARE, ELLIPSE, ROUNDED_RECTANGLE, DIAMOND, TRIANGLE_UP/DOWN, PARALLELOGRAM_RIGHT/LEFT, ENG_DATABASE, ENG_QUEUE, ENG_FILE, ENG_FOLDER, TRAPEZOID, PREDEFINED_PROCESS, SHIELD, DOCUMENT_SINGLE/MULTIPLE, MANUAL_INPUT, HEXAGON, CHEVRON, PENTAGON, OCTAGON, STAR, PLUS, ARROW_LEFT/RIGHT, SUMMING_JUNCTION, OR, SPEECH_BUBBLE, INTERNAL_STORAGE'
|
|
1706
|
+
),
|
|
1395
1707
|
text: z.string().optional().describe('Embedded text content (font auto-loaded)'),
|
|
1396
1708
|
fills: colorSchema.optional().describe('Shape fill color'),
|
|
1397
1709
|
strokes: colorSchema.optional().describe('Shape stroke color'),
|
|
1398
1710
|
strokeWeight: z.number().optional().describe('Stroke weight in pixels'),
|
|
1399
1711
|
parentId: z.string().optional().describe('Parent node ID (defaults to current page)')
|
|
1400
1712
|
},
|
|
1401
|
-
async
|
|
1713
|
+
async args => handleCreateShapeWithText(bridge, args)
|
|
1402
1714
|
);
|
|
1403
1715
|
|
|
1404
1716
|
// figma_set_shape_type - Change the shape variant
|
|
@@ -1409,7 +1721,7 @@ export function registerTools(server, bridge) {
|
|
|
1409
1721
|
nodeId: z.string().describe('The SHAPE_WITH_TEXT node ID'),
|
|
1410
1722
|
shapeType: z.enum(SHAPE_TYPES).describe('New shape type')
|
|
1411
1723
|
},
|
|
1412
|
-
async
|
|
1724
|
+
async args => handleSetShapeType(bridge, args)
|
|
1413
1725
|
);
|
|
1414
1726
|
|
|
1415
1727
|
// figma_create_connector - Create an arrow/connector between nodes
|
|
@@ -1417,23 +1729,35 @@ export function registerTools(server, bridge) {
|
|
|
1417
1729
|
'figma_create_connector',
|
|
1418
1730
|
'FigJam only: create a connector (arrow line) between two nodes for flowcharts and diagrams. Endpoints can attach to nodes via magnets (AUTO recommended), to fixed positions on nodes, or be free-floating on the canvas. Default end cap is ARROW_EQUILATERAL so it looks like an arrow without configuration. ELBOWED is best for orthogonal flowcharts; STRAIGHT only supports CENTER/NONE magnets.',
|
|
1419
1731
|
{
|
|
1420
|
-
start: connectorEndpointSchema
|
|
1421
|
-
|
|
1422
|
-
|
|
1732
|
+
start: connectorEndpointSchema
|
|
1733
|
+
.optional()
|
|
1734
|
+
.describe('Start endpoint: { nodeId, magnet } | { nodeId, position } | { position }'),
|
|
1735
|
+
end: connectorEndpointSchema
|
|
1736
|
+
.optional()
|
|
1737
|
+
.describe('End endpoint: { nodeId, magnet } | { nodeId, position } | { position }'),
|
|
1738
|
+
lineType: z
|
|
1739
|
+
.enum(CONNECTOR_LINE_TYPES)
|
|
1740
|
+
.optional()
|
|
1741
|
+
.default('ELBOWED')
|
|
1742
|
+
.describe('Line routing: ELBOWED (right angles), STRAIGHT, or CURVED'),
|
|
1423
1743
|
startCap: z.enum(CONNECTOR_STROKE_CAPS).optional().default('NONE').describe('Decoration at start endpoint'),
|
|
1424
|
-
endCap: z
|
|
1744
|
+
endCap: z
|
|
1745
|
+
.enum(CONNECTOR_STROKE_CAPS)
|
|
1746
|
+
.optional()
|
|
1747
|
+
.default('ARROW_EQUILATERAL')
|
|
1748
|
+
.describe('Decoration at end endpoint (default arrow)'),
|
|
1425
1749
|
text: z.string().optional().describe('Center label text on the connector'),
|
|
1426
1750
|
strokes: colorSchema.optional().describe('Line color'),
|
|
1427
1751
|
strokeWeight: z.number().optional().describe('Line thickness in pixels'),
|
|
1428
1752
|
parentId: z.string().optional().describe('Parent node ID (defaults to current page)')
|
|
1429
1753
|
},
|
|
1430
|
-
async
|
|
1754
|
+
async args => handleCreateConnector(bridge, args)
|
|
1431
1755
|
);
|
|
1432
1756
|
|
|
1433
1757
|
// figma_set_connector - Update an existing connector
|
|
1434
1758
|
server.tool(
|
|
1435
1759
|
'figma_set_connector',
|
|
1436
|
-
|
|
1760
|
+
"FigJam only: modify an existing connector's endpoints, line type, end caps, or label.",
|
|
1437
1761
|
{
|
|
1438
1762
|
nodeId: z.string().describe('The CONNECTOR node ID'),
|
|
1439
1763
|
start: connectorEndpointSchema.optional().describe('Replacement start endpoint'),
|
|
@@ -1443,7 +1767,7 @@ export function registerTools(server, bridge) {
|
|
|
1443
1767
|
endCap: z.enum(CONNECTOR_STROKE_CAPS).optional().describe('New end decoration'),
|
|
1444
1768
|
text: z.string().optional().describe('Replacement label text')
|
|
1445
1769
|
},
|
|
1446
|
-
async
|
|
1770
|
+
async args => handleSetConnector(bridge, args)
|
|
1447
1771
|
);
|
|
1448
1772
|
|
|
1449
1773
|
// figma_create_section - Create a labeled section (works in Figma and FigJam)
|
|
@@ -1457,26 +1781,33 @@ export function registerTools(server, bridge) {
|
|
|
1457
1781
|
height: z.number().optional().default(400).describe('Height in pixels'),
|
|
1458
1782
|
name: z.string().optional().describe('Section label'),
|
|
1459
1783
|
fills: colorSchema.optional().describe('Section background fill'),
|
|
1460
|
-
sectionContentsHidden: z.boolean().optional().describe(
|
|
1461
|
-
devStatus: z
|
|
1784
|
+
sectionContentsHidden: z.boolean().optional().describe("Visually collapse the section's contents"),
|
|
1785
|
+
devStatus: z
|
|
1786
|
+
.enum(['READY_FOR_DEV', 'COMPLETED'])
|
|
1787
|
+
.optional()
|
|
1788
|
+
.describe('Dev Mode handoff status (only valid on sections directly under a page or another section)'),
|
|
1462
1789
|
devStatusDescription: z.string().optional().describe('Optional description shown with the dev status'),
|
|
1463
1790
|
parentId: z.string().optional().describe('Parent node ID (defaults to current page)')
|
|
1464
1791
|
},
|
|
1465
|
-
async
|
|
1792
|
+
async args => handleCreateSection(bridge, args)
|
|
1466
1793
|
);
|
|
1467
1794
|
|
|
1468
1795
|
// figma_set_section - Update a section
|
|
1469
1796
|
server.tool(
|
|
1470
1797
|
'figma_set_section',
|
|
1471
|
-
|
|
1798
|
+
"Update a section's name, dev status, or content visibility. Pass devStatus: null to clear.",
|
|
1472
1799
|
{
|
|
1473
1800
|
nodeId: z.string().describe('The SECTION node ID'),
|
|
1474
1801
|
name: z.string().optional().describe('New section label'),
|
|
1475
1802
|
sectionContentsHidden: z.boolean().optional().describe('Show or hide section contents'),
|
|
1476
|
-
devStatus: z
|
|
1803
|
+
devStatus: z
|
|
1804
|
+
.enum(['READY_FOR_DEV', 'COMPLETED'])
|
|
1805
|
+
.nullable()
|
|
1806
|
+
.optional()
|
|
1807
|
+
.describe('Set dev status, or null to clear'),
|
|
1477
1808
|
devStatusDescription: z.string().optional().describe('Description shown with dev status')
|
|
1478
1809
|
},
|
|
1479
|
-
async
|
|
1810
|
+
async args => handleSetSection(bridge, args)
|
|
1480
1811
|
);
|
|
1481
1812
|
|
|
1482
1813
|
// figma_create_table - Create a table
|
|
@@ -1488,16 +1819,21 @@ export function registerTools(server, bridge) {
|
|
|
1488
1819
|
y: z.number().optional().default(0).describe('Y position'),
|
|
1489
1820
|
numRows: z.number().int().min(1).optional().default(2).describe('Number of rows'),
|
|
1490
1821
|
numColumns: z.number().int().min(1).optional().default(2).describe('Number of columns'),
|
|
1491
|
-
cells: z
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1822
|
+
cells: z
|
|
1823
|
+
.array(
|
|
1824
|
+
z.object({
|
|
1825
|
+
row: z.number().int().min(0).describe('Row index (0-based)'),
|
|
1826
|
+
column: z.number().int().min(0).describe('Column index (0-based)'),
|
|
1827
|
+
text: z.string().optional().describe('Cell text content'),
|
|
1828
|
+
fills: colorSchema.optional().describe('Cell background fill')
|
|
1829
|
+
})
|
|
1830
|
+
)
|
|
1831
|
+
.optional()
|
|
1832
|
+
.describe('Initial cell content. Cells outside the table bounds are silently ignored.'),
|
|
1497
1833
|
fills: colorSchema.optional().describe('Table background fill'),
|
|
1498
1834
|
parentId: z.string().optional().describe('Parent node ID (defaults to current page)')
|
|
1499
1835
|
},
|
|
1500
|
-
async
|
|
1836
|
+
async args => handleCreateTable(bridge, args)
|
|
1501
1837
|
);
|
|
1502
1838
|
|
|
1503
1839
|
// figma_set_table_cell - Set the text/fill of a table cell
|
|
@@ -1511,7 +1847,7 @@ export function registerTools(server, bridge) {
|
|
|
1511
1847
|
text: z.string().optional().describe('New cell text'),
|
|
1512
1848
|
fills: colorSchema.optional().describe('New cell background fill')
|
|
1513
1849
|
},
|
|
1514
|
-
async
|
|
1850
|
+
async args => handleSetTableCell(bridge, args)
|
|
1515
1851
|
);
|
|
1516
1852
|
|
|
1517
1853
|
// figma_insert_table_row - Insert a row before the given index
|
|
@@ -1522,7 +1858,7 @@ export function registerTools(server, bridge) {
|
|
|
1522
1858
|
nodeId: z.string().describe('The TABLE node ID'),
|
|
1523
1859
|
rowIndex: z.number().int().min(0).describe('Insert position (0 = top)')
|
|
1524
1860
|
},
|
|
1525
|
-
async
|
|
1861
|
+
async args => handleInsertTableRow(bridge, args)
|
|
1526
1862
|
);
|
|
1527
1863
|
|
|
1528
1864
|
// figma_insert_table_column - Insert a column before the given index
|
|
@@ -1533,7 +1869,7 @@ export function registerTools(server, bridge) {
|
|
|
1533
1869
|
nodeId: z.string().describe('The TABLE node ID'),
|
|
1534
1870
|
columnIndex: z.number().int().min(0).describe('Insert position (0 = leftmost)')
|
|
1535
1871
|
},
|
|
1536
|
-
async
|
|
1872
|
+
async args => handleInsertTableColumn(bridge, args)
|
|
1537
1873
|
);
|
|
1538
1874
|
|
|
1539
1875
|
// figma_remove_table_row - Remove a row
|
|
@@ -1544,7 +1880,7 @@ export function registerTools(server, bridge) {
|
|
|
1544
1880
|
nodeId: z.string().describe('The TABLE node ID'),
|
|
1545
1881
|
rowIndex: z.number().int().min(0).describe('Row to remove')
|
|
1546
1882
|
},
|
|
1547
|
-
async
|
|
1883
|
+
async args => handleRemoveTableRow(bridge, args)
|
|
1548
1884
|
);
|
|
1549
1885
|
|
|
1550
1886
|
// figma_remove_table_column - Remove a column
|
|
@@ -1555,7 +1891,7 @@ export function registerTools(server, bridge) {
|
|
|
1555
1891
|
nodeId: z.string().describe('The TABLE node ID'),
|
|
1556
1892
|
columnIndex: z.number().int().min(0).describe('Column to remove')
|
|
1557
1893
|
},
|
|
1558
|
-
async
|
|
1894
|
+
async args => handleRemoveTableColumn(bridge, args)
|
|
1559
1895
|
);
|
|
1560
1896
|
|
|
1561
1897
|
// figma_resize_table_row - Set row height
|
|
@@ -1567,7 +1903,7 @@ export function registerTools(server, bridge) {
|
|
|
1567
1903
|
rowIndex: z.number().int().min(0).describe('Row index'),
|
|
1568
1904
|
height: z.number().min(1).describe('New height in pixels')
|
|
1569
1905
|
},
|
|
1570
|
-
async
|
|
1906
|
+
async args => handleResizeTableRow(bridge, args)
|
|
1571
1907
|
);
|
|
1572
1908
|
|
|
1573
1909
|
// figma_resize_table_column - Set column width
|
|
@@ -1579,7 +1915,7 @@ export function registerTools(server, bridge) {
|
|
|
1579
1915
|
columnIndex: z.number().int().min(0).describe('Column index'),
|
|
1580
1916
|
width: z.number().min(1).describe('New width in pixels')
|
|
1581
1917
|
},
|
|
1582
|
-
async
|
|
1918
|
+
async args => handleResizeTableColumn(bridge, args)
|
|
1583
1919
|
);
|
|
1584
1920
|
|
|
1585
1921
|
// figma_move_table_row - Reorder rows
|
|
@@ -1591,7 +1927,7 @@ export function registerTools(server, bridge) {
|
|
|
1591
1927
|
fromIndex: z.number().int().min(0).describe('Source row index'),
|
|
1592
1928
|
toIndex: z.number().int().min(0).describe('Destination row index')
|
|
1593
1929
|
},
|
|
1594
|
-
async
|
|
1930
|
+
async args => handleMoveTableRow(bridge, args)
|
|
1595
1931
|
);
|
|
1596
1932
|
|
|
1597
1933
|
// figma_move_table_column - Reorder columns
|
|
@@ -1603,7 +1939,7 @@ export function registerTools(server, bridge) {
|
|
|
1603
1939
|
fromIndex: z.number().int().min(0).describe('Source column index'),
|
|
1604
1940
|
toIndex: z.number().int().min(0).describe('Destination column index')
|
|
1605
1941
|
},
|
|
1606
|
-
async
|
|
1942
|
+
async args => handleMoveTableColumn(bridge, args)
|
|
1607
1943
|
);
|
|
1608
1944
|
|
|
1609
1945
|
// figma_create_code_block - Create a syntax-highlighted code block
|
|
@@ -1617,19 +1953,19 @@ export function registerTools(server, bridge) {
|
|
|
1617
1953
|
codeLanguage: z.enum(CODE_LANGUAGES).optional().default('PLAINTEXT').describe('Syntax highlighting language'),
|
|
1618
1954
|
parentId: z.string().optional().describe('Parent node ID (defaults to current page)')
|
|
1619
1955
|
},
|
|
1620
|
-
async
|
|
1956
|
+
async args => handleCreateCodeBlock(bridge, args)
|
|
1621
1957
|
);
|
|
1622
1958
|
|
|
1623
1959
|
// figma_set_code_block - Update a code block
|
|
1624
1960
|
server.tool(
|
|
1625
1961
|
'figma_set_code_block',
|
|
1626
|
-
|
|
1962
|
+
"FigJam only: update an existing code block's code text or language.",
|
|
1627
1963
|
{
|
|
1628
1964
|
nodeId: z.string().describe('The CODE_BLOCK node ID'),
|
|
1629
1965
|
code: z.string().optional().describe('New code text'),
|
|
1630
1966
|
codeLanguage: z.enum(CODE_LANGUAGES).optional().describe('New syntax-highlighting language')
|
|
1631
1967
|
},
|
|
1632
|
-
async
|
|
1968
|
+
async args => handleSetCodeBlock(bridge, args)
|
|
1633
1969
|
);
|
|
1634
1970
|
|
|
1635
1971
|
// figma_create_link_preview - Embed a URL (auto-detects iframe vs. card)
|
|
@@ -1642,7 +1978,7 @@ export function registerTools(server, bridge) {
|
|
|
1642
1978
|
url: z.string().describe('The URL to preview'),
|
|
1643
1979
|
parentId: z.string().optional().describe('Parent node ID (defaults to current page)')
|
|
1644
1980
|
},
|
|
1645
|
-
async
|
|
1981
|
+
async args => handleCreateLinkPreview(bridge, args)
|
|
1646
1982
|
);
|
|
1647
1983
|
|
|
1648
1984
|
// ---- Prototype tools ----
|
|
@@ -1654,7 +1990,7 @@ export function registerTools(server, bridge) {
|
|
|
1654
1990
|
{
|
|
1655
1991
|
nodeId: z.string().describe('Node ID to read reactions from')
|
|
1656
1992
|
},
|
|
1657
|
-
async
|
|
1993
|
+
async args => handleGetReactions(bridge, args)
|
|
1658
1994
|
);
|
|
1659
1995
|
|
|
1660
1996
|
// figma_add_reaction - Add a prototype interaction to a node
|
|
@@ -1663,43 +1999,121 @@ export function registerTools(server, bridge) {
|
|
|
1663
1999
|
'Prototype (Figma Design only): add a reaction (interaction) to a node. A reaction pairs a trigger with an action. Existing reactions are preserved.\n\nTrigger types: ON_CLICK, ON_HOVER, ON_PRESS, ON_DRAG, ON_MEDIA_END, AFTER_TIMEOUT, MOUSE_UP, MOUSE_DOWN, MOUSE_ENTER, MOUSE_LEAVE, ON_KEY_DOWN, ON_MEDIA_HIT.\n\nAction types: NODE (navigate/overlay/scroll — set navigation field), BACK, CLOSE, URL.\n\nFor NODE actions, navigation values: NAVIGATE (go to frame), SWAP (replace current frame), OVERLAY (open as overlay), SCROLL_TO (scroll to frame), CHANGE_TO (change component variant).',
|
|
1664
2000
|
{
|
|
1665
2001
|
nodeId: z.string().describe('Node ID to add the reaction to'),
|
|
1666
|
-
trigger: z
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
2002
|
+
trigger: z
|
|
2003
|
+
.object({
|
|
2004
|
+
type: z
|
|
2005
|
+
.enum([
|
|
2006
|
+
'ON_CLICK',
|
|
2007
|
+
'ON_HOVER',
|
|
2008
|
+
'ON_PRESS',
|
|
2009
|
+
'ON_DRAG',
|
|
2010
|
+
'ON_MEDIA_END',
|
|
2011
|
+
'AFTER_TIMEOUT',
|
|
2012
|
+
'MOUSE_UP',
|
|
2013
|
+
'MOUSE_DOWN',
|
|
2014
|
+
'MOUSE_ENTER',
|
|
2015
|
+
'MOUSE_LEAVE',
|
|
2016
|
+
'ON_KEY_DOWN',
|
|
2017
|
+
'ON_MEDIA_HIT'
|
|
2018
|
+
])
|
|
2019
|
+
.describe('Trigger type'),
|
|
2020
|
+
timeout: z.number().optional().describe('Delay in ms — required for AFTER_TIMEOUT'),
|
|
2021
|
+
delay: z.number().optional().describe('Delay in ms — for MOUSE_UP, MOUSE_DOWN, MOUSE_ENTER, MOUSE_LEAVE'),
|
|
2022
|
+
device: z
|
|
2023
|
+
.enum(['KEYBOARD', 'XBOX_ONE', 'PS4', 'SWITCH_PRO', 'UNKNOWN_CONTROLLER'])
|
|
2024
|
+
.optional()
|
|
2025
|
+
.describe('Input device — for ON_KEY_DOWN (default: KEYBOARD)'),
|
|
2026
|
+
keyCodes: z.array(z.number()).optional().describe('Key codes — for ON_KEY_DOWN'),
|
|
2027
|
+
mediaHitTime: z.number().optional().describe('Time in seconds — for ON_MEDIA_HIT')
|
|
2028
|
+
})
|
|
2029
|
+
.describe('What triggers the reaction'),
|
|
2030
|
+
action: z
|
|
2031
|
+
.object({
|
|
2032
|
+
type: z
|
|
2033
|
+
.enum(['NODE', 'BACK', 'CLOSE', 'URL'])
|
|
2034
|
+
.describe(
|
|
2035
|
+
'Action type. NODE covers all navigation (use navigation field to specify NAVIGATE/OVERLAY/SCROLL_TO/SWAP/CHANGE_TO). BACK goes to previous frame. CLOSE closes overlay. URL opens a URL.'
|
|
2036
|
+
),
|
|
2037
|
+
destinationId: z.string().optional().describe('Target frame/node ID — for NODE action'),
|
|
2038
|
+
url: z.string().optional().describe('URL string — required for URL action'),
|
|
2039
|
+
openInNewTab: z.boolean().optional().describe('Open URL in a new tab (default false) — for URL action'),
|
|
2040
|
+
navigation: z
|
|
2041
|
+
.enum(['NAVIGATE', 'SWAP', 'OVERLAY', 'SCROLL_TO', 'CHANGE_TO'])
|
|
2042
|
+
.optional()
|
|
2043
|
+
.describe('Navigation type for NODE action (default: NAVIGATE)'),
|
|
2044
|
+
transition: z
|
|
2045
|
+
.object({
|
|
2046
|
+
type: z
|
|
2047
|
+
.enum([
|
|
2048
|
+
'DISSOLVE',
|
|
2049
|
+
'SMART_ANIMATE',
|
|
2050
|
+
'SCROLL_ANIMATE',
|
|
2051
|
+
'MOVE_IN',
|
|
2052
|
+
'MOVE_OUT',
|
|
2053
|
+
'PUSH',
|
|
2054
|
+
'SLIDE_IN',
|
|
2055
|
+
'SLIDE_OUT'
|
|
2056
|
+
])
|
|
2057
|
+
.describe(
|
|
2058
|
+
'Transition type. DISSOLVE/SMART_ANIMATE/SCROLL_ANIMATE take no direction; MOVE_IN/MOVE_OUT/PUSH/SLIDE_IN/SLIDE_OUT require direction.'
|
|
2059
|
+
),
|
|
2060
|
+
direction: z
|
|
2061
|
+
.enum(['LEFT', 'RIGHT', 'TOP', 'BOTTOM'])
|
|
2062
|
+
.optional()
|
|
2063
|
+
.describe('Direction — required for MOVE_IN, MOVE_OUT, PUSH, SLIDE_IN, SLIDE_OUT'),
|
|
2064
|
+
matchLayers: z
|
|
2065
|
+
.boolean()
|
|
2066
|
+
.optional()
|
|
2067
|
+
.describe(
|
|
2068
|
+
'Smart-match shared layers across frames during a directional transition (default false). Only used by directional types.'
|
|
2069
|
+
),
|
|
2070
|
+
duration: z.number().optional().describe('Duration in seconds (default 0.3)'),
|
|
2071
|
+
easing: z
|
|
2072
|
+
.object({
|
|
2073
|
+
type: z
|
|
2074
|
+
.enum([
|
|
2075
|
+
'LINEAR',
|
|
2076
|
+
'EASE_IN',
|
|
2077
|
+
'EASE_OUT',
|
|
2078
|
+
'EASE_IN_AND_OUT',
|
|
2079
|
+
'EASE_IN_BACK',
|
|
2080
|
+
'EASE_OUT_BACK',
|
|
2081
|
+
'EASE_IN_AND_OUT_BACK',
|
|
2082
|
+
'CUSTOM_CUBIC_BEZIER',
|
|
2083
|
+
'GENTLE',
|
|
2084
|
+
'QUICK',
|
|
2085
|
+
'BOUNCY',
|
|
2086
|
+
'SLOW',
|
|
2087
|
+
'CUSTOM_SPRING'
|
|
2088
|
+
])
|
|
2089
|
+
.describe('Easing type. GENTLE/QUICK/BOUNCY/SLOW are spring presets.'),
|
|
2090
|
+
easingFunctionCubicBezier: z
|
|
2091
|
+
.object({
|
|
2092
|
+
x1: z.number(),
|
|
2093
|
+
y1: z.number(),
|
|
2094
|
+
x2: z.number(),
|
|
2095
|
+
y2: z.number()
|
|
2096
|
+
})
|
|
2097
|
+
.optional()
|
|
2098
|
+
.describe('Cubic bezier control points — required for CUSTOM_CUBIC_BEZIER')
|
|
2099
|
+
})
|
|
2100
|
+
.optional()
|
|
2101
|
+
.describe('Easing curve (default: LINEAR)')
|
|
2102
|
+
})
|
|
2103
|
+
.optional()
|
|
2104
|
+
.describe('Transition animation — omit for no animation'),
|
|
2105
|
+
preserveScrollPosition: z
|
|
2106
|
+
.boolean()
|
|
2107
|
+
.optional()
|
|
2108
|
+
.describe('Preserve scroll position on navigate (default false)'),
|
|
2109
|
+
overlayRelativePosition: z
|
|
2110
|
+
.object({ x: z.number(), y: z.number() })
|
|
2111
|
+
.optional()
|
|
2112
|
+
.describe('Overlay position offset — for OVERLAY navigation')
|
|
2113
|
+
})
|
|
2114
|
+
.describe('What happens when the trigger fires')
|
|
2115
|
+
},
|
|
2116
|
+
async args => handleAddReaction(bridge, args)
|
|
1703
2117
|
);
|
|
1704
2118
|
|
|
1705
2119
|
// figma_remove_reaction - Remove a reaction by index
|
|
@@ -1710,7 +2124,7 @@ export function registerTools(server, bridge) {
|
|
|
1710
2124
|
nodeId: z.string().describe('Node ID to remove the reaction from'),
|
|
1711
2125
|
index: z.number().int().min(0).describe('Zero-based index of the reaction to remove')
|
|
1712
2126
|
},
|
|
1713
|
-
async
|
|
2127
|
+
async args => handleRemoveReaction(bridge, args)
|
|
1714
2128
|
);
|
|
1715
2129
|
|
|
1716
2130
|
// figma_set_flow_starting_point - Set or clear a prototype flow starting point
|
|
@@ -1718,10 +2132,17 @@ export function registerTools(server, bridge) {
|
|
|
1718
2132
|
'figma_set_flow_starting_point',
|
|
1719
2133
|
'Prototype (Figma Design only): set a top-level frame as a prototype flow starting point on the current page, or clear it. Flow starting points are page-level — Figma stores them as { nodeId, name } entries on the page.',
|
|
1720
2134
|
{
|
|
1721
|
-
nodeId: z
|
|
1722
|
-
|
|
2135
|
+
nodeId: z
|
|
2136
|
+
.string()
|
|
2137
|
+
.describe('Frame node ID to set as flow starting point. Must be FRAME, COMPONENT, or COMPONENT_SET.'),
|
|
2138
|
+
flowName: z
|
|
2139
|
+
.string()
|
|
2140
|
+
.optional()
|
|
2141
|
+
.describe(
|
|
2142
|
+
'Name for the flow (defaults to "Flow 1" if omitted). If the frame is already a flow starting point, its name is updated.'
|
|
2143
|
+
),
|
|
1723
2144
|
clear: z.boolean().optional().describe('If true, remove the flow starting point for this frame from the page')
|
|
1724
2145
|
},
|
|
1725
|
-
async
|
|
2146
|
+
async args => handleSetFlowStartingPoint(bridge, args)
|
|
1726
2147
|
);
|
|
1727
2148
|
}
|