genable-mcp 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1220 @@
1
+ [
2
+ {
3
+ "name": "jsx",
4
+ "description": "Create design trees with nested JSX markup. One jsx call builds a complete subtree atomically — nesting is the hierarchy. Keep a single logical unit inside one call; the returned root's children are already built, not stubs to be filled in later.\n\nExamples:\n jsx({markup: \"<frame name='Card' layout='column' padding={16} fill='#FFFFFF' w='fill' />\"})\n jsx({markup: \"<frame name='Row' layout='row' gap={8} padding={12} w='fill'><icon name='lucide:settings' size={20} /><text name='Label' w='fill'>Account</text><icon name='lucide:chevron-right' size={16} /></frame>\"})\n\nElements: frame, text, rect, ellipse, line, icon, image, instance, component, group, section, vector\nAttributes (frame): layout, justify, items, wrap, w, h, minW, maxW, p, gap, bg, fill, rounded, stroke, shadow, blur, bgblur, opacity, layoutPositioning\nAttributes (text): size, weight, lineHeight, font, fill, w (w=\"fill\" for wrap), maxLines, textTruncation\nEffects: shadow=\"0,8,32,0,#0006\" or shadow={shadow(0,8,32,0,'#0006')}; blur={10} for layer blur; bgblur={20} for frosted-glass/glassmorphism background blur. Multiple effects merge automatically.\nDecoration in auto-layout: floating orbs/blobs/decorative shapes inside a row/column parent need layoutPositioning=\"absolute\" so they don't get stacked into the main-axis flow.\nFull-frame backgrounds: set the parent frame's bg directly (supports gradients via bg=\"linear-gradient(135deg, #A 0%, #B 100%)\"). Don't add a separate <rect> backdrop.\nText: <text size={24}>content here</text>\nInstance: <instance ref=\"Button\" variant=\"Size=Large\"/>\nSelf-closing: <line w=\"fill\" stroke=\"#E5E7EB\"/> (use line for dividers/separators; rect/ellipse for SMALL pure decoration with no children — page-level backgrounds belong on the parent frame's bg)\nArc/Ring: <ellipse w={120} h={120} arc=\"0 270\" fill=\"#4F46E5\"/> (arc=\"start end innerRadius?\" — innerRadius 0-1 makes a donut/ring)\nGrid layout: call help({ name: \"grid-layout\" }) for tracks, gaps, and when row/column is a better fit\nVariable binding: fill/bg/stroke accept qualified bare-name token strings (e.g. bg=\"$Theme/Bg/Surface\"). Object literals (fill={{variable_id:...}}) drop the binding silently — always use the string form.\n\nSwap an existing subtree: jsx({replaceId: \"<id>\", markup: \"...\"}) replaces the old node at the same parent and sibling index atomically, preserving position in one call. Markup must have a single root. Use jsx for tree creation; edit for property updates on known nodes.",
5
+ "parameters": {
6
+ "type": "object",
7
+ "properties": {
8
+ "markup": {
9
+ "type": "string",
10
+ "description": "JSX-like nested markup string"
11
+ },
12
+ "parent": {
13
+ "type": "string",
14
+ "description": "Target parent node ID — the frame/container the new subtree will live inside"
15
+ },
16
+ "replaceId": {
17
+ "type": "string",
18
+ "description": "Replace this existing node in-place (keeps parent + sibling index). Old node is deleted on success. Markup must be single-root. Mutually exclusive with parent."
19
+ },
20
+ "insertIndex": {
21
+ "type": "number",
22
+ "description": "Position among parent siblings (0 = first). Omit to append at end. Ignored when replaceId is set (inherits old node index)."
23
+ }
24
+ },
25
+ "required": [
26
+ "markup"
27
+ ]
28
+ },
29
+ "mutates": true
30
+ },
31
+ {
32
+ "name": "inspect",
33
+ "description": "Read design node(s) — choose what to surface with `facets`.\n\nDefault (no facets) returns a skeleton: id, name, type, role, children.\nFor anything else, list the facets you need — nothing else is included.\n\nFacets:\n structure name, type, size, layout shorthand — cheap overview\n layout layoutMode/gap/padding/align/sizing (row/column, fill/hug, etc.)\n paint|fill fills + Paint.boundVariables.color (see bound tokens)\n stroke strokes, strokeWeight, strokeAlign, dashPattern\n effects shadows, blurs\n typography|text fontFamily, fontSize, fontWeight, lineHeight, letterSpacing\n appearance opacity, visible, blendMode, cornerRadius, clipsContent\n variables node-level boundVariables + explicitVariableModes (token bindings)\n all everything\n\nParameters:\n node \"/\" for page root, or node ID from jsx/inspect results (e.g. \"100:5\").\n facets array of facet names listed above.\n depth Max tree depth (default: 5, max: 10).\n\nExamples:\n inspect({node: \"/\"}) → page skeleton\n inspect({node: \"100:5\"}) → one-node skeleton\n inspect({node: \"100:5\", facets: [\"variables\"]}) → token bindings only\n inspect({node: \"100:5\", facets: [\"layout\", \"paint\"]}) → layout + fills\n inspect({node: \"100:5\", facets: [\"all\"]}) → full properties\n\nUse `get_screenshot` for visual verification. Use `describe` for lint/validation.",
34
+ "parameters": {
35
+ "type": "object",
36
+ "properties": {
37
+ "node": {
38
+ "type": "string",
39
+ "description": "\"/\" for page root, or node ID (e.g. \"100:5\")."
40
+ },
41
+ "facets": {
42
+ "type": "array",
43
+ "items": {
44
+ "type": "string",
45
+ "description": "A single facet name.",
46
+ "enum": [
47
+ "structure",
48
+ "paint",
49
+ "layout",
50
+ "text",
51
+ "effects",
52
+ "variables",
53
+ "appearance",
54
+ "stroke",
55
+ "fill",
56
+ "typography",
57
+ "all"
58
+ ]
59
+ },
60
+ "description": "Property buckets to surface. Omit for a skeleton-only response."
61
+ },
62
+ "depth": {
63
+ "type": "number",
64
+ "description": "Max depth (default: 5, max: 10)"
65
+ }
66
+ },
67
+ "required": [
68
+ "node"
69
+ ]
70
+ }
71
+ },
72
+ {
73
+ "name": "describe",
74
+ "description": "Validate a design subtree — semantic description, role detection, and lint rules. Checks for layout conflicts, overflow, missing properties, and structural issues.\n\nParameters:\n node: Node ID to describe (e.g. \"100:5\"). Required.\n depth: How deep to check children (default: 3, max: 8).\n\nReturns per-node: role, visual summary, layout summary, and issues (severity: error/warning/info).\n\nExamples:\n describe({node: \"100:5\"}) → validate subtree, depth 3\n describe({node: \"100:5\", depth: 1}) → shallow check (root + direct children only)",
75
+ "parameters": {
76
+ "type": "object",
77
+ "properties": {
78
+ "node": {
79
+ "type": "string",
80
+ "description": "Node ID to describe (e.g. \"100:5\")."
81
+ },
82
+ "depth": {
83
+ "type": "number",
84
+ "description": "Max depth to check (default: 3, max: 8)"
85
+ }
86
+ },
87
+ "required": [
88
+ "node"
89
+ ]
90
+ }
91
+ },
92
+ {
93
+ "name": "edit",
94
+ "description": "Batch update properties on multiple nodes.\n\nFor single-property changes, prefer focused setters:\n set_text — text content\n set_fill — fill/background color\n set_stroke — border\n set_layout — padding, gap, direction\n\nUse edit for batch fixes or properties not covered by setters (sizing, radius, opacity, effects, component props):\n edit({nodes: [\n {node: \"1:1\", props: {w: \"fill\", corner: 8}}, // Figma native props\n {node: \"1:2\", props: {opacity: 0.6}},\n {node: \"1:3\", props: {Label: \"Sign In\"}}, // instance TEXT prop (by display name)\n ]})\n\nFor instances, use component property DISPLAY NAMES (e.g. \"Label\") — edit resolves them to Figma's internal keys automatically. Component props can be mixed with Figma props in the same call.",
95
+ "parameters": {
96
+ "type": "object",
97
+ "properties": {
98
+ "node": {
99
+ "type": "string",
100
+ "description": "Node ID (e.g. \"1:2\") from jsx/inspect results"
101
+ },
102
+ "nodes": {
103
+ "type": "array",
104
+ "description": "Batch: array of {node, props?, content?} objects. No hard item cap — real ceiling is LLM output stream length (~10KB+ of rendered params can stall mid-JSON). If a batch is large and props are rich, split into 2 calls.",
105
+ "items": {
106
+ "type": "object",
107
+ "description": "{node, props?, content?} — at least one of props or content is required",
108
+ "properties": {
109
+ "node": {
110
+ "type": "string",
111
+ "description": "Node ID to update"
112
+ },
113
+ "props": {
114
+ "type": "object",
115
+ "description": "Properties to update (object, not a stringified JSON)"
116
+ },
117
+ "content": {
118
+ "type": "string",
119
+ "description": "New text content (for text nodes / overrides)"
120
+ }
121
+ },
122
+ "required": [
123
+ "node"
124
+ ]
125
+ }
126
+ },
127
+ "props": {
128
+ "type": "object",
129
+ "description": "Properties to update (single mode)"
130
+ },
131
+ "content": {
132
+ "type": "string",
133
+ "description": "New text content (single mode)"
134
+ }
135
+ }
136
+ },
137
+ "mutates": true
138
+ },
139
+ {
140
+ "name": "find_nodes",
141
+ "description": "Search nodes by name or type. Scoped to the current page — call switch_page first if your target lives on a different page.\n\nExamples:\n find_nodes({query: \"Button\"})\n find_nodes({query: \"frame\", scope: \"1:2\"})",
142
+ "parameters": {
143
+ "type": "object",
144
+ "properties": {
145
+ "query": {
146
+ "type": "string",
147
+ "description": "Search query — matches node name or type"
148
+ },
149
+ "scope": {
150
+ "type": "string",
151
+ "description": "Limit search to subtree. Node ID (e.g. \"1:2\"). Default: entire page."
152
+ }
153
+ },
154
+ "required": [
155
+ "query"
156
+ ]
157
+ }
158
+ },
159
+ {
160
+ "name": "discover_props",
161
+ "description": "Discover unique property values in a subtree.\n\nExamples:\n discover_props({node: \"1:2\", props: [\"fillColor\", \"fontSize\"]})\n\nSearchable properties: fillColor, textColor, strokeColor, strokeWeight, opacity, cornerRadius, gap, fontSize, fontFamily, fontWeight.",
162
+ "parameters": {
163
+ "type": "object",
164
+ "properties": {
165
+ "node": {
166
+ "type": "string",
167
+ "description": "Target node ID (e.g. \"1:2\")"
168
+ },
169
+ "props": {
170
+ "type": "array",
171
+ "description": "Properties to discover",
172
+ "items": {
173
+ "type": "string",
174
+ "description": "Property name"
175
+ }
176
+ }
177
+ },
178
+ "required": [
179
+ "node",
180
+ "props"
181
+ ]
182
+ }
183
+ },
184
+ {
185
+ "name": "replace_props",
186
+ "description": "Batch search-and-replace property values in a subtree.\n\nExamples:\n replace_props({node: \"1:2\", rules: [{prop: \"fillColor\", from: \"#FFF\", to: \"#000\"}]})\n replace_props({node: \"1:2\", rules: [\n {prop: \"fillColor\", from: \"#FFF\", to: \"#000\"},\n {prop: \"fontSize\", from: \"14\", to: \"16\"}\n ]})",
187
+ "parameters": {
188
+ "type": "object",
189
+ "properties": {
190
+ "node": {
191
+ "type": "string",
192
+ "description": "Target node ID (e.g. \"1:2\")"
193
+ },
194
+ "rules": {
195
+ "type": "array",
196
+ "description": "Replacement rules",
197
+ "items": {
198
+ "type": "object",
199
+ "description": "{prop, from, to}",
200
+ "properties": {
201
+ "prop": {
202
+ "type": "string",
203
+ "description": "Property name"
204
+ },
205
+ "from": {
206
+ "type": "string",
207
+ "description": "Value to find"
208
+ },
209
+ "to": {
210
+ "type": "string",
211
+ "description": "Value to replace with"
212
+ }
213
+ },
214
+ "required": [
215
+ "prop",
216
+ "from",
217
+ "to"
218
+ ]
219
+ }
220
+ }
221
+ },
222
+ "required": [
223
+ "node",
224
+ "rules"
225
+ ]
226
+ },
227
+ "mutates": true
228
+ },
229
+ {
230
+ "name": "find_references",
231
+ "description": "Find every node on the current page that references a given variable.\n\nThis is the REVERSE of inspect: inspect asks \"what does this node bind?\",\nfind_references asks \"who uses this variable?\". Use it when renaming,\nauditing, or swapping tokens — you need to know all the binding sites\nbefore you touch the variable.\n\nScan scope: currentPage only. Invisible nodes are skipped by default.\nNode-level bindings (e.g. boundVariables.paddingLeft) and per-paint color\nbindings (fills[i].boundVariables.color, strokes[i].boundVariables.color)\nare both returned.\n\nParameters:\n variable — VariableID (e.g. \"VariableID:1:5\"). Required.\n\nReturns:\n {variable, variableName, variableType, referenceCount,\n references: [{nodeId, nodeName, nodeType, path}, ...]}\n\n path values look like:\n \"boundVariables.paddingLeft\"\n \"fills[0].boundVariables.color\"\n \"strokes[2].boundVariables.color\"\n\nExamples:\n find_references({variable: \"VariableID:1:5\"})",
232
+ "parameters": {
233
+ "type": "object",
234
+ "properties": {
235
+ "variable": {
236
+ "type": "string",
237
+ "description": "VariableID to look up (e.g. \"VariableID:1:5\"). Get IDs from list_variables."
238
+ }
239
+ },
240
+ "required": [
241
+ "variable"
242
+ ]
243
+ }
244
+ },
245
+ {
246
+ "name": "delete_node",
247
+ "description": "Delete a node and its children.\n\nExamples:\n delete_node({node: \"1:2\"})",
248
+ "parameters": {
249
+ "type": "object",
250
+ "properties": {
251
+ "node": {
252
+ "type": "string",
253
+ "description": "Node ID (e.g. \"1:2\")"
254
+ }
255
+ },
256
+ "required": [
257
+ "node"
258
+ ]
259
+ },
260
+ "mutates": true
261
+ },
262
+ {
263
+ "name": "move_node",
264
+ "description": "Relocate a node without recreating it. Preserves IDs, bound variables, and component instances across the move, so callers tracking the node by ID never need to re-discover it. Use for: (a) changing child order within a container, (b) moving a subtree into a different parent, (c) fixing a placement mistake after jsx.\n\nExamples:\n move_node({node: \"1:3\", name: \"NewTitle\"}) — rename in place\n move_node({node: \"1:3\", parent: \"1:4\"}) — move into parent 1:4\n move_node({node: \"1:5\", index: 0}) — reorder within current parent",
265
+ "parameters": {
266
+ "type": "object",
267
+ "properties": {
268
+ "node": {
269
+ "type": "string",
270
+ "description": "Node ID (e.g. \"1:3\") to move/rename"
271
+ },
272
+ "parent": {
273
+ "type": "string",
274
+ "description": "Target parent node ID — the frame/container the node should live inside after the call"
275
+ },
276
+ "name": {
277
+ "type": "string",
278
+ "description": "New name (rename without changing parent)"
279
+ },
280
+ "index": {
281
+ "type": "number",
282
+ "description": "Reorder position among siblings. 0 = first, -1 = last."
283
+ }
284
+ },
285
+ "required": [
286
+ "node"
287
+ ]
288
+ },
289
+ "mutates": true
290
+ },
291
+ {
292
+ "name": "clone_node",
293
+ "description": "Deep-copy a node with optional property overrides.\n\nExamples:\n clone_node({node: \"1:2\"}) — clone to page root, same name\n clone_node({node: \"1:2\", parent: \"/\"}) — clone to page root explicitly\n clone_node({node: \"1:2\", parent: \"/\", name: \"Hero Copy\"}) — clone to root with custom name\n clone_node({node: \"1:2\", parent: \"1:4\"}) — clone into parent node 1:4\n clone_node({node: \"1:2\", parent: \"1:4\", overrides: {\"bg\": \"#D9D9D9\"}})",
294
+ "parameters": {
295
+ "type": "object",
296
+ "properties": {
297
+ "node": {
298
+ "type": "string",
299
+ "description": "Source node ID (e.g. \"1:2\")"
300
+ },
301
+ "parent": {
302
+ "type": "string",
303
+ "description": "Target parent node ID the clone should live inside, or \"/\" for page root. Defaults to page root."
304
+ },
305
+ "name": {
306
+ "type": "string",
307
+ "description": "Name for the cloned node. Defaults to source node name."
308
+ },
309
+ "overrides": {
310
+ "type": "object",
311
+ "description": "Property overrides. Use \"Child.prop\" for child overrides."
312
+ }
313
+ },
314
+ "required": [
315
+ "node"
316
+ ]
317
+ },
318
+ "mutates": true
319
+ },
320
+ {
321
+ "name": "skill",
322
+ "description": "Load a procedural skill — workflow + tool sequence + anti-patterns. Use FIRST when the user is changing/adjusting existing canvas, OR before creating a new design if a matching skill exists.\n\nExample: skill({ name: \"restyle\" })",
323
+ "parameters": {
324
+ "type": "object",
325
+ "properties": {
326
+ "name": {
327
+ "type": "string",
328
+ "description": "The skill name exactly as it appears in the KNOWLEDGE LIBRARY menu — no \"skill:\" prefix, no quotes."
329
+ }
330
+ },
331
+ "required": [
332
+ "name"
333
+ ]
334
+ }
335
+ },
336
+ {
337
+ "name": "style",
338
+ "description": "Load a visual style preset — color tokens, typography, shape, depth. Use when picking a named aesthetic from the menu before generating new design.\n\nExample: style({ name: \"neon-cyber\" })",
339
+ "parameters": {
340
+ "type": "object",
341
+ "properties": {
342
+ "name": {
343
+ "type": "string",
344
+ "description": "The style name exactly as it appears in the KNOWLEDGE LIBRARY menu — no \"style:\" prefix, no quotes."
345
+ }
346
+ },
347
+ "required": [
348
+ "name"
349
+ ]
350
+ }
351
+ },
352
+ {
353
+ "name": "anatomy",
354
+ "description": "Load a component anatomy reference — structural blueprint of a UI component (parts, slots, hierarchy). Use before building complex components.\n\nExample: anatomy({ name: \"data-table\" })",
355
+ "parameters": {
356
+ "type": "object",
357
+ "properties": {
358
+ "name": {
359
+ "type": "string",
360
+ "description": "The anatomy name exactly as it appears in the KNOWLEDGE LIBRARY menu — no \"anatomy:\" prefix, no quotes."
361
+ }
362
+ },
363
+ "required": [
364
+ "name"
365
+ ]
366
+ }
367
+ },
368
+ {
369
+ "name": "guideline",
370
+ "description": "Load a page-type design guideline — layout patterns for landing pages, dashboards, login flows, forms, etc.\n\nExample: guideline({ name: \"form\" })",
371
+ "parameters": {
372
+ "type": "object",
373
+ "properties": {
374
+ "name": {
375
+ "type": "string",
376
+ "description": "The guideline name exactly as it appears in the KNOWLEDGE LIBRARY menu — no \"guideline:\" prefix, no quotes."
377
+ }
378
+ },
379
+ "required": [
380
+ "name"
381
+ ]
382
+ }
383
+ },
384
+ {
385
+ "name": "help",
386
+ "description": "Load narrow how-to / process help — tool usage rules, edge cases, naming conventions.\n\nExample: help({ name: \"interaction-model\" })",
387
+ "parameters": {
388
+ "type": "object",
389
+ "properties": {
390
+ "name": {
391
+ "type": "string",
392
+ "description": "The help name exactly as it appears in the KNOWLEDGE LIBRARY menu — no \"help:\" prefix, no quotes."
393
+ }
394
+ },
395
+ "required": [
396
+ "name"
397
+ ]
398
+ }
399
+ },
400
+ {
401
+ "name": "list_variables",
402
+ "description": "List variables as a flat array with referenced collections.\n\nReturns {data: {variables[], collections[], nextCursor?}}. Each variable carries\nits full Figma shape: id, name, variableCollectionId, resolvedType, valuesByMode.\ncollections[] only includes collections referenced by the returned variables\n(use for mode-name resolution).\n\nParameters:\n collection — VariableCollectionId to filter by\n filter — substring match on variable name (case-insensitive)\n cursor — opaque pagination cursor from a previous call\n limit — max variables per page (default 100)\n\nExamples:\n list_variables()\n list_variables({collection: \"VariableCollectionId:1:2\"})\n list_variables({filter: \"bg\"})\n list_variables({cursor: \"100\"})",
403
+ "parameters": {
404
+ "type": "object",
405
+ "properties": {
406
+ "collection": {
407
+ "type": "string",
408
+ "description": "VariableCollectionId to filter by"
409
+ },
410
+ "filter": {
411
+ "type": "string",
412
+ "description": "Substring match on variable name (case-insensitive)"
413
+ },
414
+ "cursor": {
415
+ "type": "string",
416
+ "description": "Opaque pagination cursor from a previous call"
417
+ },
418
+ "limit": {
419
+ "type": "number",
420
+ "description": "Max variables per page (default 100)"
421
+ }
422
+ }
423
+ }
424
+ },
425
+ {
426
+ "name": "create_collection",
427
+ "description": "Create a VariableCollection with named modes.\n\nThe first mode in the array becomes the default mode. Returns\n{data: {id, modes: [{modeId, name}]}} — use those modeIds with\nset_variable_value and set_variable_mode.\n\nExamples:\n create_collection({name: \"Theme\", modes: [\"Light\", \"Dark\"]})\n create_collection({name: \"Device\", modes: [\"Desktop\", \"Tablet\", \"Mobile\"]})",
428
+ "parameters": {
429
+ "type": "object",
430
+ "properties": {
431
+ "name": {
432
+ "type": "string",
433
+ "description": "Collection name"
434
+ },
435
+ "modes": {
436
+ "type": "array",
437
+ "description": "Mode names (first becomes default)",
438
+ "items": {
439
+ "type": "string",
440
+ "description": "Mode name"
441
+ }
442
+ }
443
+ },
444
+ "required": [
445
+ "name",
446
+ "modes"
447
+ ]
448
+ },
449
+ "mutates": true
450
+ },
451
+ {
452
+ "name": "create_variable",
453
+ "description": "Create a variable in an existing collection.\n\nNo value is set here — use set_variable_value after. Returns {data: {id}}.\n\nExamples:\n create_variable({collection: \"VariableCollectionId:1:2\", name: \"Theme/bg\", type: \"COLOR\"})\n create_variable({collection: \"VariableCollectionId:1:2\", name: \"spacing/md\", type: \"FLOAT\"})",
454
+ "parameters": {
455
+ "type": "object",
456
+ "properties": {
457
+ "collection": {
458
+ "type": "string",
459
+ "description": "VariableCollectionId to create the variable in"
460
+ },
461
+ "name": {
462
+ "type": "string",
463
+ "description": "Variable name (slashes denote hierarchy in the Figma UI)"
464
+ },
465
+ "type": {
466
+ "type": "string",
467
+ "description": "Variable type",
468
+ "enum": [
469
+ "COLOR",
470
+ "FLOAT",
471
+ "BOOLEAN",
472
+ "STRING"
473
+ ]
474
+ }
475
+ },
476
+ "required": [
477
+ "collection",
478
+ "name",
479
+ "type"
480
+ ]
481
+ },
482
+ "mutates": true
483
+ },
484
+ {
485
+ "name": "ensure_collection",
486
+ "description": "Idempotent VariableCollection creation — safe to retry.\n\nReturns existing collection if one with the same name + identical mode list\nalready exists, otherwise creates a new one. Spec §3.1.\n\nPrefer this over create_collection — re-running with the same name + modes\nreturns the existing collection instead of creating a duplicate.\n\nOmit idempotency_key — the handler computes it canonically from (name, modes).\nPass it only if you need strict concurrency-safety validation (LLMs should\nnot try to compute SHA-256 inline; placeholder strings are rejected).\n\nReturns {data: {collection_id, modes: [{modeId, name}], reused?: true}}.\n\nExamples:\n ensure_collection({name: \"Theme\", modes: [\"Light\", \"Dark\"]})",
487
+ "parameters": {
488
+ "type": "object",
489
+ "properties": {
490
+ "name": {
491
+ "type": "string",
492
+ "description": "Collection name"
493
+ },
494
+ "modes": {
495
+ "type": "array",
496
+ "description": "Mode names (first becomes default)",
497
+ "items": {
498
+ "type": "string",
499
+ "description": "Mode name"
500
+ }
501
+ },
502
+ "idempotency_key": {
503
+ "type": "string",
504
+ "description": "Optional. Handler auto-computes canonically if omitted. Pass only if you need strict concurrency-safety validation against the SHA-256 formula in spec §3.1."
505
+ }
506
+ },
507
+ "required": [
508
+ "name",
509
+ "modes"
510
+ ]
511
+ },
512
+ "mutates": true
513
+ },
514
+ {
515
+ "name": "ensure_variable",
516
+ "description": "Idempotent variable creation — populates values_by_mode in one shot.\n\nPrefer this over create_variable — re-running with the same args returns the\nexisting variable instead of creating a duplicate.\n\nBehavior (spec §3.1):\n - Exactly 1 variable with (collection_id, name, type) in target collection → idempotent reuse.\n - 0 in target, matches in OTHER collections → create new in target + warning NAME_EXISTS_OUTSIDE_TARGET_COLLECTION.\n - 0 anywhere → create new.\n - 2+ in target collection (Figma allows duplicates) → fail SAME_COLLECTION_NAME_DUPLICATE.\n\nvalues_by_mode keys can be either mode NAMES (e.g. \"Light\") or modeIds (e.g. \"1:0\").\nEach value must match the variable type (hex/RGBA for COLOR, number for FLOAT,\nstring for STRING, boolean for BOOLEAN).\n\nOmit idempotency_key — the handler computes it canonically from\n(collection_id, name, type, values_by_mode). Pass it only if you need strict\nconcurrency-safety validation (LLMs should not try to compute SHA-256 inline;\nplaceholder strings are rejected).\n\nMode coverage policy (spec §6.2):\n - mode_coverage_required: 'all' (default) — every mode in the collection\n must have an explicit value. set_fill / bind_variable will REJECT\n bindings that fall through to a missing mode (MISSING_MODE_VALUES).\n - mode_coverage_required: 'opt-in-fallback' — fallback to default mode\n is intended. Bindings emit FALLBACK_BINDING warning instead of failing.\n Caller MUST provide fallback_reason containing the structured phrase\n \"fallback to <mode_name>\" (machine-greppable).\n\nReturns {data: {variable_id, name, type, collection_id, mode_coverage[],\nmode_coverage_required, reused?: true}, warnings?: [...]}.\n\nExamples:\n ensure_variable({collection_id: \"VariableCollectionId:1:2\", name: \"Text/Primary\", type: \"COLOR\", values_by_mode: {Light: \"#111\", Dark: \"#EEE\"}})\n ensure_variable({collection_id: \"VariableCollectionId:1:2\", name: \"Spacing/desktop\", type: \"FLOAT\", values_by_mode: {Desktop: 24}, mode_coverage_required: \"opt-in-fallback\", fallback_reason: \"Desktop-only metric; fallback to Desktop in Mobile mode.\"})",
517
+ "parameters": {
518
+ "type": "object",
519
+ "properties": {
520
+ "collection_id": {
521
+ "type": "string",
522
+ "description": "Target VariableCollectionId — strict ID, no name lookup."
523
+ },
524
+ "name": {
525
+ "type": "string",
526
+ "description": "Variable name (slashes denote hierarchy)."
527
+ },
528
+ "type": {
529
+ "type": "string",
530
+ "description": "Variable type",
531
+ "enum": [
532
+ "COLOR",
533
+ "FLOAT",
534
+ "STRING",
535
+ "BOOLEAN"
536
+ ]
537
+ },
538
+ "values_by_mode": {
539
+ "type": "object",
540
+ "description": "Map of mode name OR modeId → value. Hex strings allowed for COLOR."
541
+ },
542
+ "idempotency_key": {
543
+ "type": "string",
544
+ "description": "Optional. Handler auto-computes canonically if omitted. Pass only if you need strict concurrency-safety validation against the SHA-256 formula in spec §3.1."
545
+ },
546
+ "mode_coverage_required": {
547
+ "type": "string",
548
+ "description": "Mode coverage policy — \"all\" (default; every mode must have explicit value) or \"opt-in-fallback\" (allow fallback, requires fallback_reason).",
549
+ "enum": [
550
+ "all",
551
+ "opt-in-fallback"
552
+ ]
553
+ },
554
+ "fallback_reason": {
555
+ "type": "string",
556
+ "description": "REQUIRED iff mode_coverage_required=\"opt-in-fallback\". Must contain the structured phrase \"fallback to <mode_name>\". Persisted on the variable for audit trail."
557
+ }
558
+ },
559
+ "required": [
560
+ "collection_id",
561
+ "name",
562
+ "type",
563
+ "values_by_mode"
564
+ ]
565
+ },
566
+ "mutates": true
567
+ },
568
+ {
569
+ "name": "set_variable_value",
570
+ "description": "Set a variable's value for a specific mode.\n\nThin wrapper over Figma's variable.setValueForMode(modeId, value). Call once per\nmode. Value is a raw value (COLOR/FLOAT/STRING/BOOLEAN) OR an alias object\n{type: \"VARIABLE_ALIAS\", id: \"VariableID:x:y\"}. Hex strings are accepted for\nCOLOR and normalized to {r,g,b,a} in 0-1 range.\n\nExamples:\n set_variable_value({variable: \"VariableID:1:5\", mode: \"1:0\", value: \"#FFFFFF\"})\n set_variable_value({variable: \"VariableID:1:5\", mode: \"1:1\", value: {r: 0.1, g: 0.1, b: 0.1, a: 1}})\n set_variable_value({variable: \"VariableID:1:6\", mode: \"1:0\", value: 16})\n set_variable_value({variable: \"VariableID:1:7\", mode: \"1:0\", value: {type: \"VARIABLE_ALIAS\", id: \"VariableID:1:9\"}})",
571
+ "parameters": {
572
+ "type": "object",
573
+ "properties": {
574
+ "variable": {
575
+ "type": "string",
576
+ "description": "VariableID to set"
577
+ },
578
+ "mode": {
579
+ "type": "string",
580
+ "description": "Mode id from the variable's collection (e.g. \"1:0\")"
581
+ },
582
+ "value": {
583
+ "type": "object",
584
+ "description": "Raw value (COLOR/FLOAT/STRING/BOOLEAN) or {type: \"VARIABLE_ALIAS\", id}"
585
+ }
586
+ },
587
+ "required": [
588
+ "variable",
589
+ "mode",
590
+ "value"
591
+ ]
592
+ },
593
+ "mutates": true
594
+ },
595
+ {
596
+ "name": "bind_variable",
597
+ "description": "Bind a FLOAT, BOOLEAN, or STRING variable to a node property.\n\nprop is a flat Figma bindable field (e.g. fontSize, itemSpacing, paddingTop,\ncornerRadius, opacity, visible, width, height, characters). Shorthands:\ngap → itemSpacing, padding → paddingTop, corner → cornerRadius,\nfont-size → fontSize.\n\nCOLOR variables are NOT bound here — they live inside Paint objects. To apply\na color token, specify it at the source instead:\n • At creation: jsx <frame bg=\"$TokenName\" ...> or fill=\"$TokenName\"\n • Post-hoc: set_fill({node, bg: \"$TokenName\"}) or set_stroke\n\nWhen selecting which variable to bind: if the node is a Tablet or Mobile variant\n(name or variant property contains \"Tablet\"/\"Mobile\"), match the node's property\nvalue against the Tablet/Mobile mode column from list_variables — not Desktop.\n\nExamples:\n bind_variable({node: \"1:2\", prop: \"fontSize\", variable: \"VariableID:1:6\"})\n bind_variable({node: \"1:3\", prop: \"paddingTop\", variable: \"VariableID:1:7\"})\n bind_variable({node: \"1:4\", prop: \"visible\", variable: \"VariableID:1:8\"})\n bind_variable({node: \"1:5\", prop: \"characters\", variable: \"VariableID:1:9\"})",
598
+ "parameters": {
599
+ "type": "object",
600
+ "properties": {
601
+ "node": {
602
+ "type": "string",
603
+ "description": "Node ID (e.g. \"1:2\")"
604
+ },
605
+ "prop": {
606
+ "type": "string",
607
+ "description": "Flat Figma bindable field (fontSize, paddingTop, itemSpacing, visible, characters, etc.). COLOR props (fills/strokes) not supported — use set_fill/jsx."
608
+ },
609
+ "variable": {
610
+ "type": "string",
611
+ "description": "VariableID to bind (FLOAT/BOOLEAN/STRING only)"
612
+ }
613
+ },
614
+ "required": [
615
+ "node",
616
+ "prop",
617
+ "variable"
618
+ ]
619
+ },
620
+ "mutates": true
621
+ },
622
+ {
623
+ "name": "set_variable_mode",
624
+ "description": "Set a node to use a specific mode of a variable collection.\n\nThis controls which variable values the node displays. For example, set a frame\nto use \"Dark\" mode of the \"Theme\" collection so all bound variables show dark values.\n\nExamples:\n set_variable_mode({node: \"1:2\", collection: \"VariableCollectionId:1:2\", mode: \"1:1\"})\n set_variable_mode({node: \"1:5\", collection: \"VariableCollectionId:1:3\", mode: \"1:2\"})",
625
+ "parameters": {
626
+ "type": "object",
627
+ "properties": {
628
+ "node": {
629
+ "type": "string",
630
+ "description": "Node ID (e.g. \"1:2\")"
631
+ },
632
+ "collection": {
633
+ "type": "string",
634
+ "description": "VariableCollectionId"
635
+ },
636
+ "mode": {
637
+ "type": "string",
638
+ "description": "Mode id (e.g. \"1:1\")"
639
+ }
640
+ },
641
+ "required": [
642
+ "node",
643
+ "collection",
644
+ "mode"
645
+ ]
646
+ },
647
+ "mutates": true
648
+ },
649
+ {
650
+ "name": "create_component",
651
+ "description": "Convert a frame or group to a Figma component.\n\nExamples:\n create_component({node: \"1:2\"})",
652
+ "parameters": {
653
+ "type": "object",
654
+ "properties": {
655
+ "node": {
656
+ "type": "string",
657
+ "description": "Node ID (e.g. \"1:2\") to convert"
658
+ }
659
+ },
660
+ "required": [
661
+ "node"
662
+ ]
663
+ },
664
+ "mutates": true
665
+ },
666
+ {
667
+ "name": "combine_components",
668
+ "description": "Combine multiple components into a variant set (ComponentSet).\n\nExamples:\n combine_components({nodes: [\"1:2\", \"1:3\", \"1:4\"], name: \"Button\"})",
669
+ "parameters": {
670
+ "type": "object",
671
+ "properties": {
672
+ "nodes": {
673
+ "type": "array",
674
+ "description": "Component node IDs to combine",
675
+ "items": {
676
+ "type": "string",
677
+ "description": "Node ID"
678
+ }
679
+ },
680
+ "name": {
681
+ "type": "string",
682
+ "description": "Component set name"
683
+ }
684
+ },
685
+ "required": [
686
+ "nodes"
687
+ ]
688
+ },
689
+ "mutates": true
690
+ },
691
+ {
692
+ "name": "add_component_prop",
693
+ "description": "Add a component property and bind it to a child node.\n\nFor TEXT properties: binds to the target text node's characters, so instances can override the text content.\nFor BOOLEAN properties: binds to the target node's visibility.\n\nParameters:\n node: The component node ID (must be COMPONENT or COMPONENT_SET).\n name: Property display name.\n type: TEXT, BOOLEAN, or INSTANCE_SWAP.\n default: Default value.\n bind: Child node ID to bind this property to. For TEXT, binds to text content. For BOOLEAN, binds to visibility.\n\nExamples:\n add_component_prop({node: \"1:2\", name: \"Label\", type: \"TEXT\", default: \"Click me\", bind: \"1:5\"})\n add_component_prop({node: \"1:2\", name: \"Show Icon\", type: \"BOOLEAN\", default: \"true\", bind: \"1:6\"})",
694
+ "parameters": {
695
+ "type": "object",
696
+ "properties": {
697
+ "node": {
698
+ "type": "string",
699
+ "description": "Component node ID (e.g. \"1:2\")"
700
+ },
701
+ "name": {
702
+ "type": "string",
703
+ "description": "Property name"
704
+ },
705
+ "type": {
706
+ "type": "string",
707
+ "description": "Property type",
708
+ "enum": [
709
+ "TEXT",
710
+ "BOOLEAN",
711
+ "INSTANCE_SWAP"
712
+ ]
713
+ },
714
+ "default": {
715
+ "type": "string",
716
+ "description": "Default value"
717
+ },
718
+ "bind": {
719
+ "type": "string",
720
+ "description": "Child node ID to bind this property to"
721
+ }
722
+ },
723
+ "required": [
724
+ "node",
725
+ "name",
726
+ "type"
727
+ ]
728
+ },
729
+ "mutates": true
730
+ },
731
+ {
732
+ "name": "list_component_props",
733
+ "description": "List properties and variants of a component, component set, or instance.\n\nExamples:\n list_component_props({node: \"1:2\"})",
734
+ "parameters": {
735
+ "type": "object",
736
+ "properties": {
737
+ "node": {
738
+ "type": "string",
739
+ "description": "Component/instance node ID (e.g. \"1:2\")"
740
+ }
741
+ },
742
+ "required": [
743
+ "node"
744
+ ]
745
+ }
746
+ },
747
+ {
748
+ "name": "create_instance",
749
+ "description": "Create an instance of a component.\n\nExamples:\n create_instance({node: \"1:2\"})\n create_instance({node: \"1:2\", parent: \"1:4\"})",
750
+ "parameters": {
751
+ "type": "object",
752
+ "properties": {
753
+ "node": {
754
+ "type": "string",
755
+ "description": "Component node ID (e.g. \"1:2\") to instantiate"
756
+ },
757
+ "parent": {
758
+ "type": "string",
759
+ "description": "Parent node ID for placement"
760
+ }
761
+ },
762
+ "required": [
763
+ "node"
764
+ ]
765
+ },
766
+ "mutates": true
767
+ },
768
+ {
769
+ "name": "set_text",
770
+ "description": "Set text content on one or more nodes.\n\n set_text({node: \"1:2\", text: \"Hello World\"})\n set_text({nodes: [{node: \"1:2\", text: \"Title\"}, {node: \"1:3\", text: \"Subtitle\"}]})\n\nUse this when changing what text says. For text styling (font, size, weight), use edit.",
771
+ "parameters": {
772
+ "type": "object",
773
+ "properties": {
774
+ "node": {
775
+ "type": "string",
776
+ "description": "Node ID"
777
+ },
778
+ "text": {
779
+ "type": "string",
780
+ "description": "New text content"
781
+ },
782
+ "nodes": {
783
+ "type": "array",
784
+ "description": "Batch: [{node, text}]",
785
+ "items": {
786
+ "type": "object",
787
+ "description": "{node, text}"
788
+ }
789
+ }
790
+ }
791
+ },
792
+ "mutates": true
793
+ },
794
+ {
795
+ "name": "set_fill",
796
+ "description": "Set fill or background color on a node.\n\n set_fill({node: \"1:2\", bg: \"#F5F5F5\"})\n set_fill({node: \"1:2\", fill: \"#333333\"})\n set_fill({node: \"1:2\", bg: \"linear-gradient(135deg, #8B5CF6 0%, #F97316 100%)\"})\n\n // Batch — bulk paint update in one call:\n set_fill({nodes: [{node: \"1:2\", bg: \"#FFF\"}, {node: \"1:3\", bg: \"#F5F5F5\"}]})\n\nfill = text color or shape fill. bg = frame background.\nFor stroke color, use set_stroke.\n\nAccepted color formats (for fill or bg):\n hex \"#FFF\", \"#F5F5F5\"\n gradient string \"linear-gradient(angle, #color stop%, ...)\" or \"radial-gradient(...)\"\n variable token qualified bare name \"$Surface/Card\"\n transparent \"transparent\" (bg only)",
797
+ "parameters": {
798
+ "type": "object",
799
+ "properties": {
800
+ "node": {
801
+ "type": "string",
802
+ "description": "Node ID (single mode)"
803
+ },
804
+ "fill": {
805
+ "type": "string",
806
+ "description": "Text color or shape fill — hex, gradient string, or qualified bare-name token"
807
+ },
808
+ "bg": {
809
+ "type": "string",
810
+ "description": "Background — hex, gradient string, \"transparent\", or qualified bare-name token"
811
+ },
812
+ "nodes": {
813
+ "type": "array",
814
+ "description": "Batch: [{node, fill?, bg?}]",
815
+ "items": {
816
+ "type": "object",
817
+ "description": "{node, fill?, bg?}"
818
+ }
819
+ }
820
+ }
821
+ },
822
+ "mutates": true
823
+ },
824
+ {
825
+ "name": "set_stroke",
826
+ "description": "Set stroke (border) on a node.\n\n set_stroke({node: \"1:2\", stroke: \"1 #E0E0E0\"})\n set_stroke({node: \"1:2\", stroke: \"2 #333 inside\"})\n set_stroke({node: \"1:2\", color: \"#E0E0E0\", weight: 1, align: \"inside\"})\n set_stroke({node: \"1:2\", color: \"linear-gradient(90deg, #8B5CF6 0%, #F97316 100%)\", weight: 1.5, align: \"inside\"})\n\n // Batch — bulk stroke update in one call:\n set_stroke({nodes: [{node: \"1:2\", color: \"#E0E0E0\", weight: 1}, {node: \"1:3\", color: \"#333\", weight: 2}]})\n\nShorthand: \"weight color align\" (e.g. \"1 #E0E0E0 inside\"). Hex only in shorthand.\n\nAccepted color formats (for the explicit `color` field, not the shorthand):\n hex \"#E0E0E0\"\n gradient string \"linear-gradient(angle, #color stop%, ...)\" or \"radial-gradient(...)\"\n variable token qualified bare name \"$Border/Default\"\n\nTo bind a variable to the stroke color, use the explicit `color` field — the shorthand parser silently drops bare-name tokens.",
827
+ "parameters": {
828
+ "type": "object",
829
+ "properties": {
830
+ "node": {
831
+ "type": "string",
832
+ "description": "Node ID (single mode)"
833
+ },
834
+ "stroke": {
835
+ "type": "string",
836
+ "description": "Shorthand: \"1 #E0E0E0 inside\" — single-string form. Hex only; for gradient/variable use the explicit color field."
837
+ },
838
+ "color": {
839
+ "type": "string",
840
+ "description": "Stroke color — hex, gradient string, or qualified bare-name token"
841
+ },
842
+ "weight": {
843
+ "type": "number",
844
+ "description": "Stroke weight in px"
845
+ },
846
+ "align": {
847
+ "type": "string",
848
+ "enum": [
849
+ "inside",
850
+ "outside",
851
+ "center"
852
+ ],
853
+ "description": "Stroke alignment relative to the frame edge"
854
+ },
855
+ "nodes": {
856
+ "type": "array",
857
+ "description": "Batch: [{node, color?, weight?, align?, stroke?}]",
858
+ "items": {
859
+ "type": "object",
860
+ "description": "{node, color?, weight?, align?, stroke?}"
861
+ }
862
+ }
863
+ }
864
+ },
865
+ "mutates": true
866
+ },
867
+ {
868
+ "name": "set_layout",
869
+ "description": "Set auto-layout properties on a container.\n\n set_layout({node: \"1:2\", gap: 16, p: 24})\n set_layout({node: \"1:2\", layout: \"row\", justify: \"space-between\"})\n set_layout({node: \"1:2\", layout: \"column\", gap: 8, p: \"16 24\", align: \"center\"})\n set_layout({node: \"1:2\", layout: \"grid\", cols: 3, rows: 2, gap: 16})\n\n // Batch — bulk update in one call:\n set_layout({nodes: [{node: \"1:2\", gap: 16, p: 24}, {node: \"1:3\", gap: 8, p: 12}]})\n\nControls spacing, padding, direction, and alignment of a container's children.\nGrid: use layout:\"grid\" with cols/rows + gap (or rowGap/colGap for asymmetric).\nChildren fill the grid in insertion order.",
870
+ "parameters": {
871
+ "type": "object",
872
+ "properties": {
873
+ "node": {
874
+ "type": "string",
875
+ "description": "Node ID (single mode)"
876
+ },
877
+ "layout": {
878
+ "type": "string",
879
+ "enum": [
880
+ "row",
881
+ "column",
882
+ "grid"
883
+ ],
884
+ "description": "Auto-layout mode"
885
+ },
886
+ "gap": {
887
+ "type": "number",
888
+ "description": "Spacing between children (px). On grid sets both row+column gap."
889
+ },
890
+ "rowGap": {
891
+ "type": "number",
892
+ "description": "Grid row gap (px, grid only)"
893
+ },
894
+ "colGap": {
895
+ "type": "number",
896
+ "description": "Grid column gap (px, grid only)"
897
+ },
898
+ "cols": {
899
+ "type": "number",
900
+ "description": "Grid column count (required when layout=\"grid\")"
901
+ },
902
+ "rows": {
903
+ "type": "number",
904
+ "description": "Grid row count (required when layout=\"grid\")"
905
+ },
906
+ "p": {
907
+ "type": "number",
908
+ "description": "Padding — number, \"v h\", or \"t r b l\""
909
+ },
910
+ "justify": {
911
+ "type": "string",
912
+ "enum": [
913
+ "center",
914
+ "space-between",
915
+ "start",
916
+ "end"
917
+ ],
918
+ "description": "Main axis (flex only)"
919
+ },
920
+ "align": {
921
+ "type": "string",
922
+ "enum": [
923
+ "center",
924
+ "start",
925
+ "end",
926
+ "baseline"
927
+ ],
928
+ "description": "Cross axis (flex only)"
929
+ },
930
+ "wrap": {
931
+ "type": "string",
932
+ "enum": [
933
+ "wrap",
934
+ "nowrap"
935
+ ],
936
+ "description": "Wrap behaviour (flex only)"
937
+ },
938
+ "nodes": {
939
+ "type": "array",
940
+ "description": "Batch: [{node, layout?, gap?, p?, ...}]",
941
+ "items": {
942
+ "type": "object",
943
+ "description": "{node, layout?, gap?, p?, ...}"
944
+ }
945
+ }
946
+ }
947
+ },
948
+ "mutates": true
949
+ },
950
+ {
951
+ "name": "get_selection",
952
+ "description": "Get the user's currently selected nodes in Figma.\n\nReturns node names, types, and IDs of selected elements.\nCall this when the user's intent involves modifying existing elements:\n- \"change this button\", \"update the card\", \"fix the spacing\"\n- References to \"this\", \"the selected\", \"it\"\n\nSkip for fresh design requests (\"design a login page\", \"create a dashboard\") — a new canvas has no selection to read, so the call returns nothing and burns an iteration.\n\nExamples:\n get_selection()",
953
+ "parameters": {
954
+ "type": "object",
955
+ "properties": {}
956
+ }
957
+ },
958
+ {
959
+ "name": "switch_page",
960
+ "description": "Navigate between pages in the Figma file. ID-driven — names are not addressable (they can collide and change).\n\nTwo modes:\n- switch_page({}) → return the page roster only, no switch (use to discover IDs on first call)\n- switch_page({pageId: \"1:23\"}) → switch and return the updated state + roster\n\nPages are top-level containers under the file root. Most read/write operations default to figma.currentPage. Call this when you need to operate on nodes that live on a different page than the current one.\n\nReturns:\n- currentPageId, currentPageName — the now-current page (always present)\n- pages — full roster [{id, name}] of every page in the file (always present)\n- previousPageId, previousPageName — what you switched from (only when an actual switch happened)\n- unchanged — true if target was already current\n\nTypical flow:\n1. switch_page({}) // get IDs\n2. switch_page({pageId: \"<picked id>\"}) // switch\n\nWhen to call:\n- User mentions content on a different page than the current one\n- A previous tool reported a node ID is on a non-current page\n- You need to inspect/modify nodes outside the active page\n\nDon't call:\n- For nodes already on the current page — figma.currentPage is the default scope, this would just waste an iteration\n- Repeatedly to \"explore\" — every call returns the full pages roster, cache it",
961
+ "parameters": {
962
+ "type": "object",
963
+ "properties": {
964
+ "pageId": {
965
+ "type": "string",
966
+ "description": "Target page ID (e.g., \"0:1\"). Omit to just fetch the page roster without switching."
967
+ }
968
+ }
969
+ },
970
+ "mutates": false
971
+ },
972
+ {
973
+ "name": "read_plugin_data",
974
+ "description": "Read plugin data (private or shared) from a Figma node.\n\nUse for: i18n metadata, design-system tags, custom plugin annotations, anything stored via setPluginData / setSharedPluginData.\n\nIf `namespace` is omitted, reads private pluginData (`node.getPluginData(key)`).\nIf `namespace` is provided, reads sharedPluginData (`node.getSharedPluginData(namespace, key)`).\n\nReturns `{value: \"\"}` (empty string) when the key does not exist — Figma's API never throws here.\n\nExamples:\n read_plugin_data({node_id: \"1:5\", key: \"ref\"})\n read_plugin_data({node_id: \"1:5\", namespace: \"i18n\", key: \"ref\"})",
975
+ "parameters": {
976
+ "type": "object",
977
+ "properties": {
978
+ "node_id": {
979
+ "type": "string",
980
+ "description": "Figma node id (resolve via find_nodes / get_selection first)."
981
+ },
982
+ "namespace": {
983
+ "type": "string",
984
+ "description": "Optional sharedPluginData namespace. Omit for private pluginData."
985
+ },
986
+ "key": {
987
+ "type": "string",
988
+ "description": "Key to read."
989
+ }
990
+ },
991
+ "required": [
992
+ "node_id",
993
+ "key"
994
+ ]
995
+ },
996
+ "mutates": false
997
+ },
998
+ {
999
+ "name": "write_plugin_data",
1000
+ "description": "Write plugin data (private or shared) to a Figma node.\n\nIf `namespace` is omitted, writes private pluginData (`node.setPluginData(key, value)`).\nIf `namespace` is provided, writes sharedPluginData (`node.setSharedPluginData(namespace, key, value)`).\n\nPass an empty string as `value` to delete a key.\n\nExamples:\n write_plugin_data({node_id: \"1:5\", key: \"ref\", value: \"home_title\"})\n write_plugin_data({node_id: \"1:5\", namespace: \"i18n\", key: \"ref\", value: \"home.welcome_title\"})",
1001
+ "parameters": {
1002
+ "type": "object",
1003
+ "properties": {
1004
+ "node_id": {
1005
+ "type": "string",
1006
+ "description": "Figma node id."
1007
+ },
1008
+ "namespace": {
1009
+ "type": "string",
1010
+ "description": "Optional sharedPluginData namespace."
1011
+ },
1012
+ "key": {
1013
+ "type": "string",
1014
+ "description": "Key to write."
1015
+ },
1016
+ "value": {
1017
+ "type": "string",
1018
+ "description": "Value (string). Pass \"\" to delete."
1019
+ }
1020
+ },
1021
+ "required": [
1022
+ "node_id",
1023
+ "key",
1024
+ "value"
1025
+ ]
1026
+ },
1027
+ "mutates": true
1028
+ },
1029
+ {
1030
+ "name": "create_vector",
1031
+ "description": "Create a vector node from SVG path data or a list of points. Use for chart lines, custom icon paths, freeform curves, or any shape that needs path data.\n\nExamples:\n // Polyline (chart trend line)\n create_vector({\n parent: \"1:23\", name: \"TrendLine\",\n x: 40, y: 20, width: 550, height: 240,\n points: [[0,144],[90,96],[180,120],[270,64],[360,80],[450,40],[540,72]],\n stroke: \"#6366F1\", strokeWeight: 2\n })\n\n // Raw SVG path (custom shape)\n create_vector({\n parent: \"1:23\", name: \"Wave\",\n width: 200, height: 60,\n data: \"M 0 30 Q 50 0 100 30 T 200 30\",\n stroke: \"linear-gradient(90deg, #8B5CF6 0%, #F97316 100%)\",\n strokeWeight: 1.5\n })\n\nPath input — provide ONE of:\n points: [[x,y], ...] compiled to \"M x0 y0 L x1 y1 ...\" (polyline shortcut)\n data: \"M ... L ...\" raw SVG path (LLM-native; supports M, L, C, Q, A, Z)\n\nStroke / fill (same formats as set_stroke / set_fill):\n hex \"#6366F1\"\n gradient \"linear-gradient(angle, #color stop%, ...)\"\n variable qualified bare name \"$Brand/Primary\"\n\nDefault fill is \"transparent\" so the vector shows only its stroke. Pass an explicit fill if you want it filled.\n\nWhen NOT to use:\n - Standard rectangles / ellipses / lines — use jsx <Rect/>, <Ellipse/>, <Line/> elements (simpler, batch-friendly)\n - Existing vector edits — use edit / set_stroke instead",
1032
+ "parameters": {
1033
+ "type": "object",
1034
+ "properties": {
1035
+ "parent": {
1036
+ "type": "string",
1037
+ "description": "Parent node ID. Omit to attach to the current page."
1038
+ },
1039
+ "name": {
1040
+ "type": "string",
1041
+ "description": "Node name (default: \"Vector\")."
1042
+ },
1043
+ "x": {
1044
+ "type": "number",
1045
+ "description": "X position relative to parent (default: 0)."
1046
+ },
1047
+ "y": {
1048
+ "type": "number",
1049
+ "description": "Y position relative to parent (default: 0)."
1050
+ },
1051
+ "width": {
1052
+ "type": "number",
1053
+ "description": "Vector bounds width in px."
1054
+ },
1055
+ "height": {
1056
+ "type": "number",
1057
+ "description": "Vector bounds height in px."
1058
+ },
1059
+ "data": {
1060
+ "type": "string",
1061
+ "description": "Raw SVG path string. Mutually exclusive with `points`."
1062
+ },
1063
+ "points": {
1064
+ "type": "array",
1065
+ "description": "Polyline points as [[x,y], ...]. Compiled internally to \"M x0 y0 L x1 y1 ...\". Mutually exclusive with `data`.",
1066
+ "items": {
1067
+ "type": "array",
1068
+ "description": "[x, y] pair",
1069
+ "items": {
1070
+ "type": "number",
1071
+ "description": "coordinate"
1072
+ }
1073
+ }
1074
+ },
1075
+ "windingRule": {
1076
+ "type": "string",
1077
+ "enum": [
1078
+ "NONZERO",
1079
+ "EVENODD"
1080
+ ],
1081
+ "description": "Path fill winding rule (default: NONZERO)."
1082
+ },
1083
+ "stroke": {
1084
+ "type": "string",
1085
+ "description": "Stroke color — hex, gradient string, or qualified bare-name token."
1086
+ },
1087
+ "strokeWeight": {
1088
+ "type": "number",
1089
+ "description": "Stroke weight in px (default: 1)."
1090
+ },
1091
+ "strokeAlign": {
1092
+ "type": "string",
1093
+ "enum": [
1094
+ "inside",
1095
+ "outside",
1096
+ "center"
1097
+ ],
1098
+ "description": "Stroke alignment relative to the path (default: center)."
1099
+ },
1100
+ "fill": {
1101
+ "type": "string",
1102
+ "description": "Fill — hex / gradient / variable / \"transparent\" (default: \"transparent\")."
1103
+ }
1104
+ },
1105
+ "required": [
1106
+ "width",
1107
+ "height"
1108
+ ]
1109
+ },
1110
+ "mutates": true
1111
+ },
1112
+ {
1113
+ "name": "get_screenshot",
1114
+ "description": "Capture a PNG screenshot of a node.\n\nUse after style changes to visually verify the result instead of reading properties back.\nReturns base64 PNG data embedded in the response.\n\nParameters:\n node: Node ID from jsx/inspect results (e.g. \"100:5\"). Page root (\"/\") is not supported.\n scale: Export scale 0.5–2 (default 1). Higher = larger file.\n padding: Reserved for future use — currently ignored.\n\nExamples:\n get_screenshot({node: \"100:5\"}) → PNG at 1x\n get_screenshot({node: \"100:5\", scale: 2}) → PNG at 2x (sharper)",
1115
+ "parameters": {
1116
+ "type": "object",
1117
+ "properties": {
1118
+ "node": {
1119
+ "type": "string",
1120
+ "description": "Node ID (e.g. \"100:5\"). Page root \"/\" is not supported."
1121
+ },
1122
+ "scale": {
1123
+ "type": "number",
1124
+ "description": "Export scale 0.5–2 (default 1)."
1125
+ },
1126
+ "padding": {
1127
+ "type": "number",
1128
+ "description": "Reserved for future use."
1129
+ }
1130
+ },
1131
+ "required": [
1132
+ "node"
1133
+ ]
1134
+ },
1135
+ "mutates": false
1136
+ },
1137
+ {
1138
+ "name": "ask_user",
1139
+ "description": "Ask the user 1-3 questions in a single form. Each question has its own options and can be single- or multi-select. Bundle related decisions in ONE call instead of multiple turns.\n\nUse when:\n- The prompt is ambiguous on multiple dimensions (audience + aesthetic + length) — bundle them into one form\n- You need a decision before proceeding (delete existing? which section first?)\n- Multiple valid approaches exist and user preference matters\n\nReturns one of:\n- { answers: [...] } — array indexed to questions order. string for single-select, string[] for multi-select. The string MAY be one of the option labels OR custom text the user typed via the auto-injected \"Other...\" option.\n- { freeText: \"...\" } — when the user typed a free-form answer in the chat input instead of submitting the form. Treat as authoritative — user is overriding the structured options.\n\nEach question:\n- question: required prompt string. Self-contained — no separate header/label, the question text IS the heading.\n- options: 2-3 options, each { label, description? }. The form auto-injects an \"Other...\" row, so the user always sees options.length + 1 rows total — keep options ≤ 3 to stay within the 4-row visual cap.\n- multiSelect: optional boolean (default false). Use only when the answer is genuinely a list (e.g. \"which features?\"). For mutually exclusive choices keep false.\n\nConventions:\n- **First option = recommended.** If you have a strong default for the user, put it FIRST and add \"(Recommended)\" at the end of the label. The form auto-focuses the first option and the dev/auto-pick fallback selects it — both work better with a deliberate recommendation.\n- **Do NOT include an \"Other\" option yourself** — the form auto-injects an \"Other...\" row per question with an inline text input. Don't add a redundant one.\n- **Bundle aggressively.** 2 related dimensions in ONE call beats 2 sequential turns.\n\nExample:\n ask_user({ questions: [\n { question: \"Who is this for?\", options: [{label:\"B2B SaaS\"},{label:\"Consumer\"},{label:\"Developer tool\"}] },\n { question: \"What visual direction?\", options: [{label:\"Minimal\"},{label:\"Bold/Brutalist\"},{label:\"Neon/Cyber\"}] }\n ]})\n\nSkip when the prompt is already actionable — asking adds a turn and costs momentum.",
1140
+ "parameters": {
1141
+ "type": "object",
1142
+ "properties": {
1143
+ "questions": {
1144
+ "type": "array",
1145
+ "description": "1-3 questions to present in a single form (3 is the soft cap; bundle related decisions but don't pad).",
1146
+ "maxItems": 3,
1147
+ "items": {
1148
+ "type": "object",
1149
+ "description": "A single question with its own options and select mode",
1150
+ "properties": {
1151
+ "question": {
1152
+ "type": "string",
1153
+ "description": "The question prompt"
1154
+ },
1155
+ "options": {
1156
+ "type": "array",
1157
+ "description": "2-3 options for this question. The form auto-injects an \"Other...\" row, so user-visible total is options.length + 1 (cap at 4).",
1158
+ "maxItems": 3,
1159
+ "items": {
1160
+ "type": "object",
1161
+ "description": "A single option (label + optional description)",
1162
+ "properties": {
1163
+ "label": {
1164
+ "type": "string",
1165
+ "description": "Short option label, ~6-20 chars. The label IS the heading — no need for a separate header."
1166
+ },
1167
+ "description": {
1168
+ "type": "string",
1169
+ "description": "Brief one-line explanation, ~50-80 chars max. Anything longer is clamped to 2 lines with ellipsis in the UI."
1170
+ }
1171
+ },
1172
+ "required": [
1173
+ "label"
1174
+ ]
1175
+ }
1176
+ },
1177
+ "multiSelect": {
1178
+ "type": "boolean",
1179
+ "description": "When true, allow multiple options to be selected. Default false (single-select)."
1180
+ }
1181
+ },
1182
+ "required": [
1183
+ "question",
1184
+ "options"
1185
+ ]
1186
+ }
1187
+ }
1188
+ },
1189
+ "required": [
1190
+ "questions"
1191
+ ]
1192
+ },
1193
+ "mutates": false
1194
+ },
1195
+ {
1196
+ "name": "subtask",
1197
+ "description": "Delegate a focused sub-task to a typed child agent. Each type has its own tools, iteration budget, and behavioral constraints.\n\nAvailable agent types:\n- create: Build an independent UI section (header, sidebar, form, card). Default.\n- audit: Read-only design review — find layout issues, property omissions, report PASS/FAIL.\n- token: Variable system operations — create collections, bind tokens, set up aliases.\n\nUse when the prompt names 3+ distinct regions (e.g. header, sidebar, main) that share no nodes, or when specialized behavior is needed (audit, token ops). For 1-2 tool-call operations, inline calls finish faster than the subtask spin-up cost.",
1198
+ "parameters": {
1199
+ "type": "object",
1200
+ "properties": {
1201
+ "prompt": {
1202
+ "type": "string",
1203
+ "description": "Description of the sub-task to delegate. Be specific about what to create/modify/audit."
1204
+ },
1205
+ "type": {
1206
+ "type": "string",
1207
+ "enum": [
1208
+ "create",
1209
+ "audit",
1210
+ "token"
1211
+ ],
1212
+ "description": "Agent type. Defaults to \"create\" if omitted."
1213
+ }
1214
+ },
1215
+ "required": [
1216
+ "prompt"
1217
+ ]
1218
+ }
1219
+ }
1220
+ ]