@ufira/vibma 0.3.2 → 1.0.0-rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/mcp.cjs CHANGED
@@ -25,13 +25,18 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
25
25
  // src/mcp.ts
26
26
  var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
27
27
  var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
28
- var import_zod18 = require("zod");
29
28
  var import_ws = __toESM(require("ws"), 1);
30
29
  var import_uuid = require("uuid");
31
30
  var import_fs = require("fs");
32
31
  var import_path = require("path");
33
32
  var import_url = require("url");
34
33
 
34
+ // src/tools/mcp-registry.ts
35
+ var import_zod5 = require("zod");
36
+
37
+ // src/tools/registry.ts
38
+ var import_zod = require("zod");
39
+
35
40
  // src/tools/types.ts
36
41
  var MAX_RESPONSE_CHARS = 5e4;
37
42
  function mcpJson(data) {
@@ -55,81 +60,972 @@ function mcpError(prefix, error) {
55
60
  return { content: [{ type: "text", text: `${prefix}: ${msg}` }] };
56
61
  }
57
62
 
63
+ // src/tools/generated/help.ts
64
+ var helpDirectory = `# Available Endpoints
65
+
66
+ [design-system]
67
+ components Create and manage reusable components and variant sets.
68
+ fonts Search available fonts in Figma.
69
+ instances Create and manage component instances.
70
+ styles CRUD for local paint, text, effect, and grid styles.
71
+ variable_collections CRUD for variable collections \u2014 the document-level API for design tokens.
72
+ variables Search and update design variables within a collection.
73
+
74
+ [connection]
75
+ connection Manage the Figma plugin connection.
76
+
77
+ [document]
78
+ document Navigate and manage Figma pages (canvases) in the document.
79
+ version_history Save named versions to the Figma file's version history.
80
+
81
+ [creation]
82
+ frames Create and manage frames, shapes, auto-layout containers, sections, and SVG nodes.
83
+ text Create and manage text nodes.
84
+
85
+ [quality]
86
+ lint Run design quality and accessibility checks.
87
+
88
+ [interaction]
89
+ prototyping Manage prototype interactions, reactions, and navigation flows.
90
+
91
+ [node-inspection]
92
+ selection Read and set the current Figma selection.
93
+
94
+ Topics:
95
+ missing_tools Why create or edit tools are missing and how to fix it
96
+
97
+ Use help(topic: "<endpoint>") for endpoint details.
98
+ Use help(topic: "<endpoint>.<method>") for method details.`;
99
+ var helpEndpoints = {
100
+ "components": {
101
+ "summary": '# components\nCreate and manage reusable components and variant sets.\n\nMethods:\n clone Duplicate nodes [create]\n audit Run lint on a node \u2014 returns severity-ranked findings [read]\n reparent Move nodes into a new parent [edit]\n list List local component names (variant sets as single entries) [read]\n get Get component detail \u2014 property definitions + ID for instances.create [read]\n create Create components [create]\n update Add, edit, or delete component properties [edit]\n delete Delete components or component sets [edit]\n\n// depth: omit \u2192 id+name stubs | 0 \u2192 props + child stubs | N \u2192 recurse N | -1 \u2192 full tree\n// fields: whitelist e.g. ["fills","opacity"] \u2014 id, name, type always included. Pass ["*"] for all.\n// layoutSizingHorizontal/Vertical: FIXED | HUG | FILL \u2014 how the node sizes within auto-layout.\n// Colors: fillVariableName/strokeVariableName bind by name \u2014 preferred over raw color values.\n// Note: node-based endpoints (frames, text, instances, components) use `results` as the list key.\n// Standalone endpoints (styles, variables, variable_collections) use `items`. Components.list uses `items` (catalog view).\n// ---\n// visible: false hides the node. Omitted from response when true (the default).\n// locked: true prevents editing in Figma UI. Omitted when false (the default).\n// rotation: degrees (0-360). Omitted when 0.\n// blendMode: layer blend mode. Omitted when PASS_THROUGH (the default).\n// layoutPositioning: ABSOLUTE = floating inside auto-layout parent. Omitted when AUTO (the default).\n// minWidth/maxWidth/minHeight/maxHeight: responsive constraints for auto-layout children.\n// constraints: position behavior in non-auto-layout parents. Ignored inside auto-layout frames.\n// bindings: bind design variables to node properties. field uses slash path: "fills/0/color" (first fill), "strokes/0/color", "opacity", "width", "height", "cornerRadius", "paddingLeft", "itemSpacing".\n// explicitMode: pin a variable mode on this node. Use { collectionName, modeName } (preferred) or { collectionId, modeId }.\n// properties: escape hatch \u2014 set any Figma node property directly. Use only when no dedicated field exists.\ninterface Node {\n id: string; name: string; type: string;\n visible?: boolean; // omitted when true\n locked?: boolean; // omitted when false\n opacity?: number; // omitted when 1\n rotation?: number; // omitted when 0\n blendMode?: string; // omitted when PASS_THROUGH\n layoutPositioning?: "AUTO" | "ABSOLUTE";\n layoutSizingHorizontal?: "FIXED" | "HUG" | "FILL";\n layoutSizingVertical?: "FIXED" | "HUG" | "FILL";\n minWidth?: number; maxWidth?: number; minHeight?: number; maxHeight?: number;\n absoluteBoundingBox: { x: number; y: number; width: number; height: number };\n fills?: Paint[]; // solid: {type: "SOLID", color: {r, g, b, a}}\n strokes?: Paint[];\n effects?: Effect[]; // DROP_SHADOW | INNER_SHADOW | LAYER_BLUR | BACKGROUND_BLUR\n children?: NodeStub[]; // stubs: {id, name, type} \u2014 use depth to expand\n}\n// PatchItem uses flat params matching create shape \u2014 no nested sub-objects.\n// Fill/stroke/corner/layout/text params are identical to frames.create and text.create.\n// Unknown keys produce a warning, preventing silent failures.\n// Components are reusable design elements. Instances stay linked to the component and inherit changes.\n// Workflow: create component (with properties) \u2192 add children \u2192 create instances via instances.create.\n// ---\n// A component set (variant set) groups related components as variants (e.g. Button/Primary, Button/Secondary).\n// Property types: BOOLEAN (toggle visibility), TEXT (editable text), INSTANCE_SWAP (swap child instance), VARIANT (variant picker).\n// exposeText: when creating from_node, text children become editable TEXT properties on the component.\n// Auto-bind: TEXT properties auto-bind to text children with matching names (case-insensitive).\n// Example: properties:[{propertyName:"Label", type:"TEXT", defaultValue:"Click"}] binds to a child text node named "Label".\n// text.create accepts componentPropertyName to bind on creation \u2014 walks up ancestors to find the nearest component.\n// For nested text (text inside frames inside a component), componentPropertyName resolves via ancestor walk. Alternatively, pass componentId explicitly.\n// For existing nodes with many text children, prefer components(method:"create", type:"from_node", exposeText:true) \u2014 it auto-discovers, creates TEXT properties, and binds all text nodes in one operation.\n// Property keys: read returns clean names ("Label"), write requires the #suffix ("Label#1:0"). Call components.get(id) to discover exact keys before edit/delete.\n// ComponentItem accepts the same params as FrameItem (layout, fill, stroke, sizing, min/max).\n// A component IS a frame \u2014 create it directly with all layout properties, then add children.\n// SIZING: Components with text need a width constraint \u2014 set width + layoutSizingHorizontal:"FIXED".\n// Without it, text won\'t wrap and the component grows unboundedly.\n// HUG on both axes is only correct for intrinsically-sized elements (buttons, badges, icons).\n// action: "add" (default) \u2014 requires type + defaultValue. "edit" \u2014 pass defaultValue to change value, name to rename property. "delete" \u2014 just propertyName#suffix.\n// action: "rename_variant" \u2014 renames a variant option VALUE (not the property). Pass defaultValue=current option name, name=new option name.\n// For VARIANT properties: "edit" with defaultValue reorders children to set the default variant.\n\nUse components(method: "help", topic: "<method>") for method details.',
102
+ "methods": {
103
+ "clone": "# components.clone\nDuplicate nodes\n\nParams:\n id (string, required) \u2014 Node ID\n parentId (string, optional) \u2014 Parent node ID. Omit to place on current page.\n x (number, optional) \u2014 X position (default: 0)\n y (number, optional) \u2014 Y position (default: 0)\n depth (number, optional) \u2014 Response detail: omit for id+name only. 0=properties + child stubs. N=recurse N levels. -1=unlimited.",
104
+ "audit": '# components.audit\nRun lint on a node \u2014 returns severity-ranked findings\n\nParams:\n id (string, required) \u2014 Node ID to audit\n rules (string[], optional) \u2014 Rules to check. Default: ["all"]. Categories: "component", "composition", "token", "naming", "wcag".\n maxDepth (number, optional) \u2014 Max tree depth (default: 10)\n maxFindings (number, optional) \u2014 Max findings (default: 50)',
105
+ "reparent": "# components.reparent\nMove nodes into a new parent\n\nParams:\n items (array, required) \u2014 Array of {id, parentId, index?}\n id (string, required)\n parentId (string, required)\n index (number, optional)",
106
+ "list": "# components.list\nList local component names (variant sets as single entries)\n\nParams:\n query (string, optional) \u2014 Name search query (case-insensitive substring match)\n offset (number, optional) \u2014 Skip N items for pagination (default 0)\n limit (number, optional) \u2014 Max items per page (default 100)",
107
+ "get": "# components.get\nGet component detail \u2014 property definitions + ID for instances.create\n\nParams:\n id (string, optional) \u2014 Component or component set ID\n names (string[], optional) \u2014 Batch lookup by name (case-insensitive). Either id or names is required.",
108
+ "create": `# components.create
109
+ Create components
110
+
111
+ Example: components(method:"create", type:"component", items:[{name:"Card", layoutMode:"VERTICAL", padding:"16", itemSpacing:"8", fillVariableName:"bg/surface", children:[{type:"text", text:"Title", componentPropertyName:"Title"}, {type:"text", text:"Description", fontSize:14, componentPropertyName:"Description"}], properties:[{propertyName:"Show Icon", type:"BOOLEAN", defaultValue:true}]}])
112
+
113
+ Discriminant: type (component | from_node | variant_set)
114
+
115
+ ## component \u2014 Create component with full frame properties (layout, fill, stroke, sizing). A component IS a frame \u2014 build directly, no need to create a frame first.
116
+ name (string, required) \u2014 Component name
117
+ parentId (string, optional) \u2014 Parent node ID. Omit to place on current page.
118
+ x (number, optional) \u2014 X position (default: 0)
119
+ y (number, optional) \u2014 Y position (default: 0)
120
+ width (number, optional) \u2014 Width (default: 100)
121
+ height (number, optional) \u2014 Height (default: 100)
122
+ rotation (number, optional) \u2014 Rotation in degrees (0-360)
123
+ opacity (string, optional) \u2014 Opacity (0-1) or variable name
124
+ visible (boolean, optional) \u2014 Show/hide (default true)
125
+ locked (boolean, optional) \u2014 Lock/unlock (default false)
126
+ blendMode (PASS_THROUGH | NORMAL | DARKEN | MULTIPLY | LINEAR_BURN | COLOR_BURN | LIGHTEN | SCREEN | LINEAR_DODGE | COLOR_DODGE | OVERLAY | SOFT_LIGHT | HARD_LIGHT | DIFFERENCE | EXCLUSION | HUE | SATURATION | COLOR | LUMINOSITY, optional)
127
+ layoutPositioning (AUTO | ABSOLUTE, optional) \u2014 ABSOLUTE = floating inside auto-layout parent
128
+ fills (array, optional) \u2014 Fill paints array \u2014 e.g. [{type: 'SOLID', color: '#hex'}] or [] for transparent. Primary way to set fills.
129
+ fillColor (Color, optional) \u2014 Shorthand \u2014 sets a single solid fill (auto-binds to matching variable/style)
130
+ fillStyleName (string, optional) \u2014 Paint style name for fill
131
+ fillVariableName (string, optional) \u2014 Color variable by name e.g. 'bg/primary'
132
+ strokes (array, optional) \u2014 Stroke paints array \u2014 e.g. [{type: 'SOLID', color: '#hex'}] or [] to clear. Primary way to set strokes.
133
+ strokeColor (Color, optional) \u2014 Shorthand \u2014 sets a single solid stroke (auto-binds to matching variable/style)
134
+ strokeStyleName (string, optional) \u2014 Paint style name for stroke
135
+ strokeVariableName (string, optional) \u2014 Color variable by name for stroke
136
+ strokeWeight (string, optional) \u2014 All sides (number) or variable name (string). Per-side: strokeTopWeight, strokeBottomWeight, strokeLeftWeight, strokeRightWeight.
137
+ strokeTopWeight (string, optional)
138
+ strokeBottomWeight (string, optional)
139
+ strokeLeftWeight (string, optional)
140
+ strokeRightWeight (string, optional)
141
+ strokeAlign (INSIDE | OUTSIDE | CENTER, optional) \u2014 Stroke position (default: INSIDE)
142
+ strokesIncludedInLayout (boolean, optional) \u2014 Include stroke width in layout measurements (default: false)
143
+ cornerRadius (string, optional) \u2014 All corners (number) or variable name (string). Per-corner: topLeftRadius, topRightRadius, bottomRightRadius, bottomLeftRadius.
144
+ topLeftRadius (string, optional)
145
+ topRightRadius (string, optional)
146
+ bottomRightRadius (string, optional)
147
+ bottomLeftRadius (string, optional)
148
+ effectStyleName (string, optional) \u2014 Effect style name (e.g. 'Shadow/Card') for shadows, blurs
149
+ layoutMode (NONE | HORIZONTAL | VERTICAL, optional) \u2014 Layout direction (default: NONE)
150
+ layoutWrap (NO_WRAP | WRAP, optional)
151
+ padding (string, optional) \u2014 All edges (number) or variable name (string). Per-edge: paddingTop, paddingRight, paddingBottom, paddingLeft.
152
+ paddingTop (string, optional)
153
+ paddingRight (string, optional)
154
+ paddingBottom (string, optional)
155
+ paddingLeft (string, optional)
156
+ primaryAxisAlignItems (MIN | MAX | CENTER | SPACE_BETWEEN, optional)
157
+ counterAxisAlignItems (MIN | MAX | CENTER | BASELINE, optional)
158
+ layoutSizingHorizontal (FIXED | HUG | FILL, optional)
159
+ layoutSizingVertical (FIXED | HUG | FILL, optional)
160
+ itemSpacing (string, optional) \u2014 Spacing between children (number or variable name string, default: 0)
161
+ counterAxisSpacing (string, optional) \u2014 Gap between wrapped rows (requires layoutWrap: WRAP)
162
+ minWidth (number, optional) \u2014 Min width for responsive auto-layout
163
+ maxWidth (number, optional) \u2014 Max width for responsive auto-layout
164
+ minHeight (number, optional) \u2014 Min height for responsive auto-layout
165
+ maxHeight (number, optional) \u2014 Max height for responsive auto-layout
166
+ overflowDirection (NONE | HORIZONTAL | VERTICAL | BOTH, optional) \u2014 Scroll overflow in prototype (default: NONE)
167
+ description (string, optional) \u2014 Component description (shown in Figma's component panel)
168
+ children (array, optional) \u2014 Inline child nodes. Text: {type:"text", text, componentPropertyName?, fontFamily?, fontSize?, fontColor?}. Frame: {type:"frame", name?, layoutMode?, fillColor?, children?}. Text with componentPropertyName auto-creates and binds a TEXT property \u2014 no need to add it to properties separately.
169
+ properties (array, optional) \u2014 Component properties to define at creation: [{propertyName, type, defaultValue}]. TEXT properties for inline children with componentPropertyName are created automatically.
170
+ propertyName (string, required) \u2014 Property name
171
+ type (BOOLEAN | TEXT | INSTANCE_SWAP, required) \u2014 Property type
172
+ defaultValue (string_or_boolean, required) \u2014 Default value
173
+ preferredValues (array, optional) \u2014 Preferred values for INSTANCE_SWAP
174
+
175
+ ## from_node \u2014 Convert existing nodes to components. Text children auto-exposed as editable properties (exposeText: true).
176
+ nodeId (string, required) \u2014 Node ID to convert
177
+ exposeText (boolean, optional) \u2014 Auto-expose text as editable properties (default: true)
178
+
179
+ ## variant_set \u2014 Combine components into a variant set. The resulting set is a frame \u2014 accepts all frame properties for layout/styling.
180
+ name (string, optional) \u2014 Node name
181
+ parentId (string, optional) \u2014 Parent node ID. Omit to place on current page.
182
+ x (number, optional) \u2014 X position (default: 0)
183
+ y (number, optional) \u2014 Y position (default: 0)
184
+ width (number, optional) \u2014 Width (default: 100)
185
+ height (number, optional) \u2014 Height (default: 100)
186
+ rotation (number, optional) \u2014 Rotation in degrees (0-360)
187
+ opacity (string, optional) \u2014 Opacity (0-1) or variable name
188
+ visible (boolean, optional) \u2014 Show/hide (default true)
189
+ locked (boolean, optional) \u2014 Lock/unlock (default false)
190
+ blendMode (PASS_THROUGH | NORMAL | DARKEN | MULTIPLY | LINEAR_BURN | COLOR_BURN | LIGHTEN | SCREEN | LINEAR_DODGE | COLOR_DODGE | OVERLAY | SOFT_LIGHT | HARD_LIGHT | DIFFERENCE | EXCLUSION | HUE | SATURATION | COLOR | LUMINOSITY, optional)
191
+ layoutPositioning (AUTO | ABSOLUTE, optional) \u2014 ABSOLUTE = floating inside auto-layout parent
192
+ fills (array, optional) \u2014 Fill paints array \u2014 e.g. [{type: 'SOLID', color: '#hex'}] or [] for transparent. Primary way to set fills.
193
+ fillColor (Color, optional) \u2014 Shorthand \u2014 sets a single solid fill (auto-binds to matching variable/style)
194
+ fillStyleName (string, optional) \u2014 Paint style name for fill
195
+ fillVariableName (string, optional) \u2014 Color variable by name e.g. 'bg/primary'
196
+ strokes (array, optional) \u2014 Stroke paints array \u2014 e.g. [{type: 'SOLID', color: '#hex'}] or [] to clear. Primary way to set strokes.
197
+ strokeColor (Color, optional) \u2014 Shorthand \u2014 sets a single solid stroke (auto-binds to matching variable/style)
198
+ strokeStyleName (string, optional) \u2014 Paint style name for stroke
199
+ strokeVariableName (string, optional) \u2014 Color variable by name for stroke
200
+ strokeWeight (string, optional) \u2014 All sides (number) or variable name (string). Per-side: strokeTopWeight, strokeBottomWeight, strokeLeftWeight, strokeRightWeight.
201
+ strokeTopWeight (string, optional)
202
+ strokeBottomWeight (string, optional)
203
+ strokeLeftWeight (string, optional)
204
+ strokeRightWeight (string, optional)
205
+ strokeAlign (INSIDE | OUTSIDE | CENTER, optional) \u2014 Stroke position (default: INSIDE)
206
+ strokesIncludedInLayout (boolean, optional) \u2014 Include stroke width in layout measurements (default: false)
207
+ cornerRadius (string, optional) \u2014 All corners (number) or variable name (string). Per-corner: topLeftRadius, topRightRadius, bottomRightRadius, bottomLeftRadius.
208
+ topLeftRadius (string, optional)
209
+ topRightRadius (string, optional)
210
+ bottomRightRadius (string, optional)
211
+ bottomLeftRadius (string, optional)
212
+ effectStyleName (string, optional) \u2014 Effect style name (e.g. 'Shadow/Card') for shadows, blurs
213
+ layoutMode (NONE | HORIZONTAL | VERTICAL, optional) \u2014 Layout direction (default: NONE)
214
+ layoutWrap (NO_WRAP | WRAP, optional)
215
+ padding (string, optional) \u2014 All edges (number) or variable name (string). Per-edge: paddingTop, paddingRight, paddingBottom, paddingLeft.
216
+ paddingTop (string, optional)
217
+ paddingRight (string, optional)
218
+ paddingBottom (string, optional)
219
+ paddingLeft (string, optional)
220
+ primaryAxisAlignItems (MIN | MAX | CENTER | SPACE_BETWEEN, optional)
221
+ counterAxisAlignItems (MIN | MAX | CENTER | BASELINE, optional)
222
+ layoutSizingHorizontal (FIXED | HUG | FILL, optional)
223
+ layoutSizingVertical (FIXED | HUG | FILL, optional)
224
+ itemSpacing (string, optional) \u2014 Spacing between children (number or variable name string, default: 0)
225
+ counterAxisSpacing (string, optional) \u2014 Gap between wrapped rows (requires layoutWrap: WRAP)
226
+ minWidth (number, optional) \u2014 Min width for responsive auto-layout
227
+ maxWidth (number, optional) \u2014 Max width for responsive auto-layout
228
+ minHeight (number, optional) \u2014 Min height for responsive auto-layout
229
+ maxHeight (number, optional) \u2014 Max height for responsive auto-layout
230
+ overflowDirection (NONE | HORIZONTAL | VERTICAL | BOTH, optional) \u2014 Scroll overflow in prototype (default: NONE)
231
+ componentIds (string[], required) \u2014 Component IDs to combine (min 2)
232
+ variantPropertyName (string, optional) \u2014 Rename the auto-generated variant property (default: 'Property 1')`,
233
+ "update": '# components.update\nAdd, edit, or delete component properties\n\nExample: components(method:"update", items:[{id:"1:23", propertyName:"Label", action:"edit", defaultValue:"Click Me"}])\n\nParams:\n items (UpdatePropertyItem[], required) \u2014 Array of {id, propertyName, action?, type?, defaultValue?, name?, preferredValues?}\n id (string, required) \u2014 Component or component set ID\n propertyName (string, required) \u2014 Property name with #suffix for edit/delete (e.g. "Label#1:0"). Call components.get to find exact keys. For add, plain name works.\n action (add | edit | delete | rename_variant, optional) \u2014 "add" (default): requires type + defaultValue. "edit": pass defaultValue to change default, name to rename property. "delete": just propertyName. "rename_variant": pass defaultValue=current option name, name=new option name.\n type (BOOLEAN | TEXT | INSTANCE_SWAP | VARIANT, optional) \u2014 Property type (required for add)\n defaultValue (string_or_boolean, optional) \u2014 Default value (add/edit). For rename_variant: the CURRENT option name to rename\n name (string, optional) \u2014 New name \u2014 for edit: renames the property itself, for rename_variant: the new option value name\n preferredValues (array, optional) \u2014 Preferred values for INSTANCE_SWAP\n depth (number, optional) \u2014 Response detail: omit for id+name only. 0=properties + child stubs. N=recurse N levels. -1=unlimited.',
234
+ "delete": "# components.delete\nDelete components or component sets\n\nParams:\n id (string, required) \u2014 Component or component set ID"
235
+ }
236
+ },
237
+ "connection": {
238
+ "summary": '# connection\nManage the Figma plugin connection.\n\nMethods:\n create Join a relay channel (required first step before any other tool) [read]\n get Verify end-to-end connection to Figma [read]\n list Inspect which clients (MCP, plugin) are connected to each channel [read]\n delete Disconnect all clients (MCP server and Figma plugin) from a channel and reset its state [edit]\n\n// Connection manages the WebSocket link between the MCP server and the Figma plugin.\n// Channels are named rooms \u2014 both the MCP server and Figma plugin must join the same channel to communicate.\n// Workflow: connection(method:"create") to join a channel \u2192 connection(method:"get") to verify Figma plugin is connected.\n// If get times out (5s), the Figma plugin is not running or not on the same channel.\n// list shows all active channels and their connected clients. delete factory-resets a channel.\n\nUse connection(method: "help", topic: "<method>") for method details.',
239
+ "methods": {
240
+ "create": "# connection.create\nJoin a relay channel (required first step before any other tool)\n\nParams:\n channel (string, optional) \u2014 The channel name displayed in the Figma plugin panel. Defaults to 'vibma' if omitted.",
241
+ "get": "# connection.get\nVerify end-to-end connection to Figma\n\nNo params.",
242
+ "list": "# connection.list\nInspect which clients (MCP, plugin) are connected to each channel\n\nNo params.",
243
+ "delete": "# connection.delete\nDisconnect all clients (MCP server and Figma plugin) from a channel and reset its state\n\nParams:\n channel (string, optional) \u2014 Channel to reset. Defaults to 'vibma'."
244
+ }
245
+ },
246
+ "document": {
247
+ "summary": '# document\nNavigate and manage Figma pages (canvases) in the document.\n\nMethods:\n get Get current page with top-level children [read]\n list Get document name and list all pages [read]\n set Switch to a page by ID or name. At least one of pageId or pageName must be provided. [read]\n create Create a new page [create]\n update Rename a page [edit]\n\n// A Figma document contains pages \u2014 each page is an independent canvas with its own node tree.\n// The "current page" is where all node operations happen. Use document.set to switch pages before working with nodes.\n// Page IDs look like "0:1", "1:1", etc. The first page is always "0:1".\n// get returns the current page with its top-level children as stubs. list returns all pages in the document.\n\nUse document(method: "help", topic: "<method>") for method details.',
248
+ "methods": {
249
+ "get": "# document.get\nGet current page with top-level children\n\nNo params.",
250
+ "list": "# document.list\nGet document name and list all pages\n\nNo params.",
251
+ "set": "# document.set\nSwitch to a page by ID or name. At least one of pageId or pageName must be provided.\n\nParams:\n pageId (string, optional) \u2014 Page ID\n pageName (string, optional) \u2014 Page name (case-insensitive, substring match)",
252
+ "create": "# document.create\nCreate a new page\n\nParams:\n name (string, optional) \u2014 Page name (default: 'New Page')",
253
+ "update": "# document.update\nRename a page\n\nParams:\n newName (string, required) \u2014 New page name\n pageId (string, optional) \u2014 Page ID (default: current page)"
254
+ }
255
+ },
256
+ "fonts": {
257
+ "summary": '# fonts\nSearch available fonts in Figma.\n\nMethods:\n list List available font families, optionally filtered by name [read]\n\n// Returns font family names installed in the Figma environment. Use to verify a fontFamily before text.create or styles.create.\n// query filters by family name substring (case-insensitive), e.g. query:"inter" matches "Inter", "Inter Tight".\n// ---\n// Default response: just family names. Set includeStyles:true to also get available styles per family (e.g. "Regular", "Bold", "Italic").\n// Use fonts.list with query to narrow results before creating text with specific fontFamily + fontStyle.\n\nUse fonts(method: "help", topic: "<method>") for method details.',
258
+ "methods": {
259
+ "list": '# fonts.list\nList available font families, optionally filtered by name\n\nExample: fonts(method:"list", query:"inter")\n\nParams:\n query (string, optional) \u2014 Filter by family name (case-insensitive substring)\n includeStyles (boolean, optional) \u2014 Include available styles per family (default: false)\n offset (number, optional) \u2014 Skip N items for pagination (default 0)\n limit (number, optional) \u2014 Max items per page (default 100)'
260
+ }
261
+ },
262
+ "frames": {
263
+ "summary": '# frames\nCreate and manage frames, shapes, auto-layout containers, sections, and SVG nodes.\n\nMethods:\n get Get serialized node data [read]\n list Search for nodes (returns stubs only \u2014 use get with depth for full properties) [read]\n update Patch node properties [edit]\n delete Delete nodes [edit]\n clone Duplicate nodes [create]\n audit Run lint on a node \u2014 returns severity-ranked findings [read]\n reparent Move nodes into a new parent [edit]\n create Create frame-like containers [create]\n export Export a node as PNG, JPG, SVG, SVG_STRING, or PDF [read]\n\n// depth: omit \u2192 id+name stubs | 0 \u2192 props + child stubs | N \u2192 recurse N | -1 \u2192 full tree\n// fields: whitelist e.g. ["fills","opacity"] \u2014 id, name, type always included. Pass ["*"] for all.\n// layoutSizingHorizontal/Vertical: FIXED | HUG | FILL \u2014 how the node sizes within auto-layout.\n// Colors: fillVariableName/strokeVariableName bind by name \u2014 preferred over raw color values.\n// Note: node-based endpoints (frames, text, instances, components) use `results` as the list key.\n// Standalone endpoints (styles, variables, variable_collections) use `items`. Components.list uses `items` (catalog view).\n// ---\n// visible: false hides the node. Omitted from response when true (the default).\n// locked: true prevents editing in Figma UI. Omitted when false (the default).\n// rotation: degrees (0-360). Omitted when 0.\n// blendMode: layer blend mode. Omitted when PASS_THROUGH (the default).\n// layoutPositioning: ABSOLUTE = floating inside auto-layout parent. Omitted when AUTO (the default).\n// minWidth/maxWidth/minHeight/maxHeight: responsive constraints for auto-layout children.\n// constraints: position behavior in non-auto-layout parents. Ignored inside auto-layout frames.\n// bindings: bind design variables to node properties. field uses slash path: "fills/0/color" (first fill), "strokes/0/color", "opacity", "width", "height", "cornerRadius", "paddingLeft", "itemSpacing".\n// explicitMode: pin a variable mode on this node. Use { collectionName, modeName } (preferred) or { collectionId, modeId }.\n// properties: escape hatch \u2014 set any Figma node property directly. Use only when no dedicated field exists.\ninterface Node {\n id: string; name: string; type: string;\n visible?: boolean; // omitted when true\n locked?: boolean; // omitted when false\n opacity?: number; // omitted when 1\n rotation?: number; // omitted when 0\n blendMode?: string; // omitted when PASS_THROUGH\n layoutPositioning?: "AUTO" | "ABSOLUTE";\n layoutSizingHorizontal?: "FIXED" | "HUG" | "FILL";\n layoutSizingVertical?: "FIXED" | "HUG" | "FILL";\n minWidth?: number; maxWidth?: number; minHeight?: number; maxHeight?: number;\n absoluteBoundingBox: { x: number; y: number; width: number; height: number };\n fills?: Paint[]; // solid: {type: "SOLID", color: {r, g, b, a}}\n strokes?: Paint[];\n effects?: Effect[]; // DROP_SHADOW | INNER_SHADOW | LAYER_BLUR | BACKGROUND_BLUR\n children?: NodeStub[]; // stubs: {id, name, type} \u2014 use depth to expand\n}\n// PatchItem uses flat params matching create shape \u2014 no nested sub-objects.\n// Fill/stroke/corner/layout/text params are identical to frames.create and text.create.\n// Unknown keys produce a warning, preventing silent failures.\n// Frames are the primary container in Figma. Use auto_layout for responsive containers.\n// Sizing: FIXED = explicit size, HUG = shrink to children, FILL = expand to fill parent.\n// Fill: pass fills:[{type:"SOLID", color:"#hex"}] or [] for transparent. Shorthand: fillColor:"#3B82F6" (auto-binds to matching variable/style).\n// Also: fillVariableName:"bg/primary", fillStyleName:"Surface/Primary".\n// Stroke: pass strokes:[{type:"SOLID", color:"#hex"}] or [] to clear. Shorthand: strokeColor:"#000", strokeVariableName:"border/default".\n// Token fields (cornerRadius, opacity, itemSpacing, padding, strokeWeight): pass number for value, string for variable name/ID.\n// ---\n// SIZING: Always think about both axes. Containers with text need a width constraint \u2014 set width + layoutSizingHorizontal:"FIXED".\n// HUG/HUG is only correct for intrinsically-sized elements (buttons, badges, icons).\n// Smart defaults inside auto-layout parent: cross-axis defaults to FILL, primary axis stays HUG.\n// FILL only works inside an auto-layout parent. Use FIXED for top-level frames.\n// minWidth/maxWidth/minHeight/maxHeight: responsive constraints for auto-layout children.\n// clipsContent: true (default) clips children to the frame bounds. Set false for overflow-visible.\n// Sections are top-level organizers (like artboards) \u2014 they cannot be nested inside frames.\n// Shape primitives: rectangle, ellipse, line \u2014 for decorative/visual elements (not containers).\n// group: wraps existing nodes into a Group. boolean_operation: UNION/SUBTRACT/INTERSECT/EXCLUDE combines shapes.\n// SVG create: pass raw SVG markup string (e.g. "<svg>...</svg>") \u2014 Figma converts it to vector nodes.\n\nUse frames(method: "help", topic: "<method>") for method details.',
264
+ "methods": {
265
+ "get": '# frames.get\nGet serialized node data\n\nParams:\n id (string, required) \u2014 Node ID\n fields (string[], optional) \u2014 Property whitelist. Identity fields (id, name, type) always included. Omit for stubs on list, full on get. Pass ["*"] for all.\n depth (number, optional) \u2014 Response detail: omit for id+name only. 0=properties + child stubs. N=recurse N levels. -1=unlimited.\n verbose (boolean, optional) \u2014 Include all properties (bounding box, constraints, text style details). Default false \u2014 returns slim, actionable output.',
266
+ "list": '# frames.list\nSearch for nodes (returns stubs only \u2014 use get with depth for full properties)\n\nParams:\n query (string, optional) \u2014 Name search query (case-insensitive substring match)\n types (string[], optional) \u2014 Filter by node types (e.g. ["FRAME", "TEXT"])\n parentId (string, optional) \u2014 Search only within this subtree\n fields (string[], optional) \u2014 Property whitelist. Identity fields (id, name, type) always included. Omit for stubs on list, full on get. Pass ["*"] for all.\n offset (number, optional) \u2014 Skip N items for pagination (default 0)\n limit (number, optional) \u2014 Max items per page (default 100)',
267
+ "update": `# frames.update
268
+ Patch node properties
269
+
270
+ Params:
271
+ items (PatchItem[], required) \u2014 Array of {id, ...properties} to patch
272
+ fills (array, optional) \u2014 Fill paints array \u2014 e.g. [{type: 'SOLID', color: '#hex'}] or [] for transparent. Primary way to set fills.
273
+ fillColor (Color, optional) \u2014 Shorthand \u2014 sets a single solid fill (auto-binds to matching variable/style)
274
+ fillStyleName (string, optional) \u2014 Paint style name for fill
275
+ fillVariableName (string, optional) \u2014 Color variable by name e.g. 'bg/primary'
276
+ strokes (array, optional) \u2014 Stroke paints array \u2014 e.g. [{type: 'SOLID', color: '#hex'}] or [] to clear. Primary way to set strokes.
277
+ strokeColor (Color, optional) \u2014 Shorthand \u2014 sets a single solid stroke (auto-binds to matching variable/style)
278
+ strokeStyleName (string, optional) \u2014 Paint style name for stroke
279
+ strokeVariableName (string, optional) \u2014 Color variable by name for stroke
280
+ strokeWeight (string, optional) \u2014 All sides (number) or variable name (string). Per-side: strokeTopWeight, strokeBottomWeight, strokeLeftWeight, strokeRightWeight.
281
+ strokeTopWeight (string, optional)
282
+ strokeBottomWeight (string, optional)
283
+ strokeLeftWeight (string, optional)
284
+ strokeRightWeight (string, optional)
285
+ strokeAlign (INSIDE | OUTSIDE | CENTER, optional) \u2014 Stroke position (default: INSIDE)
286
+ strokesIncludedInLayout (boolean, optional) \u2014 Include stroke width in layout measurements (default: false)
287
+ cornerRadius (string, optional) \u2014 All corners (number) or variable name (string). Per-corner: topLeftRadius, topRightRadius, bottomRightRadius, bottomLeftRadius.
288
+ topLeftRadius (string, optional)
289
+ topRightRadius (string, optional)
290
+ bottomRightRadius (string, optional)
291
+ bottomLeftRadius (string, optional)
292
+ opacity (string, optional) \u2014 Opacity (0-1) or variable name
293
+ visible (boolean, optional) \u2014 Show/hide (default true)
294
+ locked (boolean, optional) \u2014 Lock/unlock (default false)
295
+ blendMode (PASS_THROUGH | NORMAL | DARKEN | MULTIPLY | LINEAR_BURN | COLOR_BURN | LIGHTEN | SCREEN | LINEAR_DODGE | COLOR_DODGE | OVERLAY | SOFT_LIGHT | HARD_LIGHT | DIFFERENCE | EXCLUSION | HUE | SATURATION | COLOR | LUMINOSITY, optional)
296
+ effectStyleName (string, optional) \u2014 Effect style name (e.g. 'Shadow/Card') for shadows, blurs
297
+ layoutMode (NONE | HORIZONTAL | VERTICAL, optional) \u2014 Layout direction (default: NONE)
298
+ layoutWrap (NO_WRAP | WRAP, optional)
299
+ padding (string, optional) \u2014 All edges (number) or variable name (string). Per-edge: paddingTop, paddingRight, paddingBottom, paddingLeft.
300
+ paddingTop (string, optional)
301
+ paddingRight (string, optional)
302
+ paddingBottom (string, optional)
303
+ paddingLeft (string, optional)
304
+ primaryAxisAlignItems (MIN | MAX | CENTER | SPACE_BETWEEN, optional)
305
+ counterAxisAlignItems (MIN | MAX | CENTER | BASELINE, optional)
306
+ itemSpacing (string, optional) \u2014 Spacing between children (number or variable name string, default: 0)
307
+ counterAxisSpacing (string, optional) \u2014 Gap between wrapped rows (requires layoutWrap: WRAP)
308
+ layoutSizingHorizontal (FIXED | HUG | FILL, optional)
309
+ layoutSizingVertical (FIXED | HUG | FILL, optional)
310
+ layoutPositioning (AUTO | ABSOLUTE, optional) \u2014 ABSOLUTE = floating inside auto-layout parent
311
+ minWidth (number, optional) \u2014 Min width for responsive auto-layout
312
+ maxWidth (number, optional) \u2014 Max width for responsive auto-layout
313
+ minHeight (number, optional) \u2014 Min height for responsive auto-layout
314
+ maxHeight (number, optional) \u2014 Max height for responsive auto-layout
315
+ fontSize (number, optional) \u2014 Font size
316
+ fontFamily (string, optional) \u2014 Font family
317
+ fontStyle (string, optional) \u2014 Font variant e.g. "Bold", "Italic" \u2014 overrides fontWeight
318
+ fontWeight (number, optional) \u2014 100-900. Ignored when fontStyle is set.
319
+ fontColor (Color, optional) \u2014 Shorthand \u2014 sets text color (auto-binds to matching variable/style)
320
+ fontColorVariableName (string, optional) \u2014 Bind color variable by name e.g. 'text/primary'
321
+ fontColorStyleName (string, optional) \u2014 Apply paint style \u2014 overrides fontColor
322
+ textStyleId (string, optional) \u2014 Apply text style by ID \u2014 overrides fontSize/fontWeight
323
+ textStyleName (string, optional) \u2014 Text style by name (case-insensitive)
324
+ textAlignHorizontal (LEFT | CENTER | RIGHT | JUSTIFIED, optional)
325
+ textAlignVertical (TOP | CENTER | BOTTOM, optional)
326
+ textAutoResize (NONE | WIDTH_AND_HEIGHT | HEIGHT | TRUNCATE, optional)
327
+ id (string, required)
328
+ name (string, optional) \u2014 Rename node
329
+ rotation (number, optional) \u2014 Degrees (0-360)
330
+ x (number, optional)
331
+ y (number, optional)
332
+ width (number, optional)
333
+ height (number, optional)
334
+ clearFill (boolean, optional) \u2014 Remove all fills
335
+ effects (array, optional) \u2014 Effect array (DROP_SHADOW, INNER_SHADOW, LAYER_BLUR, BACKGROUND_BLUR)
336
+ overflowDirection (NONE | HORIZONTAL | VERTICAL | BOTH, optional) \u2014 Scroll overflow in prototype (default: NONE)
337
+ constraints (object, optional)
338
+ bindings (array, optional) \u2014 Bind variables to properties. field path examples: 'fills/0/color', 'strokes/0/color', 'opacity', 'width', 'cornerRadius', 'itemSpacing'.
339
+ field (string, required)
340
+ variableName (string, optional)
341
+ variableId (string, optional)
342
+ explicitMode (object, optional) \u2014 Pin variable mode \u2014 use { collectionName, modeName } (preferred) or { collectionId, modeId }
343
+ exportSettings (array, optional) \u2014 Export settings
344
+ properties (object, optional) \u2014 Direct Figma API props (escape hatch)`,
345
+ "delete": "# frames.delete\nDelete nodes\n\nParams:\n id (string, optional) \u2014 Single node ID\n items (array, optional) \u2014 Batch: [{id}, ...]\n id (string, optional)",
346
+ "clone": "# frames.clone\nDuplicate nodes\n\nParams:\n id (string, required) \u2014 Node ID\n parentId (string, optional) \u2014 Parent node ID. Omit to place on current page.\n x (number, optional) \u2014 X position (default: 0)\n y (number, optional) \u2014 Y position (default: 0)\n depth (number, optional) \u2014 Response detail: omit for id+name only. 0=properties + child stubs. N=recurse N levels. -1=unlimited.",
347
+ "audit": '# frames.audit\nRun lint on a node \u2014 returns severity-ranked findings\n\nParams:\n id (string, required) \u2014 Node ID to audit\n rules (string[], optional) \u2014 Rules to check. Default: ["all"]. Categories: "component", "composition", "token", "naming", "wcag".\n maxDepth (number, optional) \u2014 Max tree depth (default: 10)\n maxFindings (number, optional) \u2014 Max findings (default: 50)',
348
+ "reparent": "# frames.reparent\nMove nodes into a new parent\n\nParams:\n items (array, required) \u2014 Array of {id, parentId, index?}\n id (string, required)\n parentId (string, required)\n index (number, optional)",
349
+ "create": `# frames.create
350
+ Create frame-like containers
351
+
352
+ Example: frames(method:"create", type:"auto_layout", layoutMode:"VERTICAL", itemSpacing:16, padding:24)
353
+
354
+ Discriminant: type (frame | auto_layout | section | rectangle | ellipse | line | group | boolean_operation | svg)
355
+
356
+ ## frame \u2014 Static frame with fixed dimensions
357
+ name (string, optional) \u2014 Node name
358
+ parentId (string, optional) \u2014 Parent node ID. Omit to place on current page.
359
+ x (number, optional) \u2014 X position (default: 0)
360
+ y (number, optional) \u2014 Y position (default: 0)
361
+ width (number, optional) \u2014 Width (default: 100)
362
+ height (number, optional) \u2014 Height (default: 100)
363
+ rotation (number, optional) \u2014 Rotation in degrees (0-360)
364
+ opacity (string, optional) \u2014 Opacity (0-1) or variable name
365
+ visible (boolean, optional) \u2014 Show/hide (default true)
366
+ locked (boolean, optional) \u2014 Lock/unlock (default false)
367
+ blendMode (PASS_THROUGH | NORMAL | DARKEN | MULTIPLY | LINEAR_BURN | COLOR_BURN | LIGHTEN | SCREEN | LINEAR_DODGE | COLOR_DODGE | OVERLAY | SOFT_LIGHT | HARD_LIGHT | DIFFERENCE | EXCLUSION | HUE | SATURATION | COLOR | LUMINOSITY, optional)
368
+ layoutPositioning (AUTO | ABSOLUTE, optional) \u2014 ABSOLUTE = floating inside auto-layout parent
369
+ fills (array, optional) \u2014 Fill paints array \u2014 e.g. [{type: 'SOLID', color: '#hex'}] or [] for transparent. Primary way to set fills.
370
+ fillColor (Color, optional) \u2014 Shorthand \u2014 sets a single solid fill (auto-binds to matching variable/style)
371
+ fillStyleName (string, optional) \u2014 Paint style name for fill
372
+ fillVariableName (string, optional) \u2014 Color variable by name e.g. 'bg/primary'
373
+ strokes (array, optional) \u2014 Stroke paints array \u2014 e.g. [{type: 'SOLID', color: '#hex'}] or [] to clear. Primary way to set strokes.
374
+ strokeColor (Color, optional) \u2014 Shorthand \u2014 sets a single solid stroke (auto-binds to matching variable/style)
375
+ strokeStyleName (string, optional) \u2014 Paint style name for stroke
376
+ strokeVariableName (string, optional) \u2014 Color variable by name for stroke
377
+ strokeWeight (string, optional) \u2014 All sides (number) or variable name (string). Per-side: strokeTopWeight, strokeBottomWeight, strokeLeftWeight, strokeRightWeight.
378
+ strokeTopWeight (string, optional)
379
+ strokeBottomWeight (string, optional)
380
+ strokeLeftWeight (string, optional)
381
+ strokeRightWeight (string, optional)
382
+ strokeAlign (INSIDE | OUTSIDE | CENTER, optional) \u2014 Stroke position (default: INSIDE)
383
+ strokesIncludedInLayout (boolean, optional) \u2014 Include stroke width in layout measurements (default: false)
384
+ cornerRadius (string, optional) \u2014 All corners (number) or variable name (string). Per-corner: topLeftRadius, topRightRadius, bottomRightRadius, bottomLeftRadius.
385
+ topLeftRadius (string, optional)
386
+ topRightRadius (string, optional)
387
+ bottomRightRadius (string, optional)
388
+ bottomLeftRadius (string, optional)
389
+ effectStyleName (string, optional) \u2014 Effect style name (e.g. 'Shadow/Card') for shadows, blurs
390
+ layoutMode (NONE | HORIZONTAL | VERTICAL, optional) \u2014 Layout direction (default: NONE)
391
+ layoutWrap (NO_WRAP | WRAP, optional)
392
+ padding (string, optional) \u2014 All edges (number) or variable name (string). Per-edge: paddingTop, paddingRight, paddingBottom, paddingLeft.
393
+ paddingTop (string, optional)
394
+ paddingRight (string, optional)
395
+ paddingBottom (string, optional)
396
+ paddingLeft (string, optional)
397
+ primaryAxisAlignItems (MIN | MAX | CENTER | SPACE_BETWEEN, optional)
398
+ counterAxisAlignItems (MIN | MAX | CENTER | BASELINE, optional)
399
+ layoutSizingHorizontal (FIXED | HUG | FILL, optional)
400
+ layoutSizingVertical (FIXED | HUG | FILL, optional)
401
+ itemSpacing (string, optional) \u2014 Spacing between children (number or variable name string, default: 0)
402
+ counterAxisSpacing (string, optional) \u2014 Gap between wrapped rows (requires layoutWrap: WRAP)
403
+ minWidth (number, optional) \u2014 Min width for responsive auto-layout
404
+ maxWidth (number, optional) \u2014 Max width for responsive auto-layout
405
+ minHeight (number, optional) \u2014 Min height for responsive auto-layout
406
+ maxHeight (number, optional) \u2014 Max height for responsive auto-layout
407
+ overflowDirection (NONE | HORIZONTAL | VERTICAL | BOTH, optional) \u2014 Scroll overflow in prototype (default: NONE)
408
+ clipsContent (boolean, optional)
409
+
410
+ ## auto_layout \u2014 Auto-layout frame that arranges children automatically
411
+ name (string, optional) \u2014 Node name
412
+ parentId (string, optional) \u2014 Parent node ID. Omit to place on current page.
413
+ x (number, optional) \u2014 X position (default: 0)
414
+ y (number, optional) \u2014 Y position (default: 0)
415
+ width (number, optional) \u2014 Width (default: 100)
416
+ height (number, optional) \u2014 Height (default: 100)
417
+ rotation (number, optional) \u2014 Rotation in degrees (0-360)
418
+ opacity (string, optional) \u2014 Opacity (0-1) or variable name
419
+ visible (boolean, optional) \u2014 Show/hide (default true)
420
+ locked (boolean, optional) \u2014 Lock/unlock (default false)
421
+ blendMode (PASS_THROUGH | NORMAL | DARKEN | MULTIPLY | LINEAR_BURN | COLOR_BURN | LIGHTEN | SCREEN | LINEAR_DODGE | COLOR_DODGE | OVERLAY | SOFT_LIGHT | HARD_LIGHT | DIFFERENCE | EXCLUSION | HUE | SATURATION | COLOR | LUMINOSITY, optional)
422
+ layoutPositioning (AUTO | ABSOLUTE, optional) \u2014 ABSOLUTE = floating inside auto-layout parent
423
+ fills (array, optional) \u2014 Fill paints array \u2014 e.g. [{type: 'SOLID', color: '#hex'}] or [] for transparent. Primary way to set fills.
424
+ fillColor (Color, optional) \u2014 Shorthand \u2014 sets a single solid fill (auto-binds to matching variable/style)
425
+ fillStyleName (string, optional) \u2014 Paint style name for fill
426
+ fillVariableName (string, optional) \u2014 Color variable by name e.g. 'bg/primary'
427
+ strokes (array, optional) \u2014 Stroke paints array \u2014 e.g. [{type: 'SOLID', color: '#hex'}] or [] to clear. Primary way to set strokes.
428
+ strokeColor (Color, optional) \u2014 Shorthand \u2014 sets a single solid stroke (auto-binds to matching variable/style)
429
+ strokeStyleName (string, optional) \u2014 Paint style name for stroke
430
+ strokeVariableName (string, optional) \u2014 Color variable by name for stroke
431
+ strokeWeight (string, optional) \u2014 All sides (number) or variable name (string). Per-side: strokeTopWeight, strokeBottomWeight, strokeLeftWeight, strokeRightWeight.
432
+ strokeTopWeight (string, optional)
433
+ strokeBottomWeight (string, optional)
434
+ strokeLeftWeight (string, optional)
435
+ strokeRightWeight (string, optional)
436
+ strokeAlign (INSIDE | OUTSIDE | CENTER, optional) \u2014 Stroke position (default: INSIDE)
437
+ strokesIncludedInLayout (boolean, optional) \u2014 Include stroke width in layout measurements (default: false)
438
+ cornerRadius (string, optional) \u2014 All corners (number) or variable name (string). Per-corner: topLeftRadius, topRightRadius, bottomRightRadius, bottomLeftRadius.
439
+ topLeftRadius (string, optional)
440
+ topRightRadius (string, optional)
441
+ bottomRightRadius (string, optional)
442
+ bottomLeftRadius (string, optional)
443
+ effectStyleName (string, optional) \u2014 Effect style name (e.g. 'Shadow/Card') for shadows, blurs
444
+ layoutMode (HORIZONTAL | VERTICAL, required) \u2014 Primary axis direction
445
+ layoutWrap (NO_WRAP | WRAP, optional)
446
+ padding (string, optional) \u2014 All edges (number) or variable name (string). Per-edge: paddingTop, paddingRight, paddingBottom, paddingLeft.
447
+ paddingTop (string, optional)
448
+ paddingRight (string, optional)
449
+ paddingBottom (string, optional)
450
+ paddingLeft (string, optional)
451
+ primaryAxisAlignItems (MIN | MAX | CENTER | SPACE_BETWEEN, optional)
452
+ counterAxisAlignItems (MIN | MAX | CENTER | BASELINE, optional)
453
+ layoutSizingHorizontal (FIXED | HUG | FILL, optional)
454
+ layoutSizingVertical (FIXED | HUG | FILL, optional)
455
+ itemSpacing (string, optional) \u2014 Spacing between children (number or variable name string, default: 0)
456
+ counterAxisSpacing (string, optional) \u2014 Gap between wrapped rows (requires layoutWrap: WRAP)
457
+ minWidth (number, optional) \u2014 Min width for responsive auto-layout
458
+ maxWidth (number, optional) \u2014 Max width for responsive auto-layout
459
+ minHeight (number, optional) \u2014 Min height for responsive auto-layout
460
+ maxHeight (number, optional) \u2014 Max height for responsive auto-layout
461
+ overflowDirection (NONE | HORIZONTAL | VERTICAL | BOTH, optional) \u2014 Scroll overflow in prototype (default: NONE)
462
+ clipsContent (boolean, optional)
463
+ nodeIds (string[], optional) \u2014 Existing node IDs to wrap into auto-layout
464
+
465
+ ## section \u2014 Figma section (top-level organizer)
466
+ name (string, required) \u2014 Section name
467
+ parentId (string, optional) \u2014 Parent node ID. Omit to place on current page.
468
+ x (number, optional) \u2014 X position (default: 0)
469
+ y (number, optional) \u2014 Y position (default: 0)
470
+ width (number, optional) \u2014 Width (default: 500)
471
+ height (number, optional) \u2014 Height (default: 500)
472
+ fills (array, optional) \u2014 Fill paints array \u2014 e.g. [{type: 'SOLID', color: '#hex'}] or [] for transparent
473
+ fillColor (Color, optional) \u2014 Shorthand \u2014 sets a single solid fill (auto-binds to matching variable/style)
474
+ fillStyleName (string, optional) \u2014 Paint style name for fill
475
+ fillVariableName (string, optional) \u2014 Color variable by name e.g. 'bg/primary'
476
+
477
+ ## rectangle \u2014 Rectangle shape node
478
+ name (string, optional) \u2014 Layer name (default: 'Rectangle')
479
+ parentId (string, optional) \u2014 Parent node ID. Omit to place on current page.
480
+ x (number, optional) \u2014 X position (default: 0)
481
+ y (number, optional) \u2014 Y position (default: 0)
482
+ width (number, optional) \u2014 Width in px (default: 100)
483
+ height (number, optional) \u2014 Height in px (default: 100)
484
+ fills (array, optional) \u2014 Fill paints array \u2014 e.g. [{type: 'SOLID', color: '#hex'}] or [] for transparent
485
+ fillColor (Color, optional) \u2014 Shorthand \u2014 sets a single solid fill (auto-binds to matching variable/style)
486
+ fillStyleName (string, optional) \u2014 Paint style name for fill
487
+ fillVariableName (string, optional) \u2014 Color variable by name e.g. 'bg/primary'
488
+ strokes (array, optional) \u2014 Stroke paints array \u2014 e.g. [{type: 'SOLID', color: '#hex'}] or [] to clear
489
+ strokeColor (Color, optional) \u2014 Shorthand \u2014 sets a single solid stroke (auto-binds to matching variable/style)
490
+ strokeVariableName (string, optional) \u2014 Color variable by name for stroke
491
+ strokeWeight (string, optional)
492
+ cornerRadius (string, optional) \u2014 All corners (number) or variable name (string). Per-corner: topLeftRadius, topRightRadius, bottomRightRadius, bottomLeftRadius.
493
+ topLeftRadius (string, optional)
494
+ topRightRadius (string, optional)
495
+ bottomRightRadius (string, optional)
496
+ bottomLeftRadius (string, optional)
497
+ opacity (string, optional)
498
+ layoutSizingHorizontal (FIXED | FILL, optional) \u2014 Horizontal sizing in auto-layout parent
499
+ layoutSizingVertical (FIXED | FILL, optional) \u2014 Vertical sizing in auto-layout parent
500
+
501
+ ## ellipse \u2014 Ellipse/circle shape node
502
+ name (string, optional) \u2014 Layer name (default: 'Ellipse')
503
+ parentId (string, optional) \u2014 Parent node ID. Omit to place on current page.
504
+ x (number, optional) \u2014 X position (default: 0)
505
+ y (number, optional) \u2014 Y position (default: 0)
506
+ width (number, optional) \u2014 Width in px (default: 100)
507
+ height (number, optional) \u2014 Height in px (default: 100, same as width for circle)
508
+ fills (array, optional) \u2014 Fill paints array \u2014 e.g. [{type: 'SOLID', color: '#hex'}] or [] for transparent
509
+ fillColor (Color, optional) \u2014 Shorthand \u2014 sets a single solid fill (auto-binds to matching variable/style)
510
+ fillStyleName (string, optional) \u2014 Paint style name for fill
511
+ fillVariableName (string, optional) \u2014 Color variable by name e.g. 'bg/primary'
512
+ strokes (array, optional) \u2014 Stroke paints array \u2014 e.g. [{type: 'SOLID', color: '#hex'}] or [] to clear
513
+ strokeColor (Color, optional) \u2014 Shorthand \u2014 sets a single solid stroke (auto-binds to matching variable/style)
514
+ strokeVariableName (string, optional) \u2014 Color variable by name for stroke
515
+ strokeWeight (string, optional)
516
+ opacity (string, optional)
517
+ layoutSizingHorizontal (FIXED | FILL, optional) \u2014 Horizontal sizing in auto-layout parent
518
+ layoutSizingVertical (FIXED | FILL, optional) \u2014 Vertical sizing in auto-layout parent
519
+
520
+ ## line \u2014 Line shape node
521
+ name (string, optional) \u2014 Layer name (default: 'Line')
522
+ parentId (string, optional) \u2014 Parent node ID. Omit to place on current page.
523
+ x (number, optional) \u2014 X position (default: 0)
524
+ y (number, optional) \u2014 Y position (default: 0)
525
+ length (number, optional) \u2014 Line length in px (default: 100)
526
+ rotation (number, optional) \u2014 Rotation in degrees (default: 0 = horizontal)
527
+ strokes (array, optional) \u2014 Stroke paints array \u2014 e.g. [{type: 'SOLID', color: '#hex'}] or [] to clear
528
+ strokeColor (Color, optional) \u2014 Line color (default: black, auto-binds to matching variable/style)
529
+ strokeVariableName (string, optional) \u2014 Color variable by name for stroke
530
+ strokeWeight (string, optional) \u2014 Line thickness (default: 1)
531
+ opacity (string, optional)
532
+ layoutSizingHorizontal (FIXED | FILL, optional) \u2014 Horizontal sizing in auto-layout parent (defaults to FILL in vertical auto-layout)
533
+
534
+ ## group \u2014 Group existing nodes together
535
+ nodeIds (string[], required) \u2014 Node IDs to group (min 1)
536
+ name (string, optional) \u2014 Group name
537
+ parentId (string, optional) \u2014 Parent node ID. Omit to place on current page.
538
+
539
+ ## boolean_operation \u2014 Combine shapes with boolean operations (union, subtract, intersect, exclude)
540
+ operation (UNION | SUBTRACT | INTERSECT | EXCLUDE, required) \u2014 Boolean operation type
541
+ nodeIds (string[], required) \u2014 Node IDs to combine (min 2, first node is the base for SUBTRACT)
542
+ name (string, optional) \u2014 Result node name
543
+ parentId (string, optional) \u2014 Parent node ID. Omit to place on current page.
544
+
545
+ ## svg \u2014 Create node from SVG markup
546
+ svg (string, required) \u2014 SVG markup string
547
+ name (string, optional) \u2014 Layer name (default: 'SVG')
548
+ parentId (string, optional) \u2014 Parent node ID. Omit to place on current page.
549
+ x (number, optional) \u2014 X position (default: 0)
550
+ y (number, optional) \u2014 Y position (default: 0)
551
+ fillStyleName (string, optional) \u2014 Paint style to apply to vector fills
552
+ fillVariableName (string, optional) \u2014 Color variable by name for vector fills
553
+ strokeStyleName (string, optional) \u2014 Paint style to apply to vector strokes
554
+ strokeVariableName (string, optional) \u2014 Color variable by name for vector strokes`,
555
+ "export": "# frames.export\nExport a node as PNG, JPG, SVG, SVG_STRING, or PDF\n\nParams:\n id (string, required) \u2014 Node ID to export\n format (PNG | JPG | SVG | SVG_STRING | PDF, optional) \u2014 Export format (default: PNG). SVG_STRING returns raw SVG text.\n scale (number, optional) \u2014 Export scale (default: 1, only for PNG/JPG)"
556
+ }
557
+ },
558
+ "instances": {
559
+ "summary": '# instances\nCreate and manage component instances.\n\nMethods:\n list Search for nodes (returns stubs only \u2014 use get with depth for full properties) [read]\n delete Delete nodes [edit]\n clone Duplicate nodes [create]\n audit Run lint on a node \u2014 returns severity-ranked findings [read]\n reparent Move nodes into a new parent [edit]\n get Get instance detail with component properties and overrides [read]\n create Create component instances [create]\n update Set instance properties [edit]\n swap Swap instance component (preserves overrides) [edit]\n detach Detach instances from their component (converts to frame) [edit]\n reset_overrides Reset all overrides on instances to match their main component [edit]\n\n// depth: omit \u2192 id+name stubs | 0 \u2192 props + child stubs | N \u2192 recurse N | -1 \u2192 full tree\n// fields: whitelist e.g. ["fills","opacity"] \u2014 id, name, type always included. Pass ["*"] for all.\n// layoutSizingHorizontal/Vertical: FIXED | HUG | FILL \u2014 how the node sizes within auto-layout.\n// Colors: fillVariableName/strokeVariableName bind by name \u2014 preferred over raw color values.\n// Note: node-based endpoints (frames, text, instances, components) use `results` as the list key.\n// Standalone endpoints (styles, variables, variable_collections) use `items`. Components.list uses `items` (catalog view).\n// ---\n// visible: false hides the node. Omitted from response when true (the default).\n// locked: true prevents editing in Figma UI. Omitted when false (the default).\n// rotation: degrees (0-360). Omitted when 0.\n// blendMode: layer blend mode. Omitted when PASS_THROUGH (the default).\n// layoutPositioning: ABSOLUTE = floating inside auto-layout parent. Omitted when AUTO (the default).\n// minWidth/maxWidth/minHeight/maxHeight: responsive constraints for auto-layout children.\n// constraints: position behavior in non-auto-layout parents. Ignored inside auto-layout frames.\n// bindings: bind design variables to node properties. field uses slash path: "fills/0/color" (first fill), "strokes/0/color", "opacity", "width", "height", "cornerRadius", "paddingLeft", "itemSpacing".\n// explicitMode: pin a variable mode on this node. Use { collectionName, modeName } (preferred) or { collectionId, modeId }.\n// properties: escape hatch \u2014 set any Figma node property directly. Use only when no dedicated field exists.\ninterface Node {\n id: string; name: string; type: string;\n visible?: boolean; // omitted when true\n locked?: boolean; // omitted when false\n opacity?: number; // omitted when 1\n rotation?: number; // omitted when 0\n blendMode?: string; // omitted when PASS_THROUGH\n layoutPositioning?: "AUTO" | "ABSOLUTE";\n layoutSizingHorizontal?: "FIXED" | "HUG" | "FILL";\n layoutSizingVertical?: "FIXED" | "HUG" | "FILL";\n minWidth?: number; maxWidth?: number; minHeight?: number; maxHeight?: number;\n absoluteBoundingBox: { x: number; y: number; width: number; height: number };\n fills?: Paint[]; // solid: {type: "SOLID", color: {r, g, b, a}}\n strokes?: Paint[];\n effects?: Effect[]; // DROP_SHADOW | INNER_SHADOW | LAYER_BLUR | BACKGROUND_BLUR\n children?: NodeStub[]; // stubs: {id, name, type} \u2014 use depth to expand\n}\n// PatchItem uses flat params matching create shape \u2014 no nested sub-objects.\n// Fill/stroke/corner/layout/text params are identical to frames.create and text.create.\n// Unknown keys produce a warning, preventing silent failures.\n// Instances are linked copies of components. Changes to the component propagate to all instances.\n// Overrides: instance-level changes (text, fills, visibility) that differ from the component. Shown in overrides array.\n// variantProperties: when creating from a component set, pick which variant e.g. {"Style":"Secondary","Size":"Large"}.\n// ---\n// Property keys: read returns clean names ("Label"), write requires the #suffix ("Label#1:0"). Call instances.get(id) to discover exact keys before update.\n// swap: change which component an instance points to while preserving compatible overrides.\n// detach: permanently converts an instance to a regular frame, breaking the component link.\n// reset_overrides: reverts all instance overrides to match the main component.\n// Workflow: components.list \u2192 instances.create with componentId + properties (one call). No separate update needed for text/boolean overrides.\n// Instances support frame-level overrides: layoutSizingHorizontal/Vertical (FIXED, FILL, HUG), opacity, width/height, min/max.\n// Use layoutSizingHorizontal: "FILL" to make instances stretch in auto-layout parents.\n\nUse instances(method: "help", topic: "<method>") for method details.',
560
+ "methods": {
561
+ "list": '# instances.list\nSearch for nodes (returns stubs only \u2014 use get with depth for full properties)\n\nParams:\n query (string, optional) \u2014 Name search query (case-insensitive substring match)\n types (string[], optional) \u2014 Filter by node types (e.g. ["FRAME", "TEXT"])\n parentId (string, optional) \u2014 Search only within this subtree\n fields (string[], optional) \u2014 Property whitelist. Identity fields (id, name, type) always included. Omit for stubs on list, full on get. Pass ["*"] for all.\n offset (number, optional) \u2014 Skip N items for pagination (default 0)\n limit (number, optional) \u2014 Max items per page (default 100)',
562
+ "delete": "# instances.delete\nDelete nodes\n\nParams:\n id (string, optional) \u2014 Single node ID\n items (array, optional) \u2014 Batch: [{id}, ...]\n id (string, optional)",
563
+ "clone": "# instances.clone\nDuplicate nodes\n\nParams:\n id (string, required) \u2014 Node ID\n parentId (string, optional) \u2014 Parent node ID. Omit to place on current page.\n x (number, optional) \u2014 X position (default: 0)\n y (number, optional) \u2014 Y position (default: 0)\n depth (number, optional) \u2014 Response detail: omit for id+name only. 0=properties + child stubs. N=recurse N levels. -1=unlimited.",
564
+ "audit": '# instances.audit\nRun lint on a node \u2014 returns severity-ranked findings\n\nParams:\n id (string, required) \u2014 Node ID to audit\n rules (string[], optional) \u2014 Rules to check. Default: ["all"]. Categories: "component", "composition", "token", "naming", "wcag".\n maxDepth (number, optional) \u2014 Max tree depth (default: 10)\n maxFindings (number, optional) \u2014 Max findings (default: 50)',
565
+ "reparent": "# instances.reparent\nMove nodes into a new parent\n\nParams:\n items (array, required) \u2014 Array of {id, parentId, index?}\n id (string, required)\n parentId (string, required)\n index (number, optional)",
566
+ "get": '# instances.get\nGet instance detail with component properties and overrides\n\nParams:\n id (string, required) \u2014 Instance node ID\n fields (string[], optional) \u2014 Property whitelist. Identity fields (id, name, type) always included. Omit for stubs on list, full on get. Pass ["*"] for all.\n depth (number, optional) \u2014 Response detail: omit for id+name only. 0=properties + child stubs. N=recurse N levels. -1=unlimited.\n verbose (boolean, optional) \u2014 Include all properties (bounding box, constraints, text style details). Default false \u2014 returns slim, actionable output.',
567
+ "create": `# instances.create
568
+ Create component instances
569
+
570
+ Example: instances(method:"create", items:[{componentId:"1:23", variantProperties:{"Size":"Large"}, properties:{"Label":"Click me"}, parentId:"2:45", layoutSizingHorizontal:"FILL"}])
571
+
572
+ Params:
573
+ items (InstanceCreateItem[], required) \u2014 Array of {componentId, variantProperties?, x?, y?, parentId?, layoutSizingHorizontal?, ...}
574
+ opacity (string, optional) \u2014 Opacity (0-1) or variable name
575
+ visible (boolean, optional) \u2014 Show/hide (default true)
576
+ locked (boolean, optional) \u2014 Lock/unlock (default false)
577
+ blendMode (PASS_THROUGH | NORMAL | DARKEN | MULTIPLY | LINEAR_BURN | COLOR_BURN | LIGHTEN | SCREEN | LINEAR_DODGE | COLOR_DODGE | OVERLAY | SOFT_LIGHT | HARD_LIGHT | DIFFERENCE | EXCLUSION | HUE | SATURATION | COLOR | LUMINOSITY, optional)
578
+ effectStyleName (string, optional) \u2014 Effect style name (e.g. 'Shadow/Card') for shadows, blurs
579
+ layoutSizingHorizontal (FIXED | HUG | FILL, optional)
580
+ layoutSizingVertical (FIXED | HUG | FILL, optional)
581
+ layoutPositioning (AUTO | ABSOLUTE, optional) \u2014 ABSOLUTE = floating inside auto-layout parent
582
+ minWidth (number, optional) \u2014 Min width for responsive auto-layout
583
+ maxWidth (number, optional) \u2014 Max width for responsive auto-layout
584
+ minHeight (number, optional) \u2014 Min height for responsive auto-layout
585
+ maxHeight (number, optional) \u2014 Max height for responsive auto-layout
586
+ componentId (string, required) \u2014 Component or component set ID
587
+ variantProperties (object, optional) \u2014 Pick variant e.g. {"Style":"Secondary"}
588
+ properties (object, optional) \u2014 Set component properties inline e.g. {"Label":"Click me", "ShowIcon":true}. Same as instances.update properties.
589
+ name (string, optional) \u2014 Instance layer name
590
+ x (number, optional)
591
+ y (number, optional)
592
+ width (number, optional) \u2014 Override width (resize)
593
+ height (number, optional) \u2014 Override height (resize)
594
+ parentId (string, optional) \u2014 Parent node ID. Omit to place on current page.
595
+ depth (number, optional) \u2014 Response detail: omit for id+name only. 0=properties + child stubs. N=recurse N levels. -1=unlimited.`,
596
+ "update": `# instances.update
597
+ Set instance properties
598
+
599
+ Example: instances(method:"update", items:[{id:"1:23", properties:{"Label#4:0":"Submit"}, fillColor:"#3B82F6", layoutSizingHorizontal:"FILL"}])
600
+
601
+ Params:
602
+ items (InstanceUpdateItem[], required) \u2014 Array of {id, properties: {"Label#1:0":"text"}, fillColor?: ...}
603
+ fills (array, optional) \u2014 Fill paints array \u2014 e.g. [{type: 'SOLID', color: '#hex'}] or [] for transparent. Primary way to set fills.
604
+ fillColor (Color, optional) \u2014 Shorthand \u2014 sets a single solid fill (auto-binds to matching variable/style)
605
+ fillStyleName (string, optional) \u2014 Paint style name for fill
606
+ fillVariableName (string, optional) \u2014 Color variable by name e.g. 'bg/primary'
607
+ strokes (array, optional) \u2014 Stroke paints array \u2014 e.g. [{type: 'SOLID', color: '#hex'}] or [] to clear. Primary way to set strokes.
608
+ strokeColor (Color, optional) \u2014 Shorthand \u2014 sets a single solid stroke (auto-binds to matching variable/style)
609
+ strokeStyleName (string, optional) \u2014 Paint style name for stroke
610
+ strokeVariableName (string, optional) \u2014 Color variable by name for stroke
611
+ strokeWeight (string, optional) \u2014 All sides (number) or variable name (string). Per-side: strokeTopWeight, strokeBottomWeight, strokeLeftWeight, strokeRightWeight.
612
+ strokeTopWeight (string, optional)
613
+ strokeBottomWeight (string, optional)
614
+ strokeLeftWeight (string, optional)
615
+ strokeRightWeight (string, optional)
616
+ strokeAlign (INSIDE | OUTSIDE | CENTER, optional) \u2014 Stroke position (default: INSIDE)
617
+ strokesIncludedInLayout (boolean, optional) \u2014 Include stroke width in layout measurements (default: false)
618
+ cornerRadius (string, optional) \u2014 All corners (number) or variable name (string). Per-corner: topLeftRadius, topRightRadius, bottomRightRadius, bottomLeftRadius.
619
+ topLeftRadius (string, optional)
620
+ topRightRadius (string, optional)
621
+ bottomRightRadius (string, optional)
622
+ bottomLeftRadius (string, optional)
623
+ opacity (string, optional) \u2014 Opacity (0-1) or variable name
624
+ visible (boolean, optional) \u2014 Show/hide (default true)
625
+ locked (boolean, optional) \u2014 Lock/unlock (default false)
626
+ blendMode (PASS_THROUGH | NORMAL | DARKEN | MULTIPLY | LINEAR_BURN | COLOR_BURN | LIGHTEN | SCREEN | LINEAR_DODGE | COLOR_DODGE | OVERLAY | SOFT_LIGHT | HARD_LIGHT | DIFFERENCE | EXCLUSION | HUE | SATURATION | COLOR | LUMINOSITY, optional)
627
+ effectStyleName (string, optional) \u2014 Effect style name (e.g. 'Shadow/Card') for shadows, blurs
628
+ layoutMode (NONE | HORIZONTAL | VERTICAL, optional) \u2014 Layout direction (default: NONE)
629
+ layoutWrap (NO_WRAP | WRAP, optional)
630
+ padding (string, optional) \u2014 All edges (number) or variable name (string). Per-edge: paddingTop, paddingRight, paddingBottom, paddingLeft.
631
+ paddingTop (string, optional)
632
+ paddingRight (string, optional)
633
+ paddingBottom (string, optional)
634
+ paddingLeft (string, optional)
635
+ primaryAxisAlignItems (MIN | MAX | CENTER | SPACE_BETWEEN, optional)
636
+ counterAxisAlignItems (MIN | MAX | CENTER | BASELINE, optional)
637
+ itemSpacing (string, optional) \u2014 Spacing between children (number or variable name string, default: 0)
638
+ counterAxisSpacing (string, optional) \u2014 Gap between wrapped rows (requires layoutWrap: WRAP)
639
+ layoutSizingHorizontal (FIXED | HUG | FILL, optional)
640
+ layoutSizingVertical (FIXED | HUG | FILL, optional)
641
+ layoutPositioning (AUTO | ABSOLUTE, optional) \u2014 ABSOLUTE = floating inside auto-layout parent
642
+ minWidth (number, optional) \u2014 Min width for responsive auto-layout
643
+ maxWidth (number, optional) \u2014 Max width for responsive auto-layout
644
+ minHeight (number, optional) \u2014 Min height for responsive auto-layout
645
+ maxHeight (number, optional) \u2014 Max height for responsive auto-layout
646
+ fontSize (number, optional) \u2014 Font size
647
+ fontFamily (string, optional) \u2014 Font family
648
+ fontStyle (string, optional) \u2014 Font variant e.g. "Bold", "Italic" \u2014 overrides fontWeight
649
+ fontWeight (number, optional) \u2014 100-900. Ignored when fontStyle is set.
650
+ fontColor (Color, optional) \u2014 Shorthand \u2014 sets text color (auto-binds to matching variable/style)
651
+ fontColorVariableName (string, optional) \u2014 Bind color variable by name e.g. 'text/primary'
652
+ fontColorStyleName (string, optional) \u2014 Apply paint style \u2014 overrides fontColor
653
+ textStyleId (string, optional) \u2014 Apply text style by ID \u2014 overrides fontSize/fontWeight
654
+ textStyleName (string, optional) \u2014 Text style by name (case-insensitive)
655
+ textAlignHorizontal (LEFT | CENTER | RIGHT | JUSTIFIED, optional)
656
+ textAlignVertical (TOP | CENTER | BOTTOM, optional)
657
+ textAutoResize (NONE | WIDTH_AND_HEIGHT | HEIGHT | TRUNCATE, optional)
658
+ id (string, required) \u2014 Instance node ID
659
+ properties (object, optional) \u2014 Component property key\u2192value map
660
+ componentProperties (object, optional) \u2014 Alias for properties (matches instances.get response shape)
661
+ name (string, optional) \u2014 Rename node
662
+ rotation (number, optional) \u2014 Degrees (0-360)
663
+ x (number, optional)
664
+ y (number, optional)
665
+ width (number, optional)
666
+ height (number, optional)
667
+ clearFill (boolean, optional) \u2014 Remove all fills
668
+ effects (array, optional) \u2014 Effect array (DROP_SHADOW, INNER_SHADOW, LAYER_BLUR, BACKGROUND_BLUR)
669
+ constraints (object, optional)
670
+ bindings (array, optional) \u2014 Bind variables to properties. field path examples: 'fills/0/color', 'strokes/0/color', 'opacity', 'width', 'cornerRadius', 'itemSpacing'.
671
+ field (string, required)
672
+ variableName (string, optional)
673
+ variableId (string, optional)
674
+ explicitMode (object, optional) \u2014 Pin variable mode \u2014 use { collectionName, modeName } (preferred) or { collectionId, modeId }
675
+ exportSettings (array, optional) \u2014 Export settings`,
676
+ "swap": "# instances.swap\nSwap instance component (preserves overrides)\n\nParams:\n items (array, required) \u2014 Array of {id, componentId}\n id (string, required) \u2014 Instance node ID\n componentId (string, required) \u2014 New component or component set ID",
677
+ "detach": "# instances.detach\nDetach instances from their component (converts to frame)\n\nParams:\n items (array, required) \u2014 Array of {id}\n id (string, required) \u2014 Instance node ID",
678
+ "reset_overrides": "# instances.reset_overrides\nReset all overrides on instances to match their main component\n\nParams:\n items (array, required) \u2014 Array of {id}\n id (string, required) \u2014 Instance node ID"
679
+ }
680
+ },
681
+ "lint": {
682
+ "summary": `# lint
683
+ Run design quality and accessibility checks.
684
+
685
+ Methods:
686
+ check Run design linter on a node tree [read]
687
+ fix Auto-fix frames to auto-layout [edit]
688
+
689
+ // Lint runs automated design quality and accessibility checks on a node tree.
690
+ // ---
691
+ // Rules: "all" (default), or filter by category or specific rule names.
692
+ // Category meta-rules (expand to all rules in that category):
693
+ // "component" \u2014 component property binding checks
694
+ // "composition" \u2014 layout and positioning checks
695
+ // "token" \u2014 design token / style usage checks
696
+ // "naming" \u2014 layer naming checks
697
+ // "wcag" / "accessibility" \u2014 all WCAG accessibility checks
698
+ // ---
699
+ // Severity levels (output is sorted by severity, highest first):
700
+ // "error" \u2014 definite bug, must fix
701
+ // "unsafe" \u2014 likely causes layout/accessibility problems
702
+ // "heuristic" \u2014 probably worth fixing, context-dependent
703
+ // "style" \u2014 opinionated, nice-to-have (leaf nodes, decorative elements often downgraded here)
704
+ // Context-aware: leaf nodes (text, shapes, small frames) are treated differently from containers.
705
+ // Small labels with HUG sizing, leaf nodes on cross-axis \u2014 downgraded to "style" instead of "heuristic".
706
+ // Per-finding severity overrides appear on individual nodes when context changes the default.
707
+ // ---
708
+ // Component rules [component]:
709
+ // "no-text-property" \u2014 component text not exposed as editable property [heuristic]
710
+ // "component-bindings" \u2014 unbound text, orphaned properties, unexposed nested text [heuristic; orphaned\u2192unsafe, nested\u2192style]
711
+ // Composition rules [composition]:
712
+ // "no-autolayout" \u2014 frames with manually positioned children [heuristic; leaf-only containers\u2192style]
713
+ // "overlapping-children" \u2014 children stacked at same position [heuristic]
714
+ // "shape-instead-of-frame" \u2014 shapes used as containers [style]
715
+ // "fixed-in-autolayout" \u2014 FIXED-size children in auto-layout parents [heuristic]
716
+ // "unbounded-hug" \u2014 HUG on both axes [unsafe; short leaf text\u2192style]
717
+ // "hug-cross-axis" \u2014 HUG on cross-axis of constrained parent [heuristic; leaf nodes\u2192style]
718
+ // "empty-container" \u2014 empty frames [style]
719
+ // Token rules [token]:
720
+ // "hardcoded-color" \u2014 colors not using styles or variables [heuristic]
721
+ // "hardcoded-token" \u2014 numeric values not bound to FLOAT variable [heuristic]
722
+ // "no-text-style" \u2014 text without a text style [heuristic]
723
+ // Naming rules [naming]:
724
+ // "default-name" \u2014 default names like "Frame 1" [style]
725
+ // "stale-text-name" \u2014 text node name doesn't match content [style]
726
+ // Accessibility rules [wcag]:
727
+ // "wcag-contrast" \u2014 AA contrast ratio [unsafe]
728
+ // "wcag-contrast-enhanced" \u2014 AAA contrast ratio [style]
729
+ // "wcag-non-text-contrast" \u2014 non-text 3:1 contrast [heuristic]
730
+ // "wcag-target-size" \u2014 targets below 24x24px [unsafe]
731
+ // "wcag-text-size" \u2014 text below 12px [unsafe]
732
+ // "wcag-line-height" \u2014 line height below 1.5x [style]
733
+ // ---
734
+ // maxDepth limits how deep the tree traversal goes (default: 10). maxFindings caps total findings (default: 50).
735
+ // fix: auto-converts frames to auto-layout. Use after lint.check identifies "no-autolayout" issues.
736
+
737
+ Use lint(method: "help", topic: "<method>") for method details.`,
738
+ "methods": {
739
+ "check": '# lint.check\nRun design linter on a node tree\n\nExample: lint(method:"check", nodeId:"0:1", rules:["wcag","hardcoded-color"])\n\nParams:\n nodeId (string, optional) \u2014 Node ID to lint. If omitted: 1 selected node \u2192 lints that node, 2+ selected \u2192 lints entire page (not the selection), 0 selected \u2192 error. Always pass nodeId explicitly for reliable targeting.\n rules (string[], optional) \u2014 Rules to run. Default: ["all"]. Categories: "component", "composition", "token", "naming", "wcag"/"accessibility". Or specific rule names.\n maxDepth (number, optional) \u2014 Max tree depth (default: 10)\n maxFindings (number, optional) \u2014 Max findings (default: 50)',
740
+ "fix": "# lint.fix\nAuto-fix frames to auto-layout\n\nParams:\n items (array, required) \u2014 Array of {nodeId, layoutMode?, itemSpacing?}\n nodeId (string, required) \u2014 Frame node ID\n layoutMode (VERTICAL | HORIZONTAL, optional) \u2014 Direction (default: auto-detected)\n itemSpacing (number, optional) \u2014 Spacing between children\n depth (number, optional) \u2014 Response detail for fixed nodes: omit for stubs, 0=properties, -1=full tree"
741
+ }
742
+ },
743
+ "prototyping": {
744
+ "summary": '# prototyping\nManage prototype interactions, reactions, and navigation flows.\n\nMethods:\n get Get reactions and overflow direction on a node [read]\n add Add a prototype reaction to a node [edit]\n set Replace all reactions on a node (raw reactions array) [edit]\n remove Remove a reaction from a node by index [edit]\n\n// Reactions wire up interactions: trigger (ON_CLICK, ON_HOVER, ...) \u2192 action (navigate, swap, overlay).\n// Common patterns: button ON_CLICK \u2192 NAVIGATE to detail frame; card ON_HOVER \u2192 CHANGE_TO hover variant.\n// Multi-action: pass actions[] array to run multiple actions on one trigger (e.g. navigate + set variable mode).\n// ---\n// TRIGGERS: ON_CLICK | ON_HOVER | ON_PRESS | ON_DRAG | AFTER_TIMEOUT(timeout) | MOUSE_ENTER(delay) | MOUSE_LEAVE(delay) | ON_KEY_DOWN(keyCodes)\n// NAVIGATION: NAVIGATE (go to frame) | SWAP (swap overlay) | OVERLAY (show overlay) | SCROLL_TO | CHANGE_TO (swap component variant)\n// TRANSITIONS: DISSOLVE | SMART_ANIMATE | MOVE_IN | MOVE_OUT | PUSH | SLIDE_IN | SLIDE_OUT (+ direction for directional)\n// EASING: EASE_IN | EASE_OUT | EASE_IN_AND_OUT | LINEAR | GENTLE | QUICK | BOUNCY | SLOW\n// ACTIONS: NODE (navigate/swap) | BACK (go back) | CLOSE (close overlay) | URL (open link) | SET_VARIABLE_MODE (switch theme/mode)\n\nUse prototyping(method: "help", topic: "<method>") for method details.',
745
+ "methods": {
746
+ "get": "# prototyping.get\nGet reactions and overflow direction on a node\n\nParams:\n id (string, required) \u2014 Node ID",
747
+ "add": '# prototyping.add\nAdd a prototype reaction to a node\n\nExample: prototyping(method:"add", id:"btn-1", trigger:"ON_CLICK", destination:"detail-frame-id", navigation:"NAVIGATE")\n\nParams:\n id (string, required) \u2014 Node ID\n trigger (ON_CLICK | ON_HOVER | ON_PRESS | ON_DRAG | AFTER_TIMEOUT | MOUSE_ENTER | MOUSE_LEAVE | ON_KEY_DOWN, required) \u2014 Trigger type\n triggerDelay (number, optional) \u2014 Delay in ms for AFTER_TIMEOUT / MOUSE_ENTER / MOUSE_LEAVE triggers\n triggerKeyCodes (number[], optional) \u2014 Key codes for ON_KEY_DOWN trigger\n triggerDevice (KEYBOARD | XBOX_ONE | PS4 | SWITCH_PRO, optional) \u2014 Device for ON_KEY_DOWN (default: KEYBOARD)\n destination (string, optional) \u2014 Target node ID (required for NODE actions)\n navigation (NAVIGATE | SWAP | OVERLAY | SCROLL_TO | CHANGE_TO, optional) \u2014 Navigation type (default: NAVIGATE)\n transition (DISSOLVE | SMART_ANIMATE | MOVE_IN | MOVE_OUT | PUSH | SLIDE_IN | SLIDE_OUT | INSTANT, optional) \u2014 Transition animation (default: DISSOLVE). INSTANT = no animation.\n transitionDirection (LEFT | RIGHT | TOP | BOTTOM, optional) \u2014 Direction for MOVE_IN, MOVE_OUT, PUSH, SLIDE_IN, SLIDE_OUT\n duration (number, optional) \u2014 Transition duration in seconds (default: 0.3)\n easing (EASE_IN | EASE_OUT | EASE_IN_AND_OUT | LINEAR | GENTLE | QUICK | BOUNCY | SLOW, optional) \u2014 Easing function (default: EASE_OUT)\n actionType (NODE | BACK | CLOSE | URL | SET_VARIABLE_MODE, optional) \u2014 Action type (default: NODE). SET_VARIABLE_MODE switches a variable collection mode.\n url (string, optional) \u2014 URL for URL action type\n collectionName (string, optional) \u2014 Variable collection name (for SET_VARIABLE_MODE)\n modeName (string, optional) \u2014 Mode name to switch to (for SET_VARIABLE_MODE)\n resetScrollPosition (boolean, optional) \u2014 Reset scroll position on navigate (default: true)\n actions (array, optional) \u2014 Multi-action: [{actionType, destination?, navigation?, collectionName?, modeName?, ...}]. Overrides single-action params.',
748
+ "set": "# prototyping.set\nReplace all reactions on a node (raw reactions array)\n\nParams:\n id (string, required) \u2014 Node ID\n reactions (array, required) \u2014 Full reactions array \u2014 [{trigger:{type}, actions:[{type, destinationId, navigation, transition}]}]",
749
+ "remove": "# prototyping.remove\nRemove a reaction from a node by index\n\nParams:\n id (string, required) \u2014 Node ID\n index (number, required) \u2014 Reaction index (0-based)"
750
+ }
751
+ },
752
+ "selection": {
753
+ "summary": '# selection\nRead and set the current Figma selection.\n\nMethods:\n get Get the current selection [read]\n set Set selection to nodes and scroll viewport to show them [read]\n\n// Selection is the set of nodes currently highlighted in the Figma canvas.\n// get returns the current selection. Without depth, returns stubs ({id, name, type}). With depth=0, returns full properties.\n// _truncated: true when the response was cut short due to node budget limits. Use depth=0 or specific fields to reduce payload.\n// set replaces the entire selection AND scrolls the viewport to show the selected nodes.\n\nUse selection(method: "help", topic: "<method>") for method details.',
754
+ "methods": {
755
+ "get": "# selection.get\nGet the current selection\n\nParams:\n depth (number, optional) \u2014 Child recursion depth. Omit for stubs only, 0=selected nodes' properties, -1=unlimited.\n verbose (boolean, optional) \u2014 Include all properties (bounding box, constraints, text style details). Default false \u2014 returns slim, actionable output.",
756
+ "set": '# selection.set\nSet selection to nodes and scroll viewport to show them\n\nParams:\n nodeIds (string[], required) \u2014 Array of node IDs to select. Example: ["1:2","1:3"]'
757
+ }
758
+ },
759
+ "styles": {
760
+ "summary": '# styles\nCRUD for local paint, text, effect, and grid styles.\n\nMethods:\n list List local styles with optional type filter [read]\n get Get full style detail by ID [read]\n create Create local styles [create]\n update Update styles by ID or name [edit]\n delete Delete styles [edit]\n\n// Styles are named, reusable design properties that can be applied to nodes. Four types:\n// paint: a named color (applied to fills/strokes), text: typography settings, effect: shadows/blurs, grid: layout grids.\n// All ID params accept both IDs and display names (case-insensitive). Use whichever you have.\n// ---\n// leadingTrim: "CAP_HEIGHT" trims line-height to cap height (tighter text boxes), "NONE" is default.\n// fontStyle: font variant name like "Bold", "Italic", "Bold Italic". Use fonts.list to find available styles.\n//\n// Effect object shape (for effect styles):\n// { type: "DROP_SHADOW"|"INNER_SHADOW"|"LAYER_BLUR"|"BACKGROUND_BLUR",\n// color?: Color, offset?: {x, y}, radius: number, spread?: number,\n// visible?: boolean, blendMode?: string }\n// DROP_SHADOW/INNER_SHADOW require color, offset, radius. LAYER_BLUR/BACKGROUND_BLUR require radius only.\n// Example: { type: "DROP_SHADOW", color: "#00000040", offset: {x:0,y:4}, radius: 8 }\n//\n// LayoutGrid object shape (for grid styles):\n// Rows/Columns: { pattern: "ROWS"|"COLUMNS", alignment: "MIN"|"MAX"|"STRETCH"|"CENTER",\n// gutterSize: number, count: number, sectionSize?: number, offset?: number, visible?: boolean, color?: Color }\n// Grid: { pattern: "GRID", sectionSize: number, visible?: boolean, color?: Color }\n// Example: { pattern: "COLUMNS", alignment: "STRETCH", gutterSize: 20, count: 12, offset: 40 }\n\nUse styles(method: "help", topic: "<method>") for method details.',
761
+ "methods": {
762
+ "list": '# styles.list\nList local styles with optional type filter\n\nParams:\n type (paint | text | effect | grid, optional) \u2014 Filter by style type\n fields (string[], optional) \u2014 Property whitelist. Identity fields (id, name, type) always included. Omit for stubs on list, full on get. Pass ["*"] for all.\n offset (number, optional) \u2014 Skip N items for pagination (default 0)\n limit (number, optional) \u2014 Max items per page (default 100)',
763
+ "get": '# styles.get\nGet full style detail by ID\n\nParams:\n id (string, required) \u2014 Style ID or name\n fields (string[], optional) \u2014 Property whitelist. Identity fields (id, name, type) always included. Omit for stubs on list, full on get. Pass ["*"] for all.',
764
+ "create": '# styles.create\nCreate local styles\n\nExample: styles(method:"create", type:"effect", name:"Shadow/Medium", effects:[{type:"DROP_SHADOW", color:"#00000040", offset:{x:0,y:4}, radius:8}])\n\nDiscriminant: type (paint | text | effect | grid)\n\n ## paint \u2014 Paint/color style\n name (string, required) \u2014 Style name\n color (Color, required) \u2014 Color value\n colorVariableName (string, optional) \u2014 Bind to a COLOR variable by name (style tracks the variable)\n description (string, optional) \u2014 Style description\n\n ## text \u2014 Text style\n name (string, required) \u2014 Style name\n fontFamily (string, required) \u2014 Font family\n fontStyle (string, optional) \u2014 Font style (default: Regular)\n fontSize (number, required) \u2014 Font size\n lineHeight (line_height, optional)\n letterSpacing (letter_spacing, optional)\n textCase (ORIGINAL | UPPER | LOWER | TITLE | SMALL_CAPS | SMALL_CAPS_FORCED, optional)\n textDecoration (NONE | UNDERLINE | STRIKETHROUGH, optional)\n paragraphIndent (number, optional) \u2014 Paragraph indent (px)\n paragraphSpacing (number, optional) \u2014 Paragraph spacing (px)\n leadingTrim (CAP_HEIGHT | NONE, optional) \u2014 Leading trim mode\n description (string, optional) \u2014 Style description\n\n ## effect \u2014 Effect style\n name (string, required) \u2014 Style name\n effects (array, required) \u2014 Array of Effect objects\n description (string, optional) \u2014 Style description\n\n ## grid \u2014 Grid/layout grid style\n name (string, required) \u2014 Style name\n layoutGrids (array, required) \u2014 Array of LayoutGrid objects\n description (string, optional) \u2014 Style description',
765
+ "update": '# styles.update\nUpdate styles by ID or name\n\nExample: styles(method:"update", items:[{id:"Surface/Primary", color:"#F5F5F5"}])\n\nParams:\n type (paint | text | effect | grid, optional) \u2014 Style type hint for strict validation (optional, auto-detected)\n items (PatchStyleItem[], required) \u2014 Array of {id, ...fields} to update\n id (string, required) \u2014 Style ID or name\n name (string, optional) \u2014 Rename the style\n description (string, optional) \u2014 Style description\n color (Color, optional) \u2014 New color (paint styles)\n colorVariableName (string, optional) \u2014 Bind to a COLOR variable by name (paint styles)\n fontFamily (string, optional)\n fontStyle (string, optional)\n fontSize (number, optional)\n lineHeight (line_height, optional)\n letterSpacing (letter_spacing, optional)\n textCase (ORIGINAL | UPPER | LOWER | TITLE | SMALL_CAPS | SMALL_CAPS_FORCED, optional)\n textDecoration (NONE | UNDERLINE | STRIKETHROUGH, optional)\n paragraphIndent (number, optional) \u2014 Paragraph indent (px)\n paragraphSpacing (number, optional) \u2014 Paragraph spacing (px)\n leadingTrim (CAP_HEIGHT | NONE, optional)\n effects (array, optional) \u2014 Array of Effect objects\n layoutGrids (array, optional) \u2014 Array of LayoutGrid objects (grid styles)',
766
+ "delete": "# styles.delete\nDelete styles\n\nParams:\n id (string, optional) \u2014 Style ID or name\n items (array, optional) \u2014 Batch: [{id}, ...]\n id (string, required) \u2014 Style ID or name"
767
+ }
768
+ },
769
+ "text": {
770
+ "summary": '# text\nCreate and manage text nodes.\n\nMethods:\n get Get serialized node data [read]\n list Search for nodes (returns stubs only \u2014 use get with depth for full properties) [read]\n update Patch node properties [edit]\n delete Delete nodes [edit]\n clone Duplicate nodes [create]\n audit Run lint on a node \u2014 returns severity-ranked findings [read]\n reparent Move nodes into a new parent [edit]\n create Create text nodes [create]\n set_content Replace text content on existing text nodes [edit]\n scan Scan all text nodes within a subtree [read]\n\n// depth: omit \u2192 id+name stubs | 0 \u2192 props + child stubs | N \u2192 recurse N | -1 \u2192 full tree\n// fields: whitelist e.g. ["fills","opacity"] \u2014 id, name, type always included. Pass ["*"] for all.\n// layoutSizingHorizontal/Vertical: FIXED | HUG | FILL \u2014 how the node sizes within auto-layout.\n// Colors: fillVariableName/strokeVariableName bind by name \u2014 preferred over raw color values.\n// Note: node-based endpoints (frames, text, instances, components) use `results` as the list key.\n// Standalone endpoints (styles, variables, variable_collections) use `items`. Components.list uses `items` (catalog view).\n// ---\n// visible: false hides the node. Omitted from response when true (the default).\n// locked: true prevents editing in Figma UI. Omitted when false (the default).\n// rotation: degrees (0-360). Omitted when 0.\n// blendMode: layer blend mode. Omitted when PASS_THROUGH (the default).\n// layoutPositioning: ABSOLUTE = floating inside auto-layout parent. Omitted when AUTO (the default).\n// minWidth/maxWidth/minHeight/maxHeight: responsive constraints for auto-layout children.\n// constraints: position behavior in non-auto-layout parents. Ignored inside auto-layout frames.\n// bindings: bind design variables to node properties. field uses slash path: "fills/0/color" (first fill), "strokes/0/color", "opacity", "width", "height", "cornerRadius", "paddingLeft", "itemSpacing".\n// explicitMode: pin a variable mode on this node. Use { collectionName, modeName } (preferred) or { collectionId, modeId }.\n// properties: escape hatch \u2014 set any Figma node property directly. Use only when no dedicated field exists.\ninterface Node {\n id: string; name: string; type: string;\n visible?: boolean; // omitted when true\n locked?: boolean; // omitted when false\n opacity?: number; // omitted when 1\n rotation?: number; // omitted when 0\n blendMode?: string; // omitted when PASS_THROUGH\n layoutPositioning?: "AUTO" | "ABSOLUTE";\n layoutSizingHorizontal?: "FIXED" | "HUG" | "FILL";\n layoutSizingVertical?: "FIXED" | "HUG" | "FILL";\n minWidth?: number; maxWidth?: number; minHeight?: number; maxHeight?: number;\n absoluteBoundingBox: { x: number; y: number; width: number; height: number };\n fills?: Paint[]; // solid: {type: "SOLID", color: {r, g, b, a}}\n strokes?: Paint[];\n effects?: Effect[]; // DROP_SHADOW | INNER_SHADOW | LAYER_BLUR | BACKGROUND_BLUR\n children?: NodeStub[]; // stubs: {id, name, type} \u2014 use depth to expand\n}\n// PatchItem uses flat params matching create shape \u2014 no nested sub-objects.\n// Fill/stroke/corner/layout/text params are identical to frames.create and text.create.\n// Unknown keys produce a warning, preventing silent failures.\n// Text nodes display text content with typography styling. They inherit node methods (get, list, update, delete, clone, reparent).\n// textAutoResize: NONE (fixed box), WIDTH_AND_HEIGHT (grow both), HEIGHT (fixed width, auto height), TRUNCATE (fixed + ellipsis).\n// Prefer textStyleName for typography, fontColorVariableName/fontColorStyleName for color.\n// Aliases: fillColor \u2192 fontColor, fillVariableName \u2192 fontColorVariableName, fillStyleName \u2192 fontColorStyleName (consistent with frames API).\n// ---\n// Smart defaults inside auto-layout parent: layoutSizingHorizontal defaults to FILL, layoutSizingVertical to HUG, textAutoResize to HEIGHT.\n// Text fills parent width and wraps automatically. Override with explicit values if needed.\n// fontStyle vs fontWeight: fontStyle is a named variant like "Bold", "Italic", "SemiBold". When set, fontWeight is ignored.\n// Use fonts.list to find available fontFamily + fontStyle combinations.\n// scan: finds all text nodes in a subtree. path (when includePath:true) shows the layer hierarchy e.g. "Frame > Card > Label".\n// ScanResult (per-item): { nodeId, count, truncated, textNodes: [{ id, name, characters, fontSize, fontFamily, fontStyle, path?, depth?, absoluteX?, absoluteY?, width?, height? }] }\n\nUse text(method: "help", topic: "<method>") for method details.',
771
+ "methods": {
772
+ "get": '# text.get\nGet serialized node data\n\nParams:\n id (string, required) \u2014 Node ID\n fields (string[], optional) \u2014 Property whitelist. Identity fields (id, name, type) always included. Omit for stubs on list, full on get. Pass ["*"] for all.\n depth (number, optional) \u2014 Response detail: omit for id+name only. 0=properties + child stubs. N=recurse N levels. -1=unlimited.\n verbose (boolean, optional) \u2014 Include all properties (bounding box, constraints, text style details). Default false \u2014 returns slim, actionable output.',
773
+ "list": '# text.list\nSearch for nodes (returns stubs only \u2014 use get with depth for full properties)\n\nParams:\n query (string, optional) \u2014 Name search query (case-insensitive substring match)\n types (string[], optional) \u2014 Filter by node types (e.g. ["FRAME", "TEXT"])\n parentId (string, optional) \u2014 Search only within this subtree\n fields (string[], optional) \u2014 Property whitelist. Identity fields (id, name, type) always included. Omit for stubs on list, full on get. Pass ["*"] for all.\n offset (number, optional) \u2014 Skip N items for pagination (default 0)\n limit (number, optional) \u2014 Max items per page (default 100)',
774
+ "update": `# text.update
775
+ Patch node properties
776
+
777
+ Params:
778
+ items (PatchItem[], required) \u2014 Array of {id, ...properties} to patch
779
+ fills (array, optional) \u2014 Fill paints array \u2014 e.g. [{type: 'SOLID', color: '#hex'}] or [] for transparent. Primary way to set fills.
780
+ fillColor (Color, optional) \u2014 Shorthand \u2014 sets a single solid fill (auto-binds to matching variable/style)
781
+ fillStyleName (string, optional) \u2014 Paint style name for fill
782
+ fillVariableName (string, optional) \u2014 Color variable by name e.g. 'bg/primary'
783
+ strokes (array, optional) \u2014 Stroke paints array \u2014 e.g. [{type: 'SOLID', color: '#hex'}] or [] to clear. Primary way to set strokes.
784
+ strokeColor (Color, optional) \u2014 Shorthand \u2014 sets a single solid stroke (auto-binds to matching variable/style)
785
+ strokeStyleName (string, optional) \u2014 Paint style name for stroke
786
+ strokeVariableName (string, optional) \u2014 Color variable by name for stroke
787
+ strokeWeight (string, optional) \u2014 All sides (number) or variable name (string). Per-side: strokeTopWeight, strokeBottomWeight, strokeLeftWeight, strokeRightWeight.
788
+ strokeTopWeight (string, optional)
789
+ strokeBottomWeight (string, optional)
790
+ strokeLeftWeight (string, optional)
791
+ strokeRightWeight (string, optional)
792
+ strokeAlign (INSIDE | OUTSIDE | CENTER, optional) \u2014 Stroke position (default: INSIDE)
793
+ strokesIncludedInLayout (boolean, optional) \u2014 Include stroke width in layout measurements (default: false)
794
+ cornerRadius (string, optional) \u2014 All corners (number) or variable name (string). Per-corner: topLeftRadius, topRightRadius, bottomRightRadius, bottomLeftRadius.
795
+ topLeftRadius (string, optional)
796
+ topRightRadius (string, optional)
797
+ bottomRightRadius (string, optional)
798
+ bottomLeftRadius (string, optional)
799
+ opacity (string, optional) \u2014 Opacity (0-1) or variable name
800
+ visible (boolean, optional) \u2014 Show/hide (default true)
801
+ locked (boolean, optional) \u2014 Lock/unlock (default false)
802
+ blendMode (PASS_THROUGH | NORMAL | DARKEN | MULTIPLY | LINEAR_BURN | COLOR_BURN | LIGHTEN | SCREEN | LINEAR_DODGE | COLOR_DODGE | OVERLAY | SOFT_LIGHT | HARD_LIGHT | DIFFERENCE | EXCLUSION | HUE | SATURATION | COLOR | LUMINOSITY, optional)
803
+ effectStyleName (string, optional) \u2014 Effect style name (e.g. 'Shadow/Card') for shadows, blurs
804
+ layoutMode (NONE | HORIZONTAL | VERTICAL, optional) \u2014 Layout direction (default: NONE)
805
+ layoutWrap (NO_WRAP | WRAP, optional)
806
+ padding (string, optional) \u2014 All edges (number) or variable name (string). Per-edge: paddingTop, paddingRight, paddingBottom, paddingLeft.
807
+ paddingTop (string, optional)
808
+ paddingRight (string, optional)
809
+ paddingBottom (string, optional)
810
+ paddingLeft (string, optional)
811
+ primaryAxisAlignItems (MIN | MAX | CENTER | SPACE_BETWEEN, optional)
812
+ counterAxisAlignItems (MIN | MAX | CENTER | BASELINE, optional)
813
+ itemSpacing (string, optional) \u2014 Spacing between children (number or variable name string, default: 0)
814
+ counterAxisSpacing (string, optional) \u2014 Gap between wrapped rows (requires layoutWrap: WRAP)
815
+ layoutSizingHorizontal (FIXED | HUG | FILL, optional)
816
+ layoutSizingVertical (FIXED | HUG | FILL, optional)
817
+ layoutPositioning (AUTO | ABSOLUTE, optional) \u2014 ABSOLUTE = floating inside auto-layout parent
818
+ minWidth (number, optional) \u2014 Min width for responsive auto-layout
819
+ maxWidth (number, optional) \u2014 Max width for responsive auto-layout
820
+ minHeight (number, optional) \u2014 Min height for responsive auto-layout
821
+ maxHeight (number, optional) \u2014 Max height for responsive auto-layout
822
+ fontSize (number, optional) \u2014 Font size
823
+ fontFamily (string, optional) \u2014 Font family
824
+ fontStyle (string, optional) \u2014 Font variant e.g. "Bold", "Italic" \u2014 overrides fontWeight
825
+ fontWeight (number, optional) \u2014 100-900. Ignored when fontStyle is set.
826
+ fontColor (Color, optional) \u2014 Shorthand \u2014 sets text color (auto-binds to matching variable/style)
827
+ fontColorVariableName (string, optional) \u2014 Bind color variable by name e.g. 'text/primary'
828
+ fontColorStyleName (string, optional) \u2014 Apply paint style \u2014 overrides fontColor
829
+ textStyleId (string, optional) \u2014 Apply text style by ID \u2014 overrides fontSize/fontWeight
830
+ textStyleName (string, optional) \u2014 Text style by name (case-insensitive)
831
+ textAlignHorizontal (LEFT | CENTER | RIGHT | JUSTIFIED, optional)
832
+ textAlignVertical (TOP | CENTER | BOTTOM, optional)
833
+ textAutoResize (NONE | WIDTH_AND_HEIGHT | HEIGHT | TRUNCATE, optional)
834
+ id (string, required)
835
+ name (string, optional) \u2014 Rename node
836
+ rotation (number, optional) \u2014 Degrees (0-360)
837
+ x (number, optional)
838
+ y (number, optional)
839
+ width (number, optional)
840
+ height (number, optional)
841
+ clearFill (boolean, optional) \u2014 Remove all fills
842
+ effects (array, optional) \u2014 Effect array (DROP_SHADOW, INNER_SHADOW, LAYER_BLUR, BACKGROUND_BLUR)
843
+ overflowDirection (NONE | HORIZONTAL | VERTICAL | BOTH, optional) \u2014 Scroll overflow in prototype (default: NONE)
844
+ constraints (object, optional)
845
+ bindings (array, optional) \u2014 Bind variables to properties. field path examples: 'fills/0/color', 'strokes/0/color', 'opacity', 'width', 'cornerRadius', 'itemSpacing'.
846
+ field (string, required)
847
+ variableName (string, optional)
848
+ variableId (string, optional)
849
+ explicitMode (object, optional) \u2014 Pin variable mode \u2014 use { collectionName, modeName } (preferred) or { collectionId, modeId }
850
+ exportSettings (array, optional) \u2014 Export settings
851
+ properties (object, optional) \u2014 Direct Figma API props (escape hatch)`,
852
+ "delete": "# text.delete\nDelete nodes\n\nParams:\n id (string, optional) \u2014 Single node ID\n items (array, optional) \u2014 Batch: [{id}, ...]\n id (string, optional)",
853
+ "clone": "# text.clone\nDuplicate nodes\n\nParams:\n id (string, required) \u2014 Node ID\n parentId (string, optional) \u2014 Parent node ID. Omit to place on current page.\n x (number, optional) \u2014 X position (default: 0)\n y (number, optional) \u2014 Y position (default: 0)\n depth (number, optional) \u2014 Response detail: omit for id+name only. 0=properties + child stubs. N=recurse N levels. -1=unlimited.",
854
+ "audit": '# text.audit\nRun lint on a node \u2014 returns severity-ranked findings\n\nParams:\n id (string, required) \u2014 Node ID to audit\n rules (string[], optional) \u2014 Rules to check. Default: ["all"]. Categories: "component", "composition", "token", "naming", "wcag".\n maxDepth (number, optional) \u2014 Max tree depth (default: 10)\n maxFindings (number, optional) \u2014 Max findings (default: 50)',
855
+ "reparent": "# text.reparent\nMove nodes into a new parent\n\nParams:\n items (array, required) \u2014 Array of {id, parentId, index?}\n id (string, required)\n parentId (string, required)\n index (number, optional)",
856
+ "create": `# text.create
857
+ Create text nodes
858
+
859
+ Example: text(method:"create", items:[{text:"Hello", fontFamily:"Inter", fontSize:16, textStyleName:"body/medium"}])
860
+
861
+ Params:
862
+ items (TextItem[], required) \u2014 Array of text items to create
863
+ text (string, optional) \u2014 Text content
864
+ name (string, optional) \u2014 Layer name
865
+ x (number, optional)
866
+ y (number, optional)
867
+ width (number, optional) \u2014 Fixed width in px \u2014 implies layoutSizingHorizontal: FIXED and textAutoResize: HEIGHT
868
+ parentId (string, optional) \u2014 Parent node ID. Omit to place on current page.
869
+ fontFamily (string, optional) \u2014 Font family (default: Inter). Use fonts.list to find installed fonts.
870
+ fontStyle (string, optional) \u2014 Font variant e.g. "Bold", "Italic" \u2014 overrides fontWeight
871
+ fontSize (number, optional) \u2014 Font size (default: 14)
872
+ fontWeight (number, optional) \u2014 100-900 (default: 400). Ignored when fontStyle is set.
873
+ fills (array, optional) \u2014 Text color paints \u2014 e.g. [{type: 'SOLID', color: '#hex'}]. Same as node fills.
874
+ fontColor (Color, optional) \u2014 Shorthand \u2014 sets text color (auto-binds to matching variable/style)
875
+ fontColorVariableName (string, optional) \u2014 Bind color variable by name e.g. 'text/primary'
876
+ fontColorStyleName (string, optional) \u2014 Apply paint style \u2014 overrides fontColor
877
+ textStyleId (string, optional) \u2014 Text style ID or name (case-insensitive) \u2014 overrides fontSize/fontWeight
878
+ textStyleName (string, optional) \u2014 Alias for textStyleId \u2014 accepts name (case-insensitive)
879
+ textAlignHorizontal (LEFT | CENTER | RIGHT | JUSTIFIED, optional)
880
+ textAlignVertical (TOP | CENTER | BOTTOM, optional)
881
+ layoutSizingHorizontal (FIXED | HUG | FILL, optional)
882
+ layoutSizingVertical (FIXED | HUG | FILL, optional)
883
+ textAutoResize (NONE | WIDTH_AND_HEIGHT | HEIGHT | TRUNCATE, optional) \u2014 NONE (fixed box), WIDTH_AND_HEIGHT (grow both), HEIGHT (fixed width, auto height), TRUNCATE (fixed + ellipsis)
884
+ componentPropertyName (string, optional) \u2014 Bind to a component TEXT property by name. Walks up ancestors to find the nearest component, or targets the component specified by componentId. For deeply nested text, consider using components(method:'create', type:'from_node') with exposeText:true instead \u2014 it auto-discovers and binds all text nodes.
885
+ componentId (string, optional) \u2014 Target component ID for componentPropertyName binding. When omitted, walks up ancestors to find the nearest COMPONENT or COMPONENT_SET.
886
+ depth (number, optional) \u2014 Response detail: omit for id+name only. 0=properties + child stubs. N=recurse N levels. -1=unlimited.`,
887
+ "set_content": "# text.set_content\nReplace text content on existing text nodes\n\nParams:\n items (array, required) \u2014 Array of {nodeId, text}\n nodeId (string, required) \u2014 Text node ID\n text (string, required) \u2014 New text content\n depth (number, optional) \u2014 Response detail: omit for id+name only. 0=properties + child stubs. N=recurse N levels. -1=unlimited.",
888
+ "scan": "# text.scan\nScan all text nodes within a subtree\n\nParams:\n items ({ nodeId: string; limit?: number; includePath?: boolean; includeGeometry?: boolean }[], required) \u2014 Array of subtrees to scan"
889
+ }
890
+ },
891
+ "variable_collections": {
892
+ "summary": '# variable_collections\nCRUD for variable collections \u2014 the document-level API for design tokens.\n\nMethods:\n list List variable collections [read]\n get Get collection with all variables and values (full document) [read]\n create Create a collection with modes and variables in one call [create]\n update Rename collections [edit]\n delete Delete collections [edit]\n add_mode Add a mode to a collection [create]\n rename_mode Rename a mode [edit]\n remove_mode Remove a mode from a collection [edit]\n\n// Variable collections group design tokens and define their modes (e.g. Light/Dark, Desktop/Mobile).\n// All ID params accept both IDs and display names.\n// ---\n// valuesByMode: values keyed by mode name, e.g. {"Light": "#FFF", "Dark": "#111"}.\n// Aliases: {type: "VARIABLE_ALIAS", name: "other/variable"}.\n// scopes: see variables endpoint for full list.\n// Deleting a collection deletes all variables inside it.\n// The default mode cannot be removed. Use add_mode/remove_mode for additional modes.\n\nUse variable_collections(method: "help", topic: "<method>") for method details.',
893
+ "methods": {
894
+ "list": '# variable_collections.list\nList variable collections\n\nParams:\n fields (string[], optional) \u2014 Property whitelist. Identity fields (id, name, type) always included. Omit for stubs on list, full on get. Pass ["*"] for all.\n offset (number, optional) \u2014 Skip N items for pagination (default 0)\n limit (number, optional) \u2014 Max items per page (default 100)',
895
+ "get": '# variable_collections.get\nGet collection with all variables and values (full document)\n\nParams:\n id (string, required) \u2014 Collection ID or name\n fields (string[], optional) \u2014 Property whitelist. Identity fields (id, name, type) always included. Omit for stubs on list, full on get. Pass ["*"] for all.',
896
+ "create": `# variable_collections.create
897
+ Create a collection with modes and variables in one call
898
+
899
+ Example: variable_collections(method:"create", items:[{name:"Tokens", modes:["Light","Dark"], variables:[{name:"bg/primary", type:"COLOR", valuesByMode:{"Light":"#FFF","Dark":"#111"}, scopes:["ALL_FILLS"]}, {name:"text/primary", type:"COLOR", valuesByMode:{"Light":"#111","Dark":"#F0F0F0"}, scopes:["TEXT_FILL"]}, {name:"space/16", type:"FLOAT", value:16, scopes:["GAP","WIDTH_HEIGHT"]}, {name:"radius/8", type:"FLOAT", value:8, scopes:["CORNER_RADIUS"]}]}])
900
+
901
+ Params:
902
+ items (array, required) \u2014 Array of collection documents
903
+ name (string, required) \u2014 Collection name
904
+ modes (string[], optional) \u2014 Mode names (e.g. ['Light', 'Dark']). Omit for single-mode collection.
905
+ variables (array, optional) \u2014 Variables to create inside this collection
906
+ name (string, required) \u2014 Variable name (unique within collection)
907
+ type (COLOR | FLOAT | STRING | BOOLEAN, required) \u2014 Variable type
908
+ value (variable_value, optional) \u2014 Shorthand \u2014 sets the default mode value. Use valuesByMode for multi-mode.
909
+ valuesByMode (object, optional) \u2014 Values keyed by mode name (e.g. {"Light": "#FFF", "Dark": "#111"})
910
+ description (string, optional)
911
+ scopes (string[], optional) \u2014 Restrict where variable can be applied (default: ALL_SCOPES)`,
912
+ "update": "# variable_collections.update\nRename collections\n\nParams:\n items (array, required) \u2014 Array of {id, name}\n id (string, required) \u2014 Collection ID or name\n name (string, required) \u2014 New name",
913
+ "delete": "# variable_collections.delete\nDelete collections\n\nParams:\n id (string, optional) \u2014 Collection ID or name\n items (array, optional) \u2014 Batch: [{id}, ...]\n id (string, required)",
914
+ "add_mode": "# variable_collections.add_mode\nAdd a mode to a collection\n\nParams:\n items (array, required) \u2014 Array of {collectionId, name}\n collectionId (string, required) \u2014 Collection ID or name\n name (string, required) \u2014 Mode name",
915
+ "rename_mode": '# variable_collections.rename_mode\nRename a mode\n\nParams:\n items (array, required) \u2014 Array of {collectionId, modeId, name}\n collectionId (string, required) \u2014 Collection ID or name\n modeId (string, required) \u2014 Mode ID or name (e.g. "Dark")\n name (string, required) \u2014 New name',
916
+ "remove_mode": '# variable_collections.remove_mode\nRemove a mode from a collection\n\nParams:\n items (array, required) \u2014 Array of {collectionId, modeId}\n collectionId (string, required) \u2014 Collection ID or name\n modeId (string, required) \u2014 Mode ID or name (e.g. "Dark")'
917
+ }
918
+ },
919
+ "variables": {
920
+ "summary": '# variables\nSearch and update design variables within a collection.\n\nMethods:\n list Search variables within a collection [read]\n get Get variable detail by name [read]\n create Create variables in a collection. Use valuesByMode for multi-mode, or value for default mode only. [create]\n update Update variable metadata and/or set values [edit]\n delete Delete variables [edit]\n\n// Search and update variables within a collection. collectionId is required on all methods.\n// Use variable_collections to create full token sets (collection + modes + variables in one call).\n// ---\n// query: prefix match first, then substring. "bg/" matches bg/canvas, bg/surface, etc.\n// valuesByMode: values keyed by mode name. value is shorthand for the default mode.\n// Aliases: {type: "VARIABLE_ALIAS", name: "other/variable"}.\n// scopes: ALL_SCOPES, TEXT_CONTENT, WIDTH_HEIGHT, GAP, CORNER_RADIUS, ALL_FILLS, FRAME_FILL, SHAPE_FILL,\n// TEXT_FILL, STROKE_COLOR, STROKE_FLOAT, EFFECT_FLOAT, EFFECT_COLOR, OPACITY, FONT_FAMILY, FONT_STYLE,\n// FONT_WEIGHT, FONT_SIZE, LINE_HEIGHT, LETTER_SPACING, PARAGRAPH_SPACING, PARAGRAPH_INDENT\n\nUse variables(method: "help", topic: "<method>") for method details.',
921
+ "methods": {
922
+ "list": '# variables.list\nSearch variables within a collection\n\nExample: variables(method:"list", collectionId:"Colors", query:"bg/")\n\nParams:\n collectionId (string, required) \u2014 Collection ID or name\n query (string, optional) \u2014 Search query \u2014 prefix match first, then substring fallback\n type (COLOR | FLOAT | STRING | BOOLEAN, optional) \u2014 Filter by variable type\n fields (string[], optional) \u2014 Property whitelist. Identity fields (id, name, type) always included. Omit for stubs on list, full on get. Pass ["*"] for all.\n offset (number, optional) \u2014 Skip N items for pagination (default 0)\n limit (number, optional) \u2014 Max items per page (default 100)',
923
+ "get": '# variables.get\nGet variable detail by name\n\nParams:\n name (string, required) \u2014 Variable name (unique within collection)\n collectionId (string, required) \u2014 Collection ID or name\n fields (string[], optional) \u2014 Property whitelist. Identity fields (id, name, type) always included. Omit for stubs on list, full on get. Pass ["*"] for all.',
924
+ "create": '# variables.create\nCreate variables in a collection. Use valuesByMode for multi-mode, or value for default mode only.\n\nExample: variables(method:"create", collectionId:"Colors", items:[{name:"bg/primary", type:"COLOR", valuesByMode:{"Light":"#FFF","Dark":"#111"}, scopes:["ALL_FILLS"]}])\n\nParams:\n collectionId (string, required) \u2014 Collection ID or name\n items (VariableCreateItem[], required) \u2014 Array of variables to create\n name (string, required) \u2014 Variable name (must be unique within collection)\n type (COLOR | FLOAT | STRING | BOOLEAN, required) \u2014 Variable type\n value (variable_value, optional) \u2014 Shorthand \u2014 sets the default mode value. Use valuesByMode for multi-mode.\n valuesByMode (object, optional) \u2014 Values keyed by mode name (e.g. {"Light": "#FFF", "Dark": "#111"}). Takes precedence over value.\n description (string, optional) \u2014 Variable description\n scopes (string[], optional) \u2014 Restrict where variable can be applied (default: ALL_SCOPES)',
925
+ "update": '# variables.update\nUpdate variable metadata and/or set values\n\nExample: variables(method:"update", collectionId:"Colors", items:[{name:"bg/primary", valuesByMode:{"Light":"#FFF","Dark":"#222"}}])\n\nParams:\n collectionId (string, required) \u2014 Collection ID or name\n items (VariableUpdateItem[], required) \u2014 Array of variable updates\n name (string, required) \u2014 Variable name\n rename (string, optional) \u2014 Rename the variable\n description (string, optional) \u2014 Set description\n scopes (string[], optional) \u2014 Update scopes\n value (variable_value, optional) \u2014 Shorthand \u2014 sets the default mode value. Use valuesByMode for multi-mode.\n valuesByMode (object, optional) \u2014 Values keyed by mode name. Takes precedence over value.',
926
+ "delete": "# variables.delete\nDelete variables\n\nParams:\n collectionId (string, required) \u2014 Collection ID or name\n name (string, optional) \u2014 Variable name\n items (array, optional) \u2014 Batch: [{name}, ...]\n name (string, required)"
927
+ }
928
+ },
929
+ "version_history": {
930
+ "summary": `# version_history
931
+ Save named versions to the Figma file's version history.
932
+
933
+ Methods:
934
+ save Save a named version to the file's version history [edit]
935
+
936
+ // Version history lets you create named snapshots of a Figma file.
937
+ // Use this after completing design tasks to create an audit trail of changes.
938
+ // Equivalent to Figma's File \u2192 Save to Version History (Cmd+Opt+S).
939
+
940
+ Use version_history(method: "help", topic: "<method>") for method details.`,
941
+ "methods": {
942
+ "save": `# version_history.save
943
+ Save a named version to the file's version history
944
+
945
+ Params:
946
+ title (string, required) \u2014 Version title (e.g., "Added hero sections with website copy")
947
+ description (string, optional) \u2014 Optional longer description of what changed`
948
+ }
949
+ }
950
+ };
951
+ var helpTopics = {
952
+ "missing_tools": '# Missing Create / Edit Tools\n\nIf the user asks you to create or modify something in Figma but you cannot find create/edit methods on endpoint tools, the MCP server was started without the correct access tier flag.\n\nVibma filters available methods at startup based on CLI flags passed in the MCP config `args` array:\n\n| Flag | Methods available |\n|------|-----------------|\n| _(none)_ | Read-only (get, list, check, scan, export) |\n| `--create` | Read + creation methods (create, clone) |\n| `--edit` | All methods (read + create + update + delete) |\n\nAsk the user to add `--edit` (or `--create`) to their MCP config args:\n\n```json\n{\n "mcpServers": {\n "Vibma": {\n "command": "npx",\n "args": ["-y", "@ufira/vibma", "--edit"]\n }\n }\n}\n```\n\nAfter updating, the user must restart their AI tool or reload MCP servers \u2014 stdio-based servers cannot hot-reload.'
953
+ };
954
+ var allEndpointNames = Object.keys(helpEndpoints);
955
+ var allTopicNames = Object.keys(helpTopics);
956
+ function resolveHelp(topic) {
957
+ if (!topic) return helpDirectory;
958
+ const dot = topic.indexOf(".");
959
+ if (dot === -1) {
960
+ const ep2 = helpEndpoints[topic];
961
+ if (ep2) return ep2.summary;
962
+ const t = helpTopics[topic];
963
+ if (t) return t;
964
+ const all = [...allEndpointNames, ...allTopicNames];
965
+ return "Unknown topic: " + topic + ". Available: " + all.join(", ");
966
+ }
967
+ const epName = topic.slice(0, dot);
968
+ const methodName = topic.slice(dot + 1);
969
+ const ep = helpEndpoints[epName];
970
+ if (!ep) {
971
+ const all = [...allEndpointNames, ...allTopicNames];
972
+ return "Unknown topic: " + epName + ". Available: " + all.join(", ");
973
+ }
974
+ const method = ep.methods[methodName];
975
+ if (method) return method;
976
+ return "Unknown method: " + methodName + " on " + epName + ". Available methods: " + Object.keys(ep.methods).join(", ");
977
+ }
978
+ function resolveEndpointHelp(endpoint, topic) {
979
+ const ep = helpEndpoints[endpoint];
980
+ if (!ep) return null;
981
+ if (!topic) return ep.summary;
982
+ const method = ep.methods[topic];
983
+ if (method) return method;
984
+ return "Unknown method: " + topic + ". Available methods on " + endpoint + ": " + Object.keys(ep.methods).join(", ");
985
+ }
986
+
58
987
  // src/tools/registry.ts
59
- function registerTools(server2, sendCommand, caps2, tools16) {
60
- for (const tool of tools16) {
988
+ function resolveCommand(tool, params) {
989
+ if (tool.commandMap && params.method) {
990
+ const cmd = tool.commandMap[params.method];
991
+ if (cmd) return cmd;
992
+ }
993
+ return tool.command ?? tool.name;
994
+ }
995
+ function registerTools(server2, sendCommand, caps2, tools2) {
996
+ for (const tool of tools2) {
61
997
  if (tool.tier === "create" && !caps2.create) continue;
62
998
  if (tool.tier === "edit" && !caps2.edit) continue;
63
999
  const schema = typeof tool.schema === "function" ? tool.schema(caps2) : tool.schema;
64
- const command = tool.command ?? tool.name;
65
1000
  const timeout = tool.timeout;
66
- const format = tool.formatResponse ?? mcpJson;
1001
+ const defaultFormat = tool.formatResponse ?? mcpJson;
67
1002
  server2.registerTool(tool.name, { description: tool.description, inputSchema: schema }, async (params) => {
68
1003
  try {
1004
+ if (params.method === "help") {
1005
+ const text = resolveEndpointHelp(tool.name, params.topic) ?? resolveHelp(tool.name);
1006
+ return { content: [{ type: "text", text }] };
1007
+ }
69
1008
  if (tool.validate) tool.validate(params);
1009
+ const command = resolveCommand(tool, params);
70
1010
  const result = await sendCommand(command, params, timeout);
1011
+ const format = tool.methodFormatters?.[params.method] ?? defaultFormat;
71
1012
  return format(result);
72
1013
  } catch (e) {
1014
+ if (e instanceof import_zod.ZodError) {
1015
+ const hints = e.issues.map((i) => {
1016
+ const path = i.path.join(".");
1017
+ return `[${path}] ${i.message}`;
1018
+ });
1019
+ return mcpError(`${tool.name} validation error`, hints.join("; "));
1020
+ }
73
1021
  return mcpError(`${tool.name} error`, e);
74
1022
  }
75
1023
  });
76
1024
  }
77
1025
  }
78
1026
 
79
- // src/tools/defs/connection.ts
80
- var tools = [
81
- {
82
- name: "ping",
83
- description: "Verify end-to-end connection to Figma. Call this right after join_channel. Returns { status: 'pong', documentName, currentPage } if the full chain (MCP \u2192 relay \u2192 plugin \u2192 Figma) is working. If this times out, the Figma plugin is not connected \u2014 ask the user to check the plugin window for the correct port and channel name.",
84
- schema: {},
85
- tier: "read",
86
- timeout: 5e3
87
- }
88
- ];
89
-
90
- // src/tools/defs/document.ts
91
- var import_zod = require("zod");
92
- var tools2 = [
93
- {
94
- name: "get_document_info",
95
- description: "Get the document name, current page, and list of all pages.",
96
- schema: {},
97
- tier: "read"
98
- },
99
- {
100
- name: "get_current_page",
101
- description: "Get the current page info and its top-level children.",
102
- schema: {},
103
- tier: "read"
104
- },
105
- {
106
- name: "set_current_page",
107
- description: "Switch to a different page. Provide either pageId or pageName.",
108
- schema: {
109
- pageId: import_zod.z.string().optional().describe("The page ID to switch to"),
110
- pageName: import_zod.z.string().optional().describe("The page name (case-insensitive, partial match)")
111
- },
112
- tier: "read"
113
- },
114
- {
115
- name: "create_page",
116
- description: "Create a new page in the document",
117
- schema: { name: import_zod.z.string().optional().describe("Name for the new page (default: 'New Page')") },
118
- tier: "create"
119
- },
120
- {
121
- name: "rename_page",
122
- description: "Rename a page. Defaults to current page if no pageId given.",
123
- schema: {
124
- newName: import_zod.z.string().describe("New name for the page"),
125
- pageId: import_zod.z.string().optional().describe("Page ID (default: current page)")
126
- },
127
- tier: "edit"
128
- }
129
- ];
130
-
131
- // src/tools/defs/selection.ts
132
- var import_zod3 = require("zod");
1027
+ // src/tools/generated/defs.ts
1028
+ var import_zod4 = require("zod");
133
1029
 
134
1030
  // src/utils/coercion.ts
135
1031
  var import_zod2 = require("zod");
@@ -148,87 +1044,15 @@ var flexJson = (inner) => import_zod2.z.preprocess((v) => {
148
1044
  }
149
1045
  return v;
150
1046
  }, inner);
151
- var flexNum = (inner) => import_zod2.z.preprocess((v) => {
152
- if (typeof v === "string") {
153
- const n = Number(v);
154
- if (!isNaN(n) && v.trim() !== "") return n;
155
- }
156
- return v;
157
- }, inner);
158
-
159
- // src/tools/defs/selection.ts
160
- var tools3 = [
161
- {
162
- name: "get_selection",
163
- description: "Get the current selection. Without depth, returns stubs (id/name/type). With depth, returns full serialized node trees.",
164
- schema: { depth: import_zod3.z.coerce.number().optional().describe("Child recursion depth. Omit for stubs only, 0=selected nodes' properties, -1=unlimited.") },
165
- tier: "read"
166
- },
167
- {
168
- name: "set_selection",
169
- description: "Set selection to nodes and scroll viewport to show them. Also works as focus (single node).",
170
- schema: {
171
- nodeIds: flexJson(import_zod3.z.array(import_zod3.z.string())).describe('Array of node IDs to select. Example: ["1:2","1:3"]')
172
- },
173
- tier: "read"
174
- }
175
- ];
176
-
177
- // src/tools/defs/node-info.ts
178
- var import_zod4 = require("zod");
179
- var tools4 = [
180
- {
181
- name: "get_node_info",
182
- description: "Get detailed information about one or more nodes. Always pass an array of IDs. Use `fields` to select only the properties you need (reduces context size).",
183
- schema: {
184
- nodeIds: flexJson(import_zod4.z.array(import_zod4.z.string())).describe('Array of node IDs. Example: ["1:2","1:3"]'),
185
- depth: import_zod4.z.coerce.number().optional().describe("Child recursion depth (default: unlimited). 0=stubs only."),
186
- fields: flexJson(import_zod4.z.array(import_zod4.z.string())).optional().describe('Whitelist of property names to include. Example: ["absoluteBoundingBox","layoutMode","fills"]. Omit to return all properties.')
187
- },
188
- tier: "read"
189
- },
190
- {
191
- name: "search_nodes",
192
- description: "Search nodes on the current page by name and/or type. Use set_current_page first to search other pages. Paginated (default 50).",
193
- schema: {
194
- query: import_zod4.z.string().optional().describe("Name search (case-insensitive substring). Omit to match all names."),
195
- types: flexJson(import_zod4.z.array(import_zod4.z.string())).optional().describe('Filter by types. Example: ["FRAME","TEXT"]. Omit to match all types.'),
196
- scopeNodeId: import_zod4.z.string().optional().describe("Node ID to search within (defaults to current page)"),
197
- caseSensitive: flexBool(import_zod4.z.boolean()).optional().describe("Case-sensitive name match (default false)"),
198
- limit: import_zod4.z.coerce.number().optional().describe("Max results (default 50)"),
199
- offset: import_zod4.z.coerce.number().optional().describe("Skip N results for pagination (default 0)")
200
- },
201
- tier: "read"
202
- },
203
- {
204
- name: "export_node_as_image",
205
- description: "Export a node as an image from Figma",
206
- schema: {
207
- nodeId: import_zod4.z.string().describe("The node ID to export"),
208
- format: import_zod4.z.enum(["PNG", "JPG", "SVG", "PDF"]).optional().describe("Export format (default: PNG)"),
209
- scale: import_zod4.z.coerce.number().positive().optional().describe("Export scale (default: 1)")
210
- },
211
- tier: "read",
212
- formatResponse: (result) => {
213
- const r = result;
214
- return {
215
- content: [{ type: "image", data: r.imageData, mimeType: r.mimeType || "image/png" }]
216
- };
217
- }
218
- }
219
- ];
220
-
221
- // src/tools/defs/create-shape.ts
222
- var import_zod6 = require("zod");
223
1047
 
224
1048
  // src/tools/schemas.ts
225
- var import_zod5 = require("zod");
226
- var nodeId = import_zod5.z.string().describe("Node ID");
227
- var nodeIds = flexJson(import_zod5.z.array(import_zod5.z.string())).describe("Array of node IDs");
228
- var parentId = import_zod5.z.string().optional().describe("Parent node ID. Omit to place on current page.");
229
- var depth = import_zod5.z.coerce.number().optional().describe("Response detail: omit for id+name only. 0=properties + child stubs. N=recurse N levels. -1=unlimited.");
230
- var xPos = import_zod5.z.coerce.number().optional().describe("X position (default: 0)");
231
- var yPos = import_zod5.z.coerce.number().optional().describe("Y position (default: 0)");
1049
+ var import_zod3 = require("zod");
1050
+ var nodeId = import_zod3.z.string().describe("Node ID");
1051
+ var nodeIds = flexJson(import_zod3.z.array(import_zod3.z.string())).describe("Array of node IDs");
1052
+ var parentId = import_zod3.z.string().optional().describe("Parent node ID. Omit to place on current page.");
1053
+ var depth = import_zod3.z.coerce.number().optional().describe("Response detail: omit for id+name only. 0=properties + child stubs. N=recurse N levels. -1=unlimited.");
1054
+ var xPos = import_zod3.z.coerce.number().optional().describe("X position (default: 0)");
1055
+ var yPos = import_zod3.z.coerce.number().optional().describe("Y position (default: 0)");
232
1056
  function parseHex(hex) {
233
1057
  const m = hex.match(/^#?([0-9a-f]{3,8})$/i);
234
1058
  if (!m) return null;
@@ -242,765 +1066,1433 @@ function parseHex(hex) {
242
1066
  if (h.length === 8) return { r, g, b, a: parseInt(h.slice(6, 8), 16) / 255 };
243
1067
  return { r, g, b };
244
1068
  }
245
- var colorRgba = import_zod5.z.preprocess((v) => {
1069
+ var colorRgba = import_zod3.z.preprocess((v) => {
1070
+ if (typeof v === "string") return parseHex(v) ?? v;
1071
+ return v;
1072
+ }, import_zod3.z.union([
1073
+ import_zod3.z.object({
1074
+ r: import_zod3.z.coerce.number().min(0).max(1),
1075
+ g: import_zod3.z.coerce.number().min(0).max(1),
1076
+ b: import_zod3.z.coerce.number().min(0).max(1),
1077
+ a: import_zod3.z.coerce.number().min(0).max(1).optional()
1078
+ }),
1079
+ import_zod3.z.string()
1080
+ // Non-hex strings pass through for handler-level style/variable resolution
1081
+ ])).describe('Hex "#FF0000", {r,g,b,a?} 0-1, or style/variable name.');
1082
+ var variableValue = import_zod3.z.preprocess((v) => {
246
1083
  if (typeof v === "string") return parseHex(v) ?? v;
247
1084
  return v;
248
- }, import_zod5.z.object({
249
- r: import_zod5.z.coerce.number().min(0).max(1),
250
- g: import_zod5.z.coerce.number().min(0).max(1),
251
- b: import_zod5.z.coerce.number().min(0).max(1),
252
- a: import_zod5.z.coerce.number().min(0).max(1).optional()
253
- })).describe('Hex "#FF0000" or {r,g,b,a?} with values 0-1.');
254
- var effectEntry = import_zod5.z.object({
255
- type: import_zod5.z.enum(["DROP_SHADOW", "INNER_SHADOW", "LAYER_BLUR", "BACKGROUND_BLUR"]),
1085
+ }, import_zod3.z.union([
1086
+ import_zod3.z.number(),
1087
+ import_zod3.z.boolean(),
1088
+ import_zod3.z.string(),
1089
+ import_zod3.z.object({ r: import_zod3.z.number(), g: import_zod3.z.number(), b: import_zod3.z.number(), a: import_zod3.z.number().optional() }),
1090
+ import_zod3.z.object({ type: import_zod3.z.literal("VARIABLE_ALIAS"), name: import_zod3.z.string() })
1091
+ ])).describe('number, boolean, string, hex "#FF0000", {r,g,b,a?}, or {type:"VARIABLE_ALIAS",name:"other/variable"}');
1092
+ var lineHeight = import_zod3.z.union([
1093
+ import_zod3.z.coerce.number(),
1094
+ import_zod3.z.object({ value: import_zod3.z.coerce.number(), unit: import_zod3.z.enum(["PIXELS", "PERCENT", "AUTO"]) })
1095
+ ]).describe('number (px) or {value, unit: "PIXELS"|"PERCENT"|"AUTO"}');
1096
+ var letterSpacing = import_zod3.z.union([
1097
+ import_zod3.z.coerce.number(),
1098
+ import_zod3.z.object({ value: import_zod3.z.coerce.number(), unit: import_zod3.z.enum(["PIXELS", "PERCENT"]) })
1099
+ ]).describe('number (px) or {value, unit: "PIXELS"|"PERCENT"}');
1100
+ var stringOrBoolean = import_zod3.z.union([import_zod3.z.string(), import_zod3.z.boolean()]);
1101
+ var token = import_zod3.z.preprocess((v) => {
1102
+ if (typeof v === "number") return String(v);
1103
+ return v;
1104
+ }, import_zod3.z.string()).describe('number as string ("8") or variable name ("Radii/Medium")');
1105
+ var effectEntry = import_zod3.z.object({
1106
+ type: import_zod3.z.enum(["DROP_SHADOW", "INNER_SHADOW", "LAYER_BLUR", "BACKGROUND_BLUR"]),
256
1107
  color: flexJson(colorRgba).optional(),
257
- offset: flexJson(import_zod5.z.object({ x: import_zod5.z.coerce.number(), y: import_zod5.z.coerce.number() })).optional(),
258
- radius: import_zod5.z.coerce.number(),
259
- spread: import_zod5.z.coerce.number().optional(),
260
- visible: flexBool(import_zod5.z.boolean()).optional(),
261
- blendMode: import_zod5.z.string().optional()
1108
+ offset: flexJson(import_zod3.z.object({ x: import_zod3.z.coerce.number(), y: import_zod3.z.coerce.number() })).optional(),
1109
+ radius: import_zod3.z.coerce.number(),
1110
+ spread: import_zod3.z.coerce.number().optional(),
1111
+ visible: flexBool(import_zod3.z.boolean()).optional(),
1112
+ blendMode: import_zod3.z.string().optional()
262
1113
  });
263
1114
 
264
- // src/tools/defs/create-shape.ts
265
- var sectionItem = import_zod6.z.object({
266
- name: import_zod6.z.string().optional().describe("Name (default: 'Section')"),
267
- x: xPos,
268
- y: yPos,
269
- width: import_zod6.z.coerce.number().optional().describe("Width (default: 500)"),
270
- height: import_zod6.z.coerce.number().optional().describe("Height (default: 500)"),
271
- parentId,
272
- fillColor: flexJson(colorRgba).optional().describe("Fill color. Default: no fill (transparent)."),
273
- fillStyleName: import_zod6.z.string().optional().describe("Apply a fill paint style by name (case-insensitive)."),
274
- fillVariableId: import_zod6.z.string().optional().describe("Bind a color variable to the fill.")
275
- });
276
- var svgItem = import_zod6.z.object({
277
- svg: import_zod6.z.string().describe("SVG markup string"),
278
- name: import_zod6.z.string().optional().describe("Layer name (default: 'SVG')"),
279
- x: xPos,
280
- y: yPos,
281
- parentId
282
- });
283
- var tools5 = [
1115
+ // src/tools/generated/defs.ts
1116
+ function filterMethodsByTier(schema, caps2, methodTiers) {
1117
+ const methods = Object.keys(methodTiers).filter((m) => {
1118
+ const tier = methodTiers[m];
1119
+ if (tier === "read") return true;
1120
+ if (tier === "create") return caps2.create;
1121
+ if (tier === "edit") return caps2.edit;
1122
+ return false;
1123
+ });
1124
+ return { ...schema, method: import_zod4.z.enum(methods) };
1125
+ }
1126
+ var tools = [
284
1127
  {
285
- name: "create_section",
286
- description: "Create section nodes to organize content on the canvas. Default: no fill (transparent).",
287
- schema: { items: flexJson(import_zod6.z.array(sectionItem)).describe("Array of sections to create"), depth },
288
- tier: "create"
1128
+ name: "components",
1129
+ description: '/** Create and manage reusable components and variant sets. Use method "help" for detailed parameter docs. */\n clone (id, parentId?, x?, y?, depth?) \u2192 { results: {id}[] } // Duplicate nodes\n audit (id, rules?, maxDepth?, maxFindings?) \u2192 { nodeId?, nodeName?, categories? } // Run lint on a node \u2014 returns severity-ranked findings\n reparent (items: { id: string; parentId: string; index?: number }[]) \u2192 { results: "ok"[] } // Move nodes into a new parent\n list (query?, offset?, limit?) \u2192 { totalCount, items } // List local component names (variant sets as single entries)\n get (id?, names?) \u2192 { results } // Get component detail \u2014 property definitions + ID for instances.create\n create (type: component|from_node|variant_set, items: (ComponentItem | FromNodeItem | VariantSetItem)[]) \u2192 { results: {id}[] } // Create components\n update (items: UpdatePropertyItem[], depth?) \u2192 { results: ("ok" | {error})[] } // Add, edit, or delete component properties\n delete (id) \u2192 { results: "ok"[] } // Delete components or component sets\n// depth: omit \u2192 id+name stubs | 0 \u2192 props + child stubs | N \u2192 recurse N | -1 \u2192 full tree\n// fields: whitelist e.g. ["fills","opacity"] \u2014 id, name, type always included. Pass ["*"] for all.\n// layoutSizingHorizontal/Vertical: FIXED | HUG | FILL \u2014 how the node sizes within auto-layout.\n// Colors: fillVariableName/strokeVariableName bind by name \u2014 preferred over raw color values.\n// Note: node-based endpoints (frames, text, instances, components) use `results` as the list key.\n// Standalone endpoints (styles, variables, variable_collections) use `items`. Components.list uses `items` (catalog view).',
1130
+ schema: (caps2) => filterMethodsByTier({
1131
+ method: import_zod4.z.enum(["clone", "audit", "reparent", "list", "get", "create", "update", "delete", "help"]),
1132
+ id: import_zod4.z.string().optional().describe("Node ID"),
1133
+ parentId: import_zod4.z.string().optional().describe("Parent node ID. Omit to place on current page."),
1134
+ x: import_zod4.z.coerce.number().optional().describe("X position (default: 0)"),
1135
+ y: import_zod4.z.coerce.number().optional().describe("Y position (default: 0)"),
1136
+ depth: import_zod4.z.coerce.number().optional().describe("Response detail: omit for id+name only. 0=properties + child stubs. N=recurse N levels. -1=unlimited."),
1137
+ rules: flexJson(import_zod4.z.array(import_zod4.z.string())).optional().describe('Rules to check. Default: ["all"]. Categories: "component", "composition", "token", "naming", "wcag".'),
1138
+ maxDepth: import_zod4.z.coerce.number().optional().describe("Max tree depth (default: 10)"),
1139
+ maxFindings: import_zod4.z.coerce.number().optional().describe("Max findings (default: 50)"),
1140
+ items: flexJson(import_zod4.z.array(import_zod4.z.record(import_zod4.z.string(), import_zod4.z.unknown()))).optional().describe("Array of {id, ...properties} to reparent/update"),
1141
+ query: import_zod4.z.string().optional().describe("Name search query (case-insensitive substring match)"),
1142
+ offset: import_zod4.z.coerce.number().optional().default(0).describe("Skip N items for pagination (default 0)"),
1143
+ limit: import_zod4.z.coerce.number().optional().default(100).describe("Max items per page (default 100)"),
1144
+ names: flexJson(import_zod4.z.array(import_zod4.z.string())).optional().describe("Batch lookup by name (case-insensitive). Either id or names is required."),
1145
+ type: import_zod4.z.enum(["component", "from_node", "variant_set"]).optional().describe("Discriminant for create method"),
1146
+ topic: import_zod4.z.string().optional().describe('Help topic \u2014 method name for endpoint help, e.g. "create"')
1147
+ }, caps2, { "clone": "create", "audit": "read", "reparent": "edit", "list": "read", "get": "read", "create": "create", "update": "edit", "delete": "edit", "help": "read" }),
1148
+ tier: "read",
1149
+ validate: (params) => {
1150
+ const m = params.method;
1151
+ if (m === "delete") {
1152
+ if (params.id === void 0) throw new Error('delete requires "id"');
1153
+ }
1154
+ if (!params.items) return;
1155
+ if (m === "create") {
1156
+ if (params.items) {
1157
+ if (params.type === "variant_set") for (const it of params.items) {
1158
+ if (it.nodeIds !== void 0 && it.componentIds === void 0) {
1159
+ it.componentIds = it.nodeIds;
1160
+ delete it.nodeIds;
1161
+ }
1162
+ }
1163
+ }
1164
+ const schemas = {
1165
+ "component": import_zod4.z.object({
1166
+ name: import_zod4.z.string().describe("Component name"),
1167
+ parentId: import_zod4.z.string().optional().describe("Parent node ID. Omit to place on current page."),
1168
+ x: import_zod4.z.coerce.number().optional().describe("X position (default: 0)"),
1169
+ y: import_zod4.z.coerce.number().optional().describe("Y position (default: 0)"),
1170
+ width: import_zod4.z.coerce.number().optional().describe("Width (default: 100)"),
1171
+ height: import_zod4.z.coerce.number().optional().describe("Height (default: 100)"),
1172
+ rotation: import_zod4.z.coerce.number().optional().describe("Rotation in degrees (0-360)"),
1173
+ opacity: token.optional().describe("Opacity (0-1) or variable name"),
1174
+ visible: flexBool(import_zod4.z.boolean()).optional().describe("Show/hide (default true)"),
1175
+ locked: flexBool(import_zod4.z.boolean()).optional().describe("Lock/unlock (default false)"),
1176
+ blendMode: import_zod4.z.enum(["PASS_THROUGH", "NORMAL", "DARKEN", "MULTIPLY", "LINEAR_BURN", "COLOR_BURN", "LIGHTEN", "SCREEN", "LINEAR_DODGE", "COLOR_DODGE", "OVERLAY", "SOFT_LIGHT", "HARD_LIGHT", "DIFFERENCE", "EXCLUSION", "HUE", "SATURATION", "COLOR", "LUMINOSITY"]).optional(),
1177
+ layoutPositioning: import_zod4.z.enum(["AUTO", "ABSOLUTE"]).optional().describe("ABSOLUTE = floating inside auto-layout parent"),
1178
+ fills: import_zod4.z.array(import_zod4.z.record(import_zod4.z.string(), import_zod4.z.unknown())).optional().describe("Fill paints array \u2014 e.g. [{type: 'SOLID', color: '#hex'}] or [] for transparent. Primary way to set fills."),
1179
+ fillColor: colorRgba.optional().describe("Shorthand \u2014 sets a single solid fill (auto-binds to matching variable/style)"),
1180
+ fillStyleName: import_zod4.z.string().optional().describe("Paint style name for fill"),
1181
+ fillVariableName: import_zod4.z.string().optional().describe("Color variable by name e.g. 'bg/primary'"),
1182
+ strokes: import_zod4.z.array(import_zod4.z.record(import_zod4.z.string(), import_zod4.z.unknown())).optional().describe("Stroke paints array \u2014 e.g. [{type: 'SOLID', color: '#hex'}] or [] to clear. Primary way to set strokes."),
1183
+ strokeColor: colorRgba.optional().describe("Shorthand \u2014 sets a single solid stroke (auto-binds to matching variable/style)"),
1184
+ strokeStyleName: import_zod4.z.string().optional().describe("Paint style name for stroke"),
1185
+ strokeVariableName: import_zod4.z.string().optional().describe("Color variable by name for stroke"),
1186
+ strokeWeight: token.optional().describe("All sides (number) or variable name (string). Per-side: strokeTopWeight, strokeBottomWeight, strokeLeftWeight, strokeRightWeight."),
1187
+ strokeTopWeight: token.optional(),
1188
+ strokeBottomWeight: token.optional(),
1189
+ strokeLeftWeight: token.optional(),
1190
+ strokeRightWeight: token.optional(),
1191
+ strokeAlign: import_zod4.z.enum(["INSIDE", "OUTSIDE", "CENTER"]).optional().describe("Stroke position (default: INSIDE)"),
1192
+ strokesIncludedInLayout: flexBool(import_zod4.z.boolean()).optional().describe("Include stroke width in layout measurements (default: false)"),
1193
+ cornerRadius: token.optional().describe("All corners (number) or variable name (string). Per-corner: topLeftRadius, topRightRadius, bottomRightRadius, bottomLeftRadius."),
1194
+ topLeftRadius: token.optional(),
1195
+ topRightRadius: token.optional(),
1196
+ bottomRightRadius: token.optional(),
1197
+ bottomLeftRadius: token.optional(),
1198
+ effectStyleName: import_zod4.z.string().optional().describe("Effect style name (e.g. 'Shadow/Card') for shadows, blurs"),
1199
+ layoutMode: import_zod4.z.enum(["NONE", "HORIZONTAL", "VERTICAL"]).optional().describe("Layout direction (default: NONE)"),
1200
+ layoutWrap: import_zod4.z.enum(["NO_WRAP", "WRAP"]).optional(),
1201
+ padding: token.optional().describe("All edges (number) or variable name (string). Per-edge: paddingTop, paddingRight, paddingBottom, paddingLeft."),
1202
+ paddingTop: token.optional(),
1203
+ paddingRight: token.optional(),
1204
+ paddingBottom: token.optional(),
1205
+ paddingLeft: token.optional(),
1206
+ primaryAxisAlignItems: import_zod4.z.enum(["MIN", "MAX", "CENTER", "SPACE_BETWEEN"]).optional(),
1207
+ counterAxisAlignItems: import_zod4.z.enum(["MIN", "MAX", "CENTER", "BASELINE"]).optional(),
1208
+ layoutSizingHorizontal: import_zod4.z.enum(["FIXED", "HUG", "FILL"]).optional(),
1209
+ layoutSizingVertical: import_zod4.z.enum(["FIXED", "HUG", "FILL"]).optional(),
1210
+ itemSpacing: token.optional().describe("Spacing between children (number or variable name string, default: 0)"),
1211
+ counterAxisSpacing: token.optional().describe("Gap between wrapped rows (requires layoutWrap: WRAP)"),
1212
+ minWidth: import_zod4.z.coerce.number().optional().describe("Min width for responsive auto-layout"),
1213
+ maxWidth: import_zod4.z.coerce.number().optional().describe("Max width for responsive auto-layout"),
1214
+ minHeight: import_zod4.z.coerce.number().optional().describe("Min height for responsive auto-layout"),
1215
+ maxHeight: import_zod4.z.coerce.number().optional().describe("Max height for responsive auto-layout"),
1216
+ overflowDirection: import_zod4.z.enum(["NONE", "HORIZONTAL", "VERTICAL", "BOTH"]).optional().describe("Scroll overflow in prototype (default: NONE)"),
1217
+ description: import_zod4.z.string().optional().describe("Component description (shown in Figma's component panel)"),
1218
+ children: flexJson(import_zod4.z.array(import_zod4.z.record(import_zod4.z.string(), import_zod4.z.unknown()))).optional().describe('Inline child nodes. Text: {type:"text", text, componentPropertyName?, fontFamily?, fontSize?, fontColor?}. Frame: {type:"frame", name?, layoutMode?, fillColor?, children?}. Text with componentPropertyName auto-creates and binds a TEXT property \u2014 no need to add it to properties separately.'),
1219
+ properties: flexJson(import_zod4.z.array(import_zod4.z.record(import_zod4.z.string(), import_zod4.z.unknown()))).optional().describe("Component properties to define at creation: [{propertyName, type, defaultValue}]. TEXT properties for inline children with componentPropertyName are created automatically.")
1220
+ }).passthrough(),
1221
+ "from_node": import_zod4.z.object({
1222
+ nodeId: import_zod4.z.string().describe("Node ID to convert"),
1223
+ exposeText: flexBool(import_zod4.z.boolean()).optional().describe("Auto-expose text as editable properties (default: true)")
1224
+ }).passthrough(),
1225
+ "variant_set": import_zod4.z.object({
1226
+ name: import_zod4.z.string().optional().describe("Node name"),
1227
+ parentId: import_zod4.z.string().optional().describe("Parent node ID. Omit to place on current page."),
1228
+ x: import_zod4.z.coerce.number().optional().describe("X position (default: 0)"),
1229
+ y: import_zod4.z.coerce.number().optional().describe("Y position (default: 0)"),
1230
+ width: import_zod4.z.coerce.number().optional().describe("Width (default: 100)"),
1231
+ height: import_zod4.z.coerce.number().optional().describe("Height (default: 100)"),
1232
+ rotation: import_zod4.z.coerce.number().optional().describe("Rotation in degrees (0-360)"),
1233
+ opacity: token.optional().describe("Opacity (0-1) or variable name"),
1234
+ visible: flexBool(import_zod4.z.boolean()).optional().describe("Show/hide (default true)"),
1235
+ locked: flexBool(import_zod4.z.boolean()).optional().describe("Lock/unlock (default false)"),
1236
+ blendMode: import_zod4.z.enum(["PASS_THROUGH", "NORMAL", "DARKEN", "MULTIPLY", "LINEAR_BURN", "COLOR_BURN", "LIGHTEN", "SCREEN", "LINEAR_DODGE", "COLOR_DODGE", "OVERLAY", "SOFT_LIGHT", "HARD_LIGHT", "DIFFERENCE", "EXCLUSION", "HUE", "SATURATION", "COLOR", "LUMINOSITY"]).optional(),
1237
+ layoutPositioning: import_zod4.z.enum(["AUTO", "ABSOLUTE"]).optional().describe("ABSOLUTE = floating inside auto-layout parent"),
1238
+ fills: import_zod4.z.array(import_zod4.z.record(import_zod4.z.string(), import_zod4.z.unknown())).optional().describe("Fill paints array \u2014 e.g. [{type: 'SOLID', color: '#hex'}] or [] for transparent. Primary way to set fills."),
1239
+ fillColor: colorRgba.optional().describe("Shorthand \u2014 sets a single solid fill (auto-binds to matching variable/style)"),
1240
+ fillStyleName: import_zod4.z.string().optional().describe("Paint style name for fill"),
1241
+ fillVariableName: import_zod4.z.string().optional().describe("Color variable by name e.g. 'bg/primary'"),
1242
+ strokes: import_zod4.z.array(import_zod4.z.record(import_zod4.z.string(), import_zod4.z.unknown())).optional().describe("Stroke paints array \u2014 e.g. [{type: 'SOLID', color: '#hex'}] or [] to clear. Primary way to set strokes."),
1243
+ strokeColor: colorRgba.optional().describe("Shorthand \u2014 sets a single solid stroke (auto-binds to matching variable/style)"),
1244
+ strokeStyleName: import_zod4.z.string().optional().describe("Paint style name for stroke"),
1245
+ strokeVariableName: import_zod4.z.string().optional().describe("Color variable by name for stroke"),
1246
+ strokeWeight: token.optional().describe("All sides (number) or variable name (string). Per-side: strokeTopWeight, strokeBottomWeight, strokeLeftWeight, strokeRightWeight."),
1247
+ strokeTopWeight: token.optional(),
1248
+ strokeBottomWeight: token.optional(),
1249
+ strokeLeftWeight: token.optional(),
1250
+ strokeRightWeight: token.optional(),
1251
+ strokeAlign: import_zod4.z.enum(["INSIDE", "OUTSIDE", "CENTER"]).optional().describe("Stroke position (default: INSIDE)"),
1252
+ strokesIncludedInLayout: flexBool(import_zod4.z.boolean()).optional().describe("Include stroke width in layout measurements (default: false)"),
1253
+ cornerRadius: token.optional().describe("All corners (number) or variable name (string). Per-corner: topLeftRadius, topRightRadius, bottomRightRadius, bottomLeftRadius."),
1254
+ topLeftRadius: token.optional(),
1255
+ topRightRadius: token.optional(),
1256
+ bottomRightRadius: token.optional(),
1257
+ bottomLeftRadius: token.optional(),
1258
+ effectStyleName: import_zod4.z.string().optional().describe("Effect style name (e.g. 'Shadow/Card') for shadows, blurs"),
1259
+ layoutMode: import_zod4.z.enum(["NONE", "HORIZONTAL", "VERTICAL"]).optional().describe("Layout direction (default: NONE)"),
1260
+ layoutWrap: import_zod4.z.enum(["NO_WRAP", "WRAP"]).optional(),
1261
+ padding: token.optional().describe("All edges (number) or variable name (string). Per-edge: paddingTop, paddingRight, paddingBottom, paddingLeft."),
1262
+ paddingTop: token.optional(),
1263
+ paddingRight: token.optional(),
1264
+ paddingBottom: token.optional(),
1265
+ paddingLeft: token.optional(),
1266
+ primaryAxisAlignItems: import_zod4.z.enum(["MIN", "MAX", "CENTER", "SPACE_BETWEEN"]).optional(),
1267
+ counterAxisAlignItems: import_zod4.z.enum(["MIN", "MAX", "CENTER", "BASELINE"]).optional(),
1268
+ layoutSizingHorizontal: import_zod4.z.enum(["FIXED", "HUG", "FILL"]).optional(),
1269
+ layoutSizingVertical: import_zod4.z.enum(["FIXED", "HUG", "FILL"]).optional(),
1270
+ itemSpacing: token.optional().describe("Spacing between children (number or variable name string, default: 0)"),
1271
+ counterAxisSpacing: token.optional().describe("Gap between wrapped rows (requires layoutWrap: WRAP)"),
1272
+ minWidth: import_zod4.z.coerce.number().optional().describe("Min width for responsive auto-layout"),
1273
+ maxWidth: import_zod4.z.coerce.number().optional().describe("Max width for responsive auto-layout"),
1274
+ minHeight: import_zod4.z.coerce.number().optional().describe("Min height for responsive auto-layout"),
1275
+ maxHeight: import_zod4.z.coerce.number().optional().describe("Max height for responsive auto-layout"),
1276
+ overflowDirection: import_zod4.z.enum(["NONE", "HORIZONTAL", "VERTICAL", "BOTH"]).optional().describe("Scroll overflow in prototype (default: NONE)"),
1277
+ componentIds: flexJson(import_zod4.z.array(import_zod4.z.string())).describe("Component IDs to combine (min 2)"),
1278
+ variantPropertyName: import_zod4.z.string().optional().describe("Rename the auto-generated variant property (default: 'Property 1')")
1279
+ }).passthrough()
1280
+ };
1281
+ const s = params.type && schemas[params.type];
1282
+ if (s) {
1283
+ try {
1284
+ params.items = import_zod4.z.array(s).parse(params.items);
1285
+ } catch (e) {
1286
+ if (e instanceof import_zod4.z.ZodError) {
1287
+ throw new Error(e.issues.map((i) => {
1288
+ const path = i.path.join(".");
1289
+ const shape = s instanceof import_zod4.z.ZodObject ? s.shape : null;
1290
+ const desc = shape?.[i.path[1]]?.description;
1291
+ return path + ": " + i.message + (desc ? " (expected: " + desc + ")" : "");
1292
+ }).join("; "));
1293
+ }
1294
+ throw e;
1295
+ }
1296
+ }
1297
+ }
1298
+ if (m === "update") {
1299
+ const itemSchema = import_zod4.z.object({
1300
+ id: import_zod4.z.string().describe("Component or component set ID"),
1301
+ propertyName: import_zod4.z.string().describe('Property name with #suffix for edit/delete (e.g. "Label#1:0"). Call components.get to find exact keys. For add, plain name works.'),
1302
+ action: import_zod4.z.enum(["add", "edit", "delete", "rename_variant"]).optional().describe('"add" (default): requires type + defaultValue. "edit": pass defaultValue to change default, name to rename property. "delete": just propertyName. "rename_variant": pass defaultValue=current option name, name=new option name.'),
1303
+ type: import_zod4.z.enum(["BOOLEAN", "TEXT", "INSTANCE_SWAP", "VARIANT"]).optional().describe("Property type (required for add)"),
1304
+ defaultValue: stringOrBoolean.optional().describe("Default value (add/edit). For rename_variant: the CURRENT option name to rename"),
1305
+ name: import_zod4.z.string().optional().describe("New name \u2014 for edit: renames the property itself, for rename_variant: the new option value name"),
1306
+ preferredValues: flexJson(import_zod4.z.array(import_zod4.z.record(import_zod4.z.string(), import_zod4.z.unknown()))).optional().describe("Preferred values for INSTANCE_SWAP")
1307
+ }).passthrough();
1308
+ try {
1309
+ params.items = import_zod4.z.array(itemSchema).parse(params.items);
1310
+ } catch (e) {
1311
+ if (e instanceof import_zod4.z.ZodError) {
1312
+ throw new Error(e.issues.map((i) => {
1313
+ const path = i.path.join(".");
1314
+ const shape = itemSchema instanceof import_zod4.z.ZodObject ? itemSchema.shape : null;
1315
+ const desc = shape?.[i.path[1]]?.description;
1316
+ return path + ": " + i.message + (desc ? " (expected: " + desc + ")" : "");
1317
+ }).join("; "));
1318
+ }
1319
+ throw e;
1320
+ }
1321
+ }
1322
+ },
1323
+ commandMap: { "clone": "components.clone", "audit": "components.audit", "reparent": "components.reparent", "list": "components.list", "get": "components.get", "create": "components.create", "update": "components.update", "delete": "components.delete" }
289
1324
  },
290
1325
  {
291
- name: "create_node_from_svg",
292
- description: "Create nodes from SVG strings.",
293
- schema: { items: flexJson(import_zod6.z.array(svgItem)).describe("Array of SVG items to create"), depth },
294
- tier: "create"
295
- }
296
- ];
297
-
298
- // src/tools/defs/create-frame.ts
299
- var import_zod7 = require("zod");
300
- var frameItem = import_zod7.z.object({
301
- name: import_zod7.z.string().optional().describe("Frame name (default: 'Frame')"),
302
- x: xPos,
303
- y: yPos,
304
- width: import_zod7.z.coerce.number().optional().describe("Width (default: 100)"),
305
- height: import_zod7.z.coerce.number().optional().describe("Height (default: 100)"),
306
- parentId,
307
- fillColor: flexJson(colorRgba).optional().describe("Fill color. Default: no fill."),
308
- strokeColor: flexJson(colorRgba).optional().describe("Stroke color. Default: none."),
309
- strokeWeight: import_zod7.z.coerce.number().positive().optional().describe("Stroke weight (default: 1)"),
310
- cornerRadius: import_zod7.z.coerce.number().min(0).optional().describe("Corner radius (default: 0)"),
311
- layoutMode: import_zod7.z.enum(["NONE", "HORIZONTAL", "VERTICAL"]).optional().describe("Auto-layout direction (default: NONE)"),
312
- layoutWrap: import_zod7.z.enum(["NO_WRAP", "WRAP"]).optional().describe("Wrap (default: NO_WRAP)"),
313
- paddingTop: import_zod7.z.coerce.number().optional().describe("Top padding (default: 0)"),
314
- paddingRight: import_zod7.z.coerce.number().optional().describe("Right padding (default: 0)"),
315
- paddingBottom: import_zod7.z.coerce.number().optional().describe("Bottom padding (default: 0)"),
316
- paddingLeft: import_zod7.z.coerce.number().optional().describe("Left padding (default: 0)"),
317
- primaryAxisAlignItems: import_zod7.z.enum(["MIN", "MAX", "CENTER", "SPACE_BETWEEN"]).optional(),
318
- counterAxisAlignItems: import_zod7.z.enum(["MIN", "MAX", "CENTER", "BASELINE"]).optional(),
319
- layoutSizingHorizontal: import_zod7.z.enum(["FIXED", "HUG", "FILL"]).optional(),
320
- layoutSizingVertical: import_zod7.z.enum(["FIXED", "HUG", "FILL"]).optional(),
321
- itemSpacing: import_zod7.z.coerce.number().optional().describe("Spacing between children (default: 0)"),
322
- fillStyleName: import_zod7.z.string().optional().describe("Apply a fill paint style by name (case-insensitive). Omit to skip."),
323
- strokeStyleName: import_zod7.z.string().optional().describe("Apply a stroke paint style by name. Omit to skip."),
324
- fillVariableId: import_zod7.z.string().optional().describe("Bind a color variable to the fill. Creates a solid fill and binds the variable to fills/0/color."),
325
- strokeVariableId: import_zod7.z.string().optional().describe("Bind a color variable to the stroke. Creates a solid stroke and binds the variable to strokes/0/color.")
326
- });
327
- var autoLayoutItem = import_zod7.z.object({
328
- nodeIds: flexJson(import_zod7.z.array(import_zod7.z.string())).describe("Array of node IDs to wrap"),
329
- name: import_zod7.z.string().optional().describe("Frame name (default: 'Auto Layout')"),
330
- layoutMode: import_zod7.z.enum(["HORIZONTAL", "VERTICAL"]).optional().describe("Direction (default: VERTICAL)"),
331
- itemSpacing: import_zod7.z.coerce.number().optional().describe("Spacing between children (default: 0)"),
332
- paddingTop: import_zod7.z.coerce.number().optional().describe("Top padding (default: 0)"),
333
- paddingRight: import_zod7.z.coerce.number().optional().describe("Right padding (default: 0)"),
334
- paddingBottom: import_zod7.z.coerce.number().optional().describe("Bottom padding (default: 0)"),
335
- paddingLeft: import_zod7.z.coerce.number().optional().describe("Left padding (default: 0)"),
336
- primaryAxisAlignItems: import_zod7.z.enum(["MIN", "MAX", "CENTER", "SPACE_BETWEEN"]).optional(),
337
- counterAxisAlignItems: import_zod7.z.enum(["MIN", "MAX", "CENTER", "BASELINE"]).optional(),
338
- layoutSizingHorizontal: import_zod7.z.enum(["FIXED", "HUG", "FILL"]).optional(),
339
- layoutSizingVertical: import_zod7.z.enum(["FIXED", "HUG", "FILL"]).optional(),
340
- layoutWrap: import_zod7.z.enum(["NO_WRAP", "WRAP"]).optional()
341
- });
342
- var tools6 = [
343
- {
344
- name: "create_frame",
345
- description: "Create frames in Figma. Batch supported. Use fillStyleName/fillVariableId and strokeStyleName/strokeVariableId instead of hardcoded colors \u2014 hardcoded values skip design tokens and will trigger lint warnings.",
346
- schema: { items: flexJson(import_zod7.z.array(frameItem)).describe("Array of frames to create"), depth },
347
- tier: "create"
1326
+ name: "connection",
1327
+ description: '/** Manage the Figma plugin connection. Use method "help" for detailed parameter docs. */\n create (channel?) \u2192 string // Join a relay channel (required first step before any other tool)\n get () \u2192 { status, documentName, currentPage, timestamp } // Verify end-to-end connection to Figma\n list () \u2192 unknown // Inspect which clients (MCP, plugin) are connected to each channel\n delete (channel?) \u2192 string // Disconnect all clients (MCP server and Figma plugin) from a channel and reset its state\n// Connection manages the WebSocket link between the MCP server and the Figma plugin.\n// Channels are named rooms \u2014 both the MCP server and Figma plugin must join the same channel to communicate.\n// Workflow: connection(method:"create") to join a channel \u2192 connection(method:"get") to verify Figma plugin is connected.\n// If get times out (5s), the Figma plugin is not running or not on the same channel.\n// list shows all active channels and their connected clients. delete factory-resets a channel.',
1328
+ schema: (caps2) => filterMethodsByTier({
1329
+ method: import_zod4.z.enum(["create", "get", "list", "delete", "help"]),
1330
+ channel: import_zod4.z.string().optional().default("vibma").describe("The channel name displayed in the Figma plugin panel. Defaults to 'vibma' if omitted."),
1331
+ topic: import_zod4.z.string().optional().describe('Help topic \u2014 method name for endpoint help, e.g. "create"')
1332
+ }, caps2, { "create": "read", "get": "read", "list": "read", "delete": "edit", "help": "read" }),
1333
+ tier: "read",
1334
+ timeout: 5e3,
1335
+ commandMap: { "create": "connection.create", "get": "connection.get", "list": "connection.list", "delete": "connection.delete" }
348
1336
  },
349
1337
  {
350
- name: "create_auto_layout",
351
- description: "Wrap existing nodes in an auto-layout frame. One call replaces create_frame + update_frame + insert_child \xD7 N.",
352
- schema: { items: flexJson(import_zod7.z.array(autoLayoutItem)).describe("Array of auto-layout wraps to perform"), depth },
353
- tier: "create"
354
- }
355
- ];
356
-
357
- // src/tools/defs/create-text.ts
358
- var import_zod8 = require("zod");
359
- var textItem = import_zod8.z.object({
360
- text: import_zod8.z.string().describe("Text content"),
361
- name: import_zod8.z.string().optional().describe("Layer name (default: text content)"),
362
- x: xPos,
363
- y: yPos,
364
- fontFamily: import_zod8.z.string().optional().describe("Font family (default: Inter). Use get_available_fonts to list installed fonts."),
365
- fontStyle: import_zod8.z.string().optional().describe("Font style, e.g. 'Regular', 'Bold', 'Italic' (default: derived from fontWeight). Overrides fontWeight when set."),
366
- fontSize: import_zod8.z.coerce.number().optional().describe("Font size (default: 14)"),
367
- fontWeight: import_zod8.z.coerce.number().optional().describe("Font weight: 100-900 (default: 400). Ignored when fontStyle is set."),
368
- fontColor: flexJson(colorRgba).optional().describe("Font color. Default: black."),
369
- fontColorVariableId: import_zod8.z.string().optional().describe("Bind a color variable to the text fill instead of hardcoded fontColor."),
370
- fontColorStyleName: import_zod8.z.string().optional().describe("Apply a paint style to the text fill by name (case-insensitive). Overrides fontColor."),
371
- parentId,
372
- textStyleId: import_zod8.z.string().optional().describe("Text style ID to apply (overrides fontSize/fontWeight). Omit to skip."),
373
- textStyleName: import_zod8.z.string().optional().describe("Text style name (case-insensitive match). Omit to skip."),
374
- textAlignHorizontal: import_zod8.z.enum(["LEFT", "CENTER", "RIGHT", "JUSTIFIED"]).optional().describe("Horizontal text alignment (default: LEFT)"),
375
- textAlignVertical: import_zod8.z.enum(["TOP", "CENTER", "BOTTOM"]).optional().describe("Vertical text alignment (default: TOP)"),
376
- layoutSizingHorizontal: import_zod8.z.enum(["FIXED", "HUG", "FILL"]).optional().describe("Horizontal sizing. FILL auto-sets textAutoResize to HEIGHT."),
377
- layoutSizingVertical: import_zod8.z.enum(["FIXED", "HUG", "FILL"]).optional().describe("Vertical sizing (default: HUG)"),
378
- textAutoResize: import_zod8.z.enum(["NONE", "WIDTH_AND_HEIGHT", "HEIGHT", "TRUNCATE"]).optional().describe("Text auto-resize behavior (default: WIDTH_AND_HEIGHT when FILL)")
379
- });
380
- var tools7 = [
381
- {
382
- name: "create_text",
383
- description: "Create text nodes. Prefer textStyleName for typography and fontColorStyleName or fontColorVariableId for color \u2014 hardcoded values skip design tokens. Supports custom fonts via fontFamily.",
384
- schema: { items: flexJson(import_zod8.z.array(textItem)).describe("Array of text nodes to create"), depth },
385
- tier: "create"
386
- }
387
- ];
388
-
389
- // src/tools/defs/modify-node.ts
390
- var import_zod9 = require("zod");
391
- var deleteItem = import_zod9.z.object({
392
- nodeId: import_zod9.z.string().describe("Node ID to delete")
393
- });
394
- var cloneItem = import_zod9.z.object({
395
- nodeId: import_zod9.z.string().describe("Node ID to clone"),
396
- parentId: import_zod9.z.string().optional().describe("Parent for the clone (e.g. a page ID). Defaults to same parent as original."),
397
- x: import_zod9.z.coerce.number().optional().describe("New X for clone. Omit to keep original position."),
398
- y: import_zod9.z.coerce.number().optional().describe("New Y for clone. Omit to keep original position.")
399
- });
400
- var insertItem = import_zod9.z.object({
401
- parentId: import_zod9.z.string().describe("Parent node ID"),
402
- childId: import_zod9.z.string().describe("Child node ID to move"),
403
- index: import_zod9.z.coerce.number().optional().describe("Index to insert at (0=first). Omit to append.")
404
- });
405
- var tools8 = [
406
- {
407
- name: "delete_node",
408
- description: "Delete nodes. Batch: pass multiple items.",
409
- schema: { items: flexJson(import_zod9.z.array(deleteItem)).describe("Array of {nodeId}") },
410
- tier: "edit"
1338
+ name: "document",
1339
+ description: '/** Navigate and manage Figma pages (canvases) in the document. Use method "help" for detailed parameter docs. */\n get () \u2192 { id, name, backgroundColor?, children: NodeStub[] } // Get current page with top-level children\n list () \u2192 { name, currentPageId, pages } // Get document name and list all pages\n set (pageId?, pageName?) \u2192 { id, name } // Switch to a page by ID or name. At least one of pageId or pageName must be provided.\n create (name?) \u2192 { id } // Create a new page\n update (newName, pageId?) \u2192 string // Rename a page\n// A Figma document contains pages \u2014 each page is an independent canvas with its own node tree.\n// The "current page" is where all node operations happen. Use document.set to switch pages before working with nodes.\n// Page IDs look like "0:1", "1:1", etc. The first page is always "0:1".\n// get returns the current page with its top-level children as stubs. list returns all pages in the document.\n// Shared types:\n// NodeStub: {id: string, name: string, type: string}',
1340
+ schema: (caps2) => filterMethodsByTier({
1341
+ method: import_zod4.z.enum(["get", "list", "set", "create", "update", "help"]),
1342
+ pageId: import_zod4.z.string().optional().describe("Page ID"),
1343
+ pageName: import_zod4.z.string().optional().describe("Page name (case-insensitive, substring match)"),
1344
+ name: import_zod4.z.string().optional().describe("Page name (default: 'New Page')"),
1345
+ newName: import_zod4.z.string().optional().describe("New page name"),
1346
+ topic: import_zod4.z.string().optional().describe('Help topic \u2014 method name for endpoint help, e.g. "create"')
1347
+ }, caps2, { "get": "read", "list": "read", "set": "read", "create": "create", "update": "edit", "help": "read" }),
1348
+ tier: "read",
1349
+ validate: (params) => {
1350
+ const m = params.method;
1351
+ if (m === "update") {
1352
+ if (params.newName === void 0) throw new Error('update requires "newName"');
1353
+ }
1354
+ },
1355
+ commandMap: { "get": "document.get", "list": "document.list", "set": "document.set", "create": "document.create", "update": "document.update" }
411
1356
  },
412
1357
  {
413
- name: "clone_node",
414
- description: "Clone nodes. Batch: pass multiple items.",
415
- schema: { items: flexJson(import_zod9.z.array(cloneItem)).describe("Array of {nodeId, x?, y?}"), depth },
416
- tier: "create"
1358
+ name: "fonts",
1359
+ description: '/** Search available fonts in Figma. Use method "help" for detailed parameter docs. */\n list (query?, includeStyles?, offset?, limit?) \u2192 { count, fonts } // List available font families, optionally filtered by name\n// Returns font family names installed in the Figma environment. Use to verify a fontFamily before text.create or styles.create.\n// query filters by family name substring (case-insensitive), e.g. query:"inter" matches "Inter", "Inter Tight".',
1360
+ schema: (caps2) => filterMethodsByTier({
1361
+ method: import_zod4.z.enum(["list", "help"]),
1362
+ query: import_zod4.z.string().optional().describe("Filter by family name (case-insensitive substring)"),
1363
+ includeStyles: flexBool(import_zod4.z.boolean()).optional().describe("Include available styles per family (default: false)"),
1364
+ offset: import_zod4.z.coerce.number().optional().default(0).describe("Skip N items for pagination (default 0)"),
1365
+ limit: import_zod4.z.coerce.number().optional().default(100).describe("Max items per page (default 100)"),
1366
+ topic: import_zod4.z.string().optional().describe('Help topic \u2014 method name for endpoint help, e.g. "create"')
1367
+ }, caps2, { "list": "read", "help": "read" }),
1368
+ tier: "read",
1369
+ commandMap: { "list": "fonts.list" }
417
1370
  },
418
1371
  {
419
- name: "insert_child",
420
- description: "Move nodes into a parent at a specific index (reorder/reparent). Batch: pass multiple items.",
421
- schema: { items: flexJson(import_zod9.z.array(insertItem)).describe("Array of {parentId, childId, index?}"), depth },
422
- tier: "edit"
423
- }
424
- ];
425
-
426
- // src/tools/defs/patch-nodes.ts
427
- var import_zod10 = require("zod");
428
- var exportSettingEntry = import_zod10.z.object({
429
- format: import_zod10.z.enum(["PNG", "JPG", "SVG", "PDF"]),
430
- suffix: import_zod10.z.string().optional(),
431
- contentsOnly: flexBool(import_zod10.z.boolean()).optional(),
432
- constraint: flexJson(import_zod10.z.object({
433
- type: import_zod10.z.enum(["SCALE", "WIDTH", "HEIGHT"]),
434
- value: import_zod10.z.coerce.number()
435
- })).optional()
436
- });
437
- var patchNodeItem = import_zod10.z.object({
438
- nodeId,
439
- // Geometry (flat)
440
- x: import_zod10.z.coerce.number().optional().describe("X position"),
441
- y: import_zod10.z.coerce.number().optional().describe("Y position"),
442
- width: import_zod10.z.coerce.number().positive().optional().describe("Width (must provide height too)"),
443
- height: import_zod10.z.coerce.number().positive().optional().describe("Height (must provide width too)"),
444
- // Appearance (nested)
445
- fill: flexJson(import_zod10.z.object({
446
- color: flexJson(colorRgba).optional(),
447
- styleName: import_zod10.z.string().optional().describe("Paint style name (preferred over color)"),
448
- clear: flexBool(import_zod10.z.boolean()).optional().describe("Set true to remove all fills")
449
- })).optional().describe("Fill color, style, or clear"),
450
- stroke: flexJson(import_zod10.z.object({
451
- color: flexJson(colorRgba).optional(),
452
- weight: import_zod10.z.coerce.number().positive().optional().describe("Stroke weight"),
453
- styleName: import_zod10.z.string().optional().describe("Paint style name (preferred over color)")
454
- })).optional().describe("Stroke color/weight or style"),
455
- cornerRadius: flexJson(import_zod10.z.object({
456
- radius: import_zod10.z.coerce.number().min(0).describe("Corner radius"),
457
- corners: flexJson(import_zod10.z.array(flexBool(import_zod10.z.boolean())).length(4)).optional().describe("Which corners [topLeft, topRight, bottomRight, bottomLeft]. Default: all.")
458
- })).optional().describe("Corner radius"),
459
- opacity: import_zod10.z.coerce.number().min(0).max(1).optional().describe("Opacity (0-1)"),
460
- effects: flexJson(import_zod10.z.object({
461
- effects: flexJson(import_zod10.z.array(effectEntry)).optional().describe("Effect objects"),
462
- styleName: import_zod10.z.string().optional().describe("Effect style name (preferred over raw effects)")
463
- })).optional().describe("Effects or effect style"),
464
- constraints: flexJson(import_zod10.z.object({
465
- horizontal: import_zod10.z.enum(["MIN", "CENTER", "MAX", "STRETCH", "SCALE"]),
466
- vertical: import_zod10.z.enum(["MIN", "CENTER", "MAX", "STRETCH", "SCALE"])
467
- })).optional().describe("Layout constraints"),
468
- exportSettings: flexJson(import_zod10.z.array(exportSettingEntry)).optional().describe("Export settings"),
469
- // Layout (nested)
470
- layout: flexJson(import_zod10.z.object({
471
- layoutMode: import_zod10.z.enum(["NONE", "HORIZONTAL", "VERTICAL"]).optional(),
472
- layoutWrap: import_zod10.z.enum(["NO_WRAP", "WRAP"]).optional(),
473
- paddingTop: import_zod10.z.coerce.number().optional(),
474
- paddingRight: import_zod10.z.coerce.number().optional(),
475
- paddingBottom: import_zod10.z.coerce.number().optional(),
476
- paddingLeft: import_zod10.z.coerce.number().optional(),
477
- primaryAxisAlignItems: import_zod10.z.enum(["MIN", "MAX", "CENTER", "SPACE_BETWEEN"]).optional(),
478
- counterAxisAlignItems: import_zod10.z.enum(["MIN", "MAX", "CENTER", "BASELINE"]).optional(),
479
- layoutSizingHorizontal: import_zod10.z.enum(["FIXED", "HUG", "FILL"]).optional(),
480
- layoutSizingVertical: import_zod10.z.enum(["FIXED", "HUG", "FILL"]).optional(),
481
- itemSpacing: import_zod10.z.coerce.number().optional(),
482
- counterAxisSpacing: import_zod10.z.coerce.number().optional()
483
- })).optional().describe("Auto-layout properties"),
484
- // Text (nested)
485
- text: flexJson(import_zod10.z.object({
486
- fontSize: import_zod10.z.coerce.number().optional(),
487
- fontWeight: import_zod10.z.coerce.number().optional(),
488
- fontColor: flexJson(colorRgba).optional(),
489
- textStyleId: import_zod10.z.string().optional(),
490
- textStyleName: import_zod10.z.string().optional(),
491
- textAlignHorizontal: import_zod10.z.enum(["LEFT", "CENTER", "RIGHT", "JUSTIFIED"]).optional(),
492
- textAlignVertical: import_zod10.z.enum(["TOP", "CENTER", "BOTTOM"]).optional(),
493
- textAutoResize: import_zod10.z.enum(["NONE", "WIDTH_AND_HEIGHT", "HEIGHT", "TRUNCATE"]).optional(),
494
- layoutSizingHorizontal: import_zod10.z.enum(["FIXED", "HUG", "FILL"]).optional(),
495
- layoutSizingVertical: import_zod10.z.enum(["FIXED", "HUG", "FILL"]).optional()
496
- })).optional().describe("Text properties (font, alignment, sizing)"),
497
- // Escape hatch
498
- properties: flexJson(import_zod10.z.record(import_zod10.z.string(), import_zod10.z.unknown())).optional().describe("Arbitrary key-value properties to set directly on the node")
499
- });
500
- var tools9 = [
501
- {
502
- name: "patch_nodes",
503
- description: "Patch properties on nodes. Combines geometry (x/y/width/height), appearance (fill, stroke, cornerRadius, opacity, effects, constraints, exportSettings), layout (auto-layout), text (font props), and arbitrary properties in one call. Prefer styleName over hardcoded colors. Batch: pass multiple items.",
504
- schema: { items: flexJson(import_zod10.z.array(patchNodeItem)).describe("Array of nodes to patch"), depth },
505
- tier: "edit"
506
- }
507
- ];
508
-
509
- // src/tools/defs/text.ts
510
- var import_zod11 = require("zod");
511
- var textContentItem = import_zod11.z.object({
512
- nodeId: import_zod11.z.string().describe("Text node ID"),
513
- text: import_zod11.z.string().describe("New text content")
514
- });
515
- var scanTextItem = import_zod11.z.object({
516
- nodeId,
517
- limit: import_zod11.z.coerce.number().optional().describe("Max text nodes to return (default: 50)"),
518
- includePath: flexBool(import_zod11.z.boolean()).optional().describe("Include ancestor path strings (default: true). Set false to reduce payload."),
519
- includeGeometry: flexBool(import_zod11.z.boolean()).optional().describe("Include absoluteX/absoluteY/width/height (default: true). Set false to reduce payload.")
520
- });
521
- var tools10 = [
522
- {
523
- name: "set_text_content",
524
- description: "Set text content on text nodes. Batch: pass multiple items to replace text in multiple nodes at once.",
525
- schema: { items: flexJson(import_zod11.z.array(textContentItem)).describe("Array of {nodeId, text}"), depth },
526
- tier: "edit"
1372
+ name: "frames",
1373
+ description: '/** Create and manage frames, shapes, auto-layout containers, sections, and SVG nodes. Use method "help" for detailed parameter docs. */\n get (id, fields?, depth?, verbose?) \u2192 { results: Node[], _truncated?, _notice? } // Get serialized node data\n list (query?, types?, parentId?, fields?, offset?, limit?) \u2192 { totalCount, returned?, offset?, limit?, results } // Search for nodes (returns stubs only \u2014 use get with depth for full properties)\n update (items: PatchItem[]) \u2192 { results: ("ok" | {error})[] } // Patch node properties\n delete (id?, items?: { id?: string }[]) \u2192 { results: "ok"[] } // Delete nodes\n clone (id, parentId?, x?, y?, depth?) \u2192 { results: {id}[] } // Duplicate nodes\n audit (id, rules?, maxDepth?, maxFindings?) \u2192 { nodeId?, nodeName?, categories? } // Run lint on a node \u2014 returns severity-ranked findings\n reparent (items: { id: string; parentId: string; index?: number }[]) \u2192 { results: "ok"[] } // Move nodes into a new parent\n create (type: frame|auto_layout|section|rectangle|ellipse|line|group|boolean_operation|svg, items: (FrameItem | AutoLayoutItem | SectionItem | RectangleItem | EllipseItem | LineItem | GroupItem | BooleanOperationItem | SvgItem)[]) \u2192 { results: {id}[] } // Create frame-like containers\n export (id, format?: PNG|JPG|SVG|SVG_STRING|PDF, scale?) \u2192 { imageData?, mimeType? } // Export a node as PNG, JPG, SVG, SVG_STRING, or PDF\n// depth: omit \u2192 id+name stubs | 0 \u2192 props + child stubs | N \u2192 recurse N | -1 \u2192 full tree\n// fields: whitelist e.g. ["fills","opacity"] \u2014 id, name, type always included. Pass ["*"] for all.\n// layoutSizingHorizontal/Vertical: FIXED | HUG | FILL \u2014 how the node sizes within auto-layout.\n// Colors: fillVariableName/strokeVariableName bind by name \u2014 preferred over raw color values.\n// Note: node-based endpoints (frames, text, instances, components) use `results` as the list key.\n// Standalone endpoints (styles, variables, variable_collections) use `items`. Components.list uses `items` (catalog view).',
1374
+ schema: (caps2) => filterMethodsByTier({
1375
+ method: import_zod4.z.enum(["get", "list", "update", "delete", "clone", "audit", "reparent", "create", "export", "help"]),
1376
+ id: import_zod4.z.string().optional().describe("Node ID"),
1377
+ fields: flexJson(import_zod4.z.array(import_zod4.z.string())).optional().describe('Property whitelist. Identity fields (id, name, type) always included. Omit for stubs on list, full on get. Pass ["*"] for all.'),
1378
+ depth: import_zod4.z.coerce.number().optional().describe("Response detail: omit for id+name only. 0=properties + child stubs. N=recurse N levels. -1=unlimited."),
1379
+ verbose: import_zod4.z.boolean().optional().describe("Include all properties (bounding box, constraints, text style details). Default false \u2014 returns slim, actionable output."),
1380
+ query: import_zod4.z.string().optional().describe("Name search query (case-insensitive substring match)"),
1381
+ types: flexJson(import_zod4.z.array(import_zod4.z.string())).optional().describe('Filter by node types (e.g. ["FRAME", "TEXT"])'),
1382
+ parentId: import_zod4.z.string().optional().describe("Search only within this subtree"),
1383
+ offset: import_zod4.z.coerce.number().optional().default(0).describe("Skip N items for pagination (default 0)"),
1384
+ limit: import_zod4.z.coerce.number().optional().default(100).describe("Max items per page (default 100)"),
1385
+ items: flexJson(import_zod4.z.array(import_zod4.z.record(import_zod4.z.string(), import_zod4.z.unknown()))).optional().describe("Array of {id, ...properties} to update/delete/reparent"),
1386
+ x: import_zod4.z.coerce.number().optional().describe("X position (default: 0)"),
1387
+ y: import_zod4.z.coerce.number().optional().describe("Y position (default: 0)"),
1388
+ rules: flexJson(import_zod4.z.array(import_zod4.z.string())).optional().describe('Rules to check. Default: ["all"]. Categories: "component", "composition", "token", "naming", "wcag".'),
1389
+ maxDepth: import_zod4.z.coerce.number().optional().describe("Max tree depth (default: 10)"),
1390
+ maxFindings: import_zod4.z.coerce.number().optional().describe("Max findings (default: 50)"),
1391
+ type: import_zod4.z.enum(["frame", "auto_layout", "section", "rectangle", "ellipse", "line", "group", "boolean_operation", "svg"]).optional().describe("Discriminant for create method"),
1392
+ format: import_zod4.z.enum(["PNG", "JPG", "SVG", "SVG_STRING", "PDF"]).optional().describe("Export format (default: PNG). SVG_STRING returns raw SVG text."),
1393
+ scale: import_zod4.z.coerce.number().optional().describe("Export scale (default: 1, only for PNG/JPG)"),
1394
+ topic: import_zod4.z.string().optional().describe('Help topic \u2014 method name for endpoint help, e.g. "create"')
1395
+ }, caps2, { "get": "read", "list": "read", "update": "edit", "delete": "edit", "clone": "create", "audit": "read", "reparent": "edit", "create": "create", "export": "read", "help": "read" }),
1396
+ tier: "read",
1397
+ validate: (params) => {
1398
+ const m = params.method;
1399
+ if (m === "export") {
1400
+ if (params.id === void 0) throw new Error('export requires "id"');
1401
+ }
1402
+ if (!params.items) return;
1403
+ if (m === "create") {
1404
+ const schemas = {
1405
+ "frame": import_zod4.z.object({
1406
+ name: import_zod4.z.string().optional().describe("Node name"),
1407
+ parentId: import_zod4.z.string().optional().describe("Parent node ID. Omit to place on current page."),
1408
+ x: import_zod4.z.coerce.number().optional().describe("X position (default: 0)"),
1409
+ y: import_zod4.z.coerce.number().optional().describe("Y position (default: 0)"),
1410
+ width: import_zod4.z.coerce.number().optional().describe("Width (default: 100)"),
1411
+ height: import_zod4.z.coerce.number().optional().describe("Height (default: 100)"),
1412
+ rotation: import_zod4.z.coerce.number().optional().describe("Rotation in degrees (0-360)"),
1413
+ opacity: token.optional().describe("Opacity (0-1) or variable name"),
1414
+ visible: flexBool(import_zod4.z.boolean()).optional().describe("Show/hide (default true)"),
1415
+ locked: flexBool(import_zod4.z.boolean()).optional().describe("Lock/unlock (default false)"),
1416
+ blendMode: import_zod4.z.enum(["PASS_THROUGH", "NORMAL", "DARKEN", "MULTIPLY", "LINEAR_BURN", "COLOR_BURN", "LIGHTEN", "SCREEN", "LINEAR_DODGE", "COLOR_DODGE", "OVERLAY", "SOFT_LIGHT", "HARD_LIGHT", "DIFFERENCE", "EXCLUSION", "HUE", "SATURATION", "COLOR", "LUMINOSITY"]).optional(),
1417
+ layoutPositioning: import_zod4.z.enum(["AUTO", "ABSOLUTE"]).optional().describe("ABSOLUTE = floating inside auto-layout parent"),
1418
+ fills: import_zod4.z.array(import_zod4.z.record(import_zod4.z.string(), import_zod4.z.unknown())).optional().describe("Fill paints array \u2014 e.g. [{type: 'SOLID', color: '#hex'}] or [] for transparent. Primary way to set fills."),
1419
+ fillColor: colorRgba.optional().describe("Shorthand \u2014 sets a single solid fill (auto-binds to matching variable/style)"),
1420
+ fillStyleName: import_zod4.z.string().optional().describe("Paint style name for fill"),
1421
+ fillVariableName: import_zod4.z.string().optional().describe("Color variable by name e.g. 'bg/primary'"),
1422
+ strokes: import_zod4.z.array(import_zod4.z.record(import_zod4.z.string(), import_zod4.z.unknown())).optional().describe("Stroke paints array \u2014 e.g. [{type: 'SOLID', color: '#hex'}] or [] to clear. Primary way to set strokes."),
1423
+ strokeColor: colorRgba.optional().describe("Shorthand \u2014 sets a single solid stroke (auto-binds to matching variable/style)"),
1424
+ strokeStyleName: import_zod4.z.string().optional().describe("Paint style name for stroke"),
1425
+ strokeVariableName: import_zod4.z.string().optional().describe("Color variable by name for stroke"),
1426
+ strokeWeight: token.optional().describe("All sides (number) or variable name (string). Per-side: strokeTopWeight, strokeBottomWeight, strokeLeftWeight, strokeRightWeight."),
1427
+ strokeTopWeight: token.optional(),
1428
+ strokeBottomWeight: token.optional(),
1429
+ strokeLeftWeight: token.optional(),
1430
+ strokeRightWeight: token.optional(),
1431
+ strokeAlign: import_zod4.z.enum(["INSIDE", "OUTSIDE", "CENTER"]).optional().describe("Stroke position (default: INSIDE)"),
1432
+ strokesIncludedInLayout: flexBool(import_zod4.z.boolean()).optional().describe("Include stroke width in layout measurements (default: false)"),
1433
+ cornerRadius: token.optional().describe("All corners (number) or variable name (string). Per-corner: topLeftRadius, topRightRadius, bottomRightRadius, bottomLeftRadius."),
1434
+ topLeftRadius: token.optional(),
1435
+ topRightRadius: token.optional(),
1436
+ bottomRightRadius: token.optional(),
1437
+ bottomLeftRadius: token.optional(),
1438
+ effectStyleName: import_zod4.z.string().optional().describe("Effect style name (e.g. 'Shadow/Card') for shadows, blurs"),
1439
+ layoutMode: import_zod4.z.enum(["NONE", "HORIZONTAL", "VERTICAL"]).optional().describe("Layout direction (default: NONE)"),
1440
+ layoutWrap: import_zod4.z.enum(["NO_WRAP", "WRAP"]).optional(),
1441
+ padding: token.optional().describe("All edges (number) or variable name (string). Per-edge: paddingTop, paddingRight, paddingBottom, paddingLeft."),
1442
+ paddingTop: token.optional(),
1443
+ paddingRight: token.optional(),
1444
+ paddingBottom: token.optional(),
1445
+ paddingLeft: token.optional(),
1446
+ primaryAxisAlignItems: import_zod4.z.enum(["MIN", "MAX", "CENTER", "SPACE_BETWEEN"]).optional(),
1447
+ counterAxisAlignItems: import_zod4.z.enum(["MIN", "MAX", "CENTER", "BASELINE"]).optional(),
1448
+ layoutSizingHorizontal: import_zod4.z.enum(["FIXED", "HUG", "FILL"]).optional(),
1449
+ layoutSizingVertical: import_zod4.z.enum(["FIXED", "HUG", "FILL"]).optional(),
1450
+ itemSpacing: token.optional().describe("Spacing between children (number or variable name string, default: 0)"),
1451
+ counterAxisSpacing: token.optional().describe("Gap between wrapped rows (requires layoutWrap: WRAP)"),
1452
+ minWidth: import_zod4.z.coerce.number().optional().describe("Min width for responsive auto-layout"),
1453
+ maxWidth: import_zod4.z.coerce.number().optional().describe("Max width for responsive auto-layout"),
1454
+ minHeight: import_zod4.z.coerce.number().optional().describe("Min height for responsive auto-layout"),
1455
+ maxHeight: import_zod4.z.coerce.number().optional().describe("Max height for responsive auto-layout"),
1456
+ overflowDirection: import_zod4.z.enum(["NONE", "HORIZONTAL", "VERTICAL", "BOTH"]).optional().describe("Scroll overflow in prototype (default: NONE)"),
1457
+ clipsContent: flexBool(import_zod4.z.boolean()).optional()
1458
+ }).passthrough(),
1459
+ "auto_layout": import_zod4.z.object({
1460
+ name: import_zod4.z.string().optional().describe("Node name"),
1461
+ parentId: import_zod4.z.string().optional().describe("Parent node ID. Omit to place on current page."),
1462
+ x: import_zod4.z.coerce.number().optional().describe("X position (default: 0)"),
1463
+ y: import_zod4.z.coerce.number().optional().describe("Y position (default: 0)"),
1464
+ width: import_zod4.z.coerce.number().optional().describe("Width (default: 100)"),
1465
+ height: import_zod4.z.coerce.number().optional().describe("Height (default: 100)"),
1466
+ rotation: import_zod4.z.coerce.number().optional().describe("Rotation in degrees (0-360)"),
1467
+ opacity: token.optional().describe("Opacity (0-1) or variable name"),
1468
+ visible: flexBool(import_zod4.z.boolean()).optional().describe("Show/hide (default true)"),
1469
+ locked: flexBool(import_zod4.z.boolean()).optional().describe("Lock/unlock (default false)"),
1470
+ blendMode: import_zod4.z.enum(["PASS_THROUGH", "NORMAL", "DARKEN", "MULTIPLY", "LINEAR_BURN", "COLOR_BURN", "LIGHTEN", "SCREEN", "LINEAR_DODGE", "COLOR_DODGE", "OVERLAY", "SOFT_LIGHT", "HARD_LIGHT", "DIFFERENCE", "EXCLUSION", "HUE", "SATURATION", "COLOR", "LUMINOSITY"]).optional(),
1471
+ layoutPositioning: import_zod4.z.enum(["AUTO", "ABSOLUTE"]).optional().describe("ABSOLUTE = floating inside auto-layout parent"),
1472
+ fills: import_zod4.z.array(import_zod4.z.record(import_zod4.z.string(), import_zod4.z.unknown())).optional().describe("Fill paints array \u2014 e.g. [{type: 'SOLID', color: '#hex'}] or [] for transparent. Primary way to set fills."),
1473
+ fillColor: colorRgba.optional().describe("Shorthand \u2014 sets a single solid fill (auto-binds to matching variable/style)"),
1474
+ fillStyleName: import_zod4.z.string().optional().describe("Paint style name for fill"),
1475
+ fillVariableName: import_zod4.z.string().optional().describe("Color variable by name e.g. 'bg/primary'"),
1476
+ strokes: import_zod4.z.array(import_zod4.z.record(import_zod4.z.string(), import_zod4.z.unknown())).optional().describe("Stroke paints array \u2014 e.g. [{type: 'SOLID', color: '#hex'}] or [] to clear. Primary way to set strokes."),
1477
+ strokeColor: colorRgba.optional().describe("Shorthand \u2014 sets a single solid stroke (auto-binds to matching variable/style)"),
1478
+ strokeStyleName: import_zod4.z.string().optional().describe("Paint style name for stroke"),
1479
+ strokeVariableName: import_zod4.z.string().optional().describe("Color variable by name for stroke"),
1480
+ strokeWeight: token.optional().describe("All sides (number) or variable name (string). Per-side: strokeTopWeight, strokeBottomWeight, strokeLeftWeight, strokeRightWeight."),
1481
+ strokeTopWeight: token.optional(),
1482
+ strokeBottomWeight: token.optional(),
1483
+ strokeLeftWeight: token.optional(),
1484
+ strokeRightWeight: token.optional(),
1485
+ strokeAlign: import_zod4.z.enum(["INSIDE", "OUTSIDE", "CENTER"]).optional().describe("Stroke position (default: INSIDE)"),
1486
+ strokesIncludedInLayout: flexBool(import_zod4.z.boolean()).optional().describe("Include stroke width in layout measurements (default: false)"),
1487
+ cornerRadius: token.optional().describe("All corners (number) or variable name (string). Per-corner: topLeftRadius, topRightRadius, bottomRightRadius, bottomLeftRadius."),
1488
+ topLeftRadius: token.optional(),
1489
+ topRightRadius: token.optional(),
1490
+ bottomRightRadius: token.optional(),
1491
+ bottomLeftRadius: token.optional(),
1492
+ effectStyleName: import_zod4.z.string().optional().describe("Effect style name (e.g. 'Shadow/Card') for shadows, blurs"),
1493
+ layoutMode: import_zod4.z.enum(["HORIZONTAL", "VERTICAL"]).describe("Primary axis direction"),
1494
+ layoutWrap: import_zod4.z.enum(["NO_WRAP", "WRAP"]).optional(),
1495
+ padding: token.optional().describe("All edges (number) or variable name (string). Per-edge: paddingTop, paddingRight, paddingBottom, paddingLeft."),
1496
+ paddingTop: token.optional(),
1497
+ paddingRight: token.optional(),
1498
+ paddingBottom: token.optional(),
1499
+ paddingLeft: token.optional(),
1500
+ primaryAxisAlignItems: import_zod4.z.enum(["MIN", "MAX", "CENTER", "SPACE_BETWEEN"]).optional(),
1501
+ counterAxisAlignItems: import_zod4.z.enum(["MIN", "MAX", "CENTER", "BASELINE"]).optional(),
1502
+ layoutSizingHorizontal: import_zod4.z.enum(["FIXED", "HUG", "FILL"]).optional(),
1503
+ layoutSizingVertical: import_zod4.z.enum(["FIXED", "HUG", "FILL"]).optional(),
1504
+ itemSpacing: token.optional().describe("Spacing between children (number or variable name string, default: 0)"),
1505
+ counterAxisSpacing: token.optional().describe("Gap between wrapped rows (requires layoutWrap: WRAP)"),
1506
+ minWidth: import_zod4.z.coerce.number().optional().describe("Min width for responsive auto-layout"),
1507
+ maxWidth: import_zod4.z.coerce.number().optional().describe("Max width for responsive auto-layout"),
1508
+ minHeight: import_zod4.z.coerce.number().optional().describe("Min height for responsive auto-layout"),
1509
+ maxHeight: import_zod4.z.coerce.number().optional().describe("Max height for responsive auto-layout"),
1510
+ overflowDirection: import_zod4.z.enum(["NONE", "HORIZONTAL", "VERTICAL", "BOTH"]).optional().describe("Scroll overflow in prototype (default: NONE)"),
1511
+ clipsContent: flexBool(import_zod4.z.boolean()).optional(),
1512
+ nodeIds: flexJson(import_zod4.z.array(import_zod4.z.string())).optional().describe("Existing node IDs to wrap into auto-layout")
1513
+ }).passthrough(),
1514
+ "section": import_zod4.z.object({
1515
+ name: import_zod4.z.string().describe("Section name"),
1516
+ parentId: import_zod4.z.string().optional().describe("Parent node ID. Omit to place on current page."),
1517
+ x: import_zod4.z.coerce.number().optional().describe("X position (default: 0)"),
1518
+ y: import_zod4.z.coerce.number().optional().describe("Y position (default: 0)"),
1519
+ width: import_zod4.z.coerce.number().optional().describe("Width (default: 500)"),
1520
+ height: import_zod4.z.coerce.number().optional().describe("Height (default: 500)"),
1521
+ fills: import_zod4.z.array(import_zod4.z.record(import_zod4.z.string(), import_zod4.z.unknown())).optional().describe("Fill paints array \u2014 e.g. [{type: 'SOLID', color: '#hex'}] or [] for transparent"),
1522
+ fillColor: colorRgba.optional().describe("Shorthand \u2014 sets a single solid fill (auto-binds to matching variable/style)"),
1523
+ fillStyleName: import_zod4.z.string().optional().describe("Paint style name for fill"),
1524
+ fillVariableName: import_zod4.z.string().optional().describe("Color variable by name e.g. 'bg/primary'")
1525
+ }).passthrough(),
1526
+ "rectangle": import_zod4.z.object({
1527
+ name: import_zod4.z.string().optional().describe("Layer name (default: 'Rectangle')"),
1528
+ parentId: import_zod4.z.string().optional().describe("Parent node ID. Omit to place on current page."),
1529
+ x: import_zod4.z.coerce.number().optional().describe("X position (default: 0)"),
1530
+ y: import_zod4.z.coerce.number().optional().describe("Y position (default: 0)"),
1531
+ width: import_zod4.z.coerce.number().optional().describe("Width in px (default: 100)"),
1532
+ height: import_zod4.z.coerce.number().optional().describe("Height in px (default: 100)"),
1533
+ fills: import_zod4.z.array(import_zod4.z.record(import_zod4.z.string(), import_zod4.z.unknown())).optional().describe("Fill paints array \u2014 e.g. [{type: 'SOLID', color: '#hex'}] or [] for transparent"),
1534
+ fillColor: colorRgba.optional().describe("Shorthand \u2014 sets a single solid fill (auto-binds to matching variable/style)"),
1535
+ fillStyleName: import_zod4.z.string().optional().describe("Paint style name for fill"),
1536
+ fillVariableName: import_zod4.z.string().optional().describe("Color variable by name e.g. 'bg/primary'"),
1537
+ strokes: import_zod4.z.array(import_zod4.z.record(import_zod4.z.string(), import_zod4.z.unknown())).optional().describe("Stroke paints array \u2014 e.g. [{type: 'SOLID', color: '#hex'}] or [] to clear"),
1538
+ strokeColor: colorRgba.optional().describe("Shorthand \u2014 sets a single solid stroke (auto-binds to matching variable/style)"),
1539
+ strokeVariableName: import_zod4.z.string().optional().describe("Color variable by name for stroke"),
1540
+ strokeWeight: token.optional(),
1541
+ cornerRadius: token.optional().describe("All corners (number) or variable name (string). Per-corner: topLeftRadius, topRightRadius, bottomRightRadius, bottomLeftRadius."),
1542
+ topLeftRadius: token.optional(),
1543
+ topRightRadius: token.optional(),
1544
+ bottomRightRadius: token.optional(),
1545
+ bottomLeftRadius: token.optional(),
1546
+ opacity: token.optional(),
1547
+ layoutSizingHorizontal: import_zod4.z.enum(["FIXED", "FILL"]).optional().describe("Horizontal sizing in auto-layout parent"),
1548
+ layoutSizingVertical: import_zod4.z.enum(["FIXED", "FILL"]).optional().describe("Vertical sizing in auto-layout parent")
1549
+ }).passthrough(),
1550
+ "ellipse": import_zod4.z.object({
1551
+ name: import_zod4.z.string().optional().describe("Layer name (default: 'Ellipse')"),
1552
+ parentId: import_zod4.z.string().optional().describe("Parent node ID. Omit to place on current page."),
1553
+ x: import_zod4.z.coerce.number().optional().describe("X position (default: 0)"),
1554
+ y: import_zod4.z.coerce.number().optional().describe("Y position (default: 0)"),
1555
+ width: import_zod4.z.coerce.number().optional().describe("Width in px (default: 100)"),
1556
+ height: import_zod4.z.coerce.number().optional().describe("Height in px (default: 100, same as width for circle)"),
1557
+ fills: import_zod4.z.array(import_zod4.z.record(import_zod4.z.string(), import_zod4.z.unknown())).optional().describe("Fill paints array \u2014 e.g. [{type: 'SOLID', color: '#hex'}] or [] for transparent"),
1558
+ fillColor: colorRgba.optional().describe("Shorthand \u2014 sets a single solid fill (auto-binds to matching variable/style)"),
1559
+ fillStyleName: import_zod4.z.string().optional().describe("Paint style name for fill"),
1560
+ fillVariableName: import_zod4.z.string().optional().describe("Color variable by name e.g. 'bg/primary'"),
1561
+ strokes: import_zod4.z.array(import_zod4.z.record(import_zod4.z.string(), import_zod4.z.unknown())).optional().describe("Stroke paints array \u2014 e.g. [{type: 'SOLID', color: '#hex'}] or [] to clear"),
1562
+ strokeColor: colorRgba.optional().describe("Shorthand \u2014 sets a single solid stroke (auto-binds to matching variable/style)"),
1563
+ strokeVariableName: import_zod4.z.string().optional().describe("Color variable by name for stroke"),
1564
+ strokeWeight: token.optional(),
1565
+ opacity: token.optional(),
1566
+ layoutSizingHorizontal: import_zod4.z.enum(["FIXED", "FILL"]).optional().describe("Horizontal sizing in auto-layout parent"),
1567
+ layoutSizingVertical: import_zod4.z.enum(["FIXED", "FILL"]).optional().describe("Vertical sizing in auto-layout parent")
1568
+ }).passthrough(),
1569
+ "line": import_zod4.z.object({
1570
+ name: import_zod4.z.string().optional().describe("Layer name (default: 'Line')"),
1571
+ parentId: import_zod4.z.string().optional().describe("Parent node ID. Omit to place on current page."),
1572
+ x: import_zod4.z.coerce.number().optional().describe("X position (default: 0)"),
1573
+ y: import_zod4.z.coerce.number().optional().describe("Y position (default: 0)"),
1574
+ length: import_zod4.z.coerce.number().optional().describe("Line length in px (default: 100)"),
1575
+ rotation: import_zod4.z.coerce.number().optional().describe("Rotation in degrees (default: 0 = horizontal)"),
1576
+ strokes: import_zod4.z.array(import_zod4.z.record(import_zod4.z.string(), import_zod4.z.unknown())).optional().describe("Stroke paints array \u2014 e.g. [{type: 'SOLID', color: '#hex'}] or [] to clear"),
1577
+ strokeColor: colorRgba.optional().describe("Line color (default: black, auto-binds to matching variable/style)"),
1578
+ strokeVariableName: import_zod4.z.string().optional().describe("Color variable by name for stroke"),
1579
+ strokeWeight: token.optional().describe("Line thickness (default: 1)"),
1580
+ opacity: token.optional(),
1581
+ layoutSizingHorizontal: import_zod4.z.enum(["FIXED", "FILL"]).optional().describe("Horizontal sizing in auto-layout parent (defaults to FILL in vertical auto-layout)")
1582
+ }).passthrough(),
1583
+ "group": import_zod4.z.object({
1584
+ nodeIds: import_zod4.z.array(import_zod4.z.string()).describe("Node IDs to group (min 1)"),
1585
+ name: import_zod4.z.string().optional().describe("Group name"),
1586
+ parentId: import_zod4.z.string().optional().describe("Parent node ID. Omit to place on current page.")
1587
+ }).passthrough(),
1588
+ "boolean_operation": import_zod4.z.object({
1589
+ operation: import_zod4.z.enum(["UNION", "SUBTRACT", "INTERSECT", "EXCLUDE"]).describe("Boolean operation type"),
1590
+ nodeIds: import_zod4.z.array(import_zod4.z.string()).describe("Node IDs to combine (min 2, first node is the base for SUBTRACT)"),
1591
+ name: import_zod4.z.string().optional().describe("Result node name"),
1592
+ parentId: import_zod4.z.string().optional().describe("Parent node ID. Omit to place on current page.")
1593
+ }).passthrough(),
1594
+ "svg": import_zod4.z.object({
1595
+ svg: import_zod4.z.string().describe("SVG markup string"),
1596
+ name: import_zod4.z.string().optional().describe("Layer name (default: 'SVG')"),
1597
+ parentId: import_zod4.z.string().optional().describe("Parent node ID. Omit to place on current page."),
1598
+ x: import_zod4.z.coerce.number().optional().describe("X position (default: 0)"),
1599
+ y: import_zod4.z.coerce.number().optional().describe("Y position (default: 0)"),
1600
+ fillStyleName: import_zod4.z.string().optional().describe("Paint style to apply to vector fills"),
1601
+ fillVariableName: import_zod4.z.string().optional().describe("Color variable by name for vector fills"),
1602
+ strokeStyleName: import_zod4.z.string().optional().describe("Paint style to apply to vector strokes"),
1603
+ strokeVariableName: import_zod4.z.string().optional().describe("Color variable by name for vector strokes")
1604
+ }).passthrough()
1605
+ };
1606
+ const s = params.type && schemas[params.type];
1607
+ if (s) {
1608
+ try {
1609
+ params.items = import_zod4.z.array(s).parse(params.items);
1610
+ } catch (e) {
1611
+ if (e instanceof import_zod4.z.ZodError) {
1612
+ throw new Error(e.issues.map((i) => {
1613
+ const path = i.path.join(".");
1614
+ const shape = s instanceof import_zod4.z.ZodObject ? s.shape : null;
1615
+ const desc = shape?.[i.path[1]]?.description;
1616
+ return path + ": " + i.message + (desc ? " (expected: " + desc + ")" : "");
1617
+ }).join("; "));
1618
+ }
1619
+ throw e;
1620
+ }
1621
+ }
1622
+ }
1623
+ },
1624
+ commandMap: { "get": "frames.get", "list": "frames.list", "update": "frames.update", "delete": "frames.delete", "clone": "frames.clone", "audit": "frames.audit", "reparent": "frames.reparent", "create": "frames.create", "export": "frames.export" }
527
1625
  },
528
1626
  {
529
- name: "scan_text_nodes",
530
- description: "Scan all text nodes within a node tree. Batch: pass multiple items.",
531
- schema: { items: flexJson(import_zod11.z.array(scanTextItem)).describe("Array of {nodeId}") },
532
- tier: "read"
533
- }
534
- ];
535
-
536
- // src/tools/defs/fonts.ts
537
- var import_zod12 = require("zod");
538
- var tools11 = [
1627
+ name: "instances",
1628
+ description: '/** Create and manage component instances. Use method "help" for detailed parameter docs. */\n list (query?, types?, parentId?, fields?, offset?, limit?) \u2192 { totalCount, returned?, offset?, limit?, results } // Search for nodes (returns stubs only \u2014 use get with depth for full properties)\n delete (id?, items?: { id?: string }[]) \u2192 { results: "ok"[] } // Delete nodes\n clone (id, parentId?, x?, y?, depth?) \u2192 { results: {id}[] } // Duplicate nodes\n audit (id, rules?, maxDepth?, maxFindings?) \u2192 { nodeId?, nodeName?, categories? } // Run lint on a node \u2014 returns severity-ranked findings\n reparent (items: { id: string; parentId: string; index?: number }[]) \u2192 { results: "ok"[] } // Move nodes into a new parent\n get (id, fields?, depth?, verbose?) \u2192 { results, _truncated? } // Get instance detail with component properties and overrides\n create (items: InstanceCreateItem[], depth?) \u2192 { results: {id}[] } // Create component instances\n update (items: InstanceUpdateItem[]) \u2192 { results: ("ok" | {error})[] } // Set instance properties\n swap (items: { id: string; componentId: string }[]) \u2192 { results: ("ok" | {error})[] } // Swap instance component (preserves overrides)\n detach (items: { id: string }[]) \u2192 { results: {id}[] } // Detach instances from their component (converts to frame)\n reset_overrides(items: { id: string }[]) \u2192 { results: ("ok" | {error})[] } // Reset all overrides on instances to match their main component\n// depth: omit \u2192 id+name stubs | 0 \u2192 props + child stubs | N \u2192 recurse N | -1 \u2192 full tree\n// fields: whitelist e.g. ["fills","opacity"] \u2014 id, name, type always included. Pass ["*"] for all.\n// layoutSizingHorizontal/Vertical: FIXED | HUG | FILL \u2014 how the node sizes within auto-layout.\n// Colors: fillVariableName/strokeVariableName bind by name \u2014 preferred over raw color values.\n// Note: node-based endpoints (frames, text, instances, components) use `results` as the list key.\n// Standalone endpoints (styles, variables, variable_collections) use `items`. Components.list uses `items` (catalog view).',
1629
+ schema: (caps2) => filterMethodsByTier({
1630
+ method: import_zod4.z.enum(["list", "delete", "clone", "audit", "reparent", "get", "create", "update", "swap", "detach", "reset_overrides", "help"]),
1631
+ query: import_zod4.z.string().optional().describe("Name search query (case-insensitive substring match)"),
1632
+ types: flexJson(import_zod4.z.array(import_zod4.z.string())).optional().describe('Filter by node types (e.g. ["FRAME", "TEXT"])'),
1633
+ parentId: import_zod4.z.string().optional().describe("Search only within this subtree"),
1634
+ fields: flexJson(import_zod4.z.array(import_zod4.z.string())).optional().describe('Property whitelist. Identity fields (id, name, type) always included. Omit for stubs on list, full on get. Pass ["*"] for all.'),
1635
+ offset: import_zod4.z.coerce.number().optional().default(0).describe("Skip N items for pagination (default 0)"),
1636
+ limit: import_zod4.z.coerce.number().optional().default(100).describe("Max items per page (default 100)"),
1637
+ id: import_zod4.z.string().optional().describe("Single node ID"),
1638
+ items: flexJson(import_zod4.z.array(import_zod4.z.record(import_zod4.z.string(), import_zod4.z.unknown()))).optional().describe("Array of {id, ...properties} to delete/reparent/create/update/swap/detach/reset_overrides"),
1639
+ x: import_zod4.z.coerce.number().optional().describe("X position (default: 0)"),
1640
+ y: import_zod4.z.coerce.number().optional().describe("Y position (default: 0)"),
1641
+ depth: import_zod4.z.coerce.number().optional().describe("Response detail: omit for id+name only. 0=properties + child stubs. N=recurse N levels. -1=unlimited."),
1642
+ rules: flexJson(import_zod4.z.array(import_zod4.z.string())).optional().describe('Rules to check. Default: ["all"]. Categories: "component", "composition", "token", "naming", "wcag".'),
1643
+ maxDepth: import_zod4.z.coerce.number().optional().describe("Max tree depth (default: 10)"),
1644
+ maxFindings: import_zod4.z.coerce.number().optional().describe("Max findings (default: 50)"),
1645
+ verbose: import_zod4.z.boolean().optional().describe("Include all properties (bounding box, constraints, text style details). Default false \u2014 returns slim, actionable output."),
1646
+ topic: import_zod4.z.string().optional().describe('Help topic \u2014 method name for endpoint help, e.g. "create"')
1647
+ }, caps2, { "list": "read", "delete": "edit", "clone": "create", "audit": "read", "reparent": "edit", "get": "read", "create": "create", "update": "edit", "swap": "edit", "detach": "edit", "reset_overrides": "edit", "help": "read" }),
1648
+ tier: "read",
1649
+ validate: (params) => {
1650
+ const m = params.method;
1651
+ if (m === "get") {
1652
+ if (params.id === void 0) throw new Error('get requires "id"');
1653
+ }
1654
+ if (!params.items) return;
1655
+ if (m === "create") {
1656
+ for (const it of params.items) {
1657
+ if (it.id !== void 0 && it.componentId === void 0) {
1658
+ it.componentId = it.id;
1659
+ delete it.id;
1660
+ }
1661
+ }
1662
+ const itemSchema = import_zod4.z.object({
1663
+ opacity: token.optional().describe("Opacity (0-1) or variable name"),
1664
+ visible: flexBool(import_zod4.z.boolean()).optional().describe("Show/hide (default true)"),
1665
+ locked: flexBool(import_zod4.z.boolean()).optional().describe("Lock/unlock (default false)"),
1666
+ blendMode: import_zod4.z.enum(["PASS_THROUGH", "NORMAL", "DARKEN", "MULTIPLY", "LINEAR_BURN", "COLOR_BURN", "LIGHTEN", "SCREEN", "LINEAR_DODGE", "COLOR_DODGE", "OVERLAY", "SOFT_LIGHT", "HARD_LIGHT", "DIFFERENCE", "EXCLUSION", "HUE", "SATURATION", "COLOR", "LUMINOSITY"]).optional(),
1667
+ effectStyleName: import_zod4.z.string().optional().describe("Effect style name (e.g. 'Shadow/Card') for shadows, blurs"),
1668
+ layoutSizingHorizontal: import_zod4.z.enum(["FIXED", "HUG", "FILL"]).optional(),
1669
+ layoutSizingVertical: import_zod4.z.enum(["FIXED", "HUG", "FILL"]).optional(),
1670
+ layoutPositioning: import_zod4.z.enum(["AUTO", "ABSOLUTE"]).optional().describe("ABSOLUTE = floating inside auto-layout parent"),
1671
+ minWidth: import_zod4.z.coerce.number().optional().describe("Min width for responsive auto-layout"),
1672
+ maxWidth: import_zod4.z.coerce.number().optional().describe("Max width for responsive auto-layout"),
1673
+ minHeight: import_zod4.z.coerce.number().optional().describe("Min height for responsive auto-layout"),
1674
+ maxHeight: import_zod4.z.coerce.number().optional().describe("Max height for responsive auto-layout"),
1675
+ componentId: import_zod4.z.string().describe("Component or component set ID"),
1676
+ variantProperties: import_zod4.z.record(import_zod4.z.string(), import_zod4.z.unknown()).optional().describe('Pick variant e.g. {"Style":"Secondary"}'),
1677
+ properties: import_zod4.z.record(import_zod4.z.string(), import_zod4.z.unknown()).optional().describe('Set component properties inline e.g. {"Label":"Click me", "ShowIcon":true}. Same as instances.update properties.'),
1678
+ name: import_zod4.z.string().optional().describe("Instance layer name"),
1679
+ x: import_zod4.z.coerce.number().optional(),
1680
+ y: import_zod4.z.coerce.number().optional(),
1681
+ width: import_zod4.z.coerce.number().optional().describe("Override width (resize)"),
1682
+ height: import_zod4.z.coerce.number().optional().describe("Override height (resize)"),
1683
+ parentId: import_zod4.z.string().optional().describe("Parent node ID. Omit to place on current page.")
1684
+ }).passthrough();
1685
+ try {
1686
+ params.items = import_zod4.z.array(itemSchema).parse(params.items);
1687
+ } catch (e) {
1688
+ if (e instanceof import_zod4.z.ZodError) {
1689
+ throw new Error(e.issues.map((i) => {
1690
+ const path = i.path.join(".");
1691
+ const shape = itemSchema instanceof import_zod4.z.ZodObject ? itemSchema.shape : null;
1692
+ const desc = shape?.[i.path[1]]?.description;
1693
+ return path + ": " + i.message + (desc ? " (expected: " + desc + ")" : "");
1694
+ }).join("; "));
1695
+ }
1696
+ throw e;
1697
+ }
1698
+ }
1699
+ if (m === "update") {
1700
+ const itemSchema = import_zod4.z.object({
1701
+ fills: import_zod4.z.array(import_zod4.z.record(import_zod4.z.string(), import_zod4.z.unknown())).optional().describe("Fill paints array \u2014 e.g. [{type: 'SOLID', color: '#hex'}] or [] for transparent. Primary way to set fills."),
1702
+ fillColor: colorRgba.optional().describe("Shorthand \u2014 sets a single solid fill (auto-binds to matching variable/style)"),
1703
+ fillStyleName: import_zod4.z.string().optional().describe("Paint style name for fill"),
1704
+ fillVariableName: import_zod4.z.string().optional().describe("Color variable by name e.g. 'bg/primary'"),
1705
+ strokes: import_zod4.z.array(import_zod4.z.record(import_zod4.z.string(), import_zod4.z.unknown())).optional().describe("Stroke paints array \u2014 e.g. [{type: 'SOLID', color: '#hex'}] or [] to clear. Primary way to set strokes."),
1706
+ strokeColor: colorRgba.optional().describe("Shorthand \u2014 sets a single solid stroke (auto-binds to matching variable/style)"),
1707
+ strokeStyleName: import_zod4.z.string().optional().describe("Paint style name for stroke"),
1708
+ strokeVariableName: import_zod4.z.string().optional().describe("Color variable by name for stroke"),
1709
+ strokeWeight: token.optional().describe("All sides (number) or variable name (string). Per-side: strokeTopWeight, strokeBottomWeight, strokeLeftWeight, strokeRightWeight."),
1710
+ strokeTopWeight: token.optional(),
1711
+ strokeBottomWeight: token.optional(),
1712
+ strokeLeftWeight: token.optional(),
1713
+ strokeRightWeight: token.optional(),
1714
+ strokeAlign: import_zod4.z.enum(["INSIDE", "OUTSIDE", "CENTER"]).optional().describe("Stroke position (default: INSIDE)"),
1715
+ strokesIncludedInLayout: flexBool(import_zod4.z.boolean()).optional().describe("Include stroke width in layout measurements (default: false)"),
1716
+ cornerRadius: token.optional().describe("All corners (number) or variable name (string). Per-corner: topLeftRadius, topRightRadius, bottomRightRadius, bottomLeftRadius."),
1717
+ topLeftRadius: token.optional(),
1718
+ topRightRadius: token.optional(),
1719
+ bottomRightRadius: token.optional(),
1720
+ bottomLeftRadius: token.optional(),
1721
+ opacity: token.optional().describe("Opacity (0-1) or variable name"),
1722
+ visible: import_zod4.z.boolean().optional().describe("Show/hide (default true)"),
1723
+ locked: import_zod4.z.boolean().optional().describe("Lock/unlock (default false)"),
1724
+ blendMode: import_zod4.z.enum(["PASS_THROUGH", "NORMAL", "DARKEN", "MULTIPLY", "LINEAR_BURN", "COLOR_BURN", "LIGHTEN", "SCREEN", "LINEAR_DODGE", "COLOR_DODGE", "OVERLAY", "SOFT_LIGHT", "HARD_LIGHT", "DIFFERENCE", "EXCLUSION", "HUE", "SATURATION", "COLOR", "LUMINOSITY"]).optional(),
1725
+ effectStyleName: import_zod4.z.string().optional().describe("Effect style name (e.g. 'Shadow/Card') for shadows, blurs"),
1726
+ layoutMode: import_zod4.z.enum(["NONE", "HORIZONTAL", "VERTICAL"]).optional().describe("Layout direction (default: NONE)"),
1727
+ layoutWrap: import_zod4.z.enum(["NO_WRAP", "WRAP"]).optional(),
1728
+ padding: token.optional().describe("All edges (number) or variable name (string). Per-edge: paddingTop, paddingRight, paddingBottom, paddingLeft."),
1729
+ paddingTop: token.optional(),
1730
+ paddingRight: token.optional(),
1731
+ paddingBottom: token.optional(),
1732
+ paddingLeft: token.optional(),
1733
+ primaryAxisAlignItems: import_zod4.z.enum(["MIN", "MAX", "CENTER", "SPACE_BETWEEN"]).optional(),
1734
+ counterAxisAlignItems: import_zod4.z.enum(["MIN", "MAX", "CENTER", "BASELINE"]).optional(),
1735
+ itemSpacing: token.optional().describe("Spacing between children (number or variable name string, default: 0)"),
1736
+ counterAxisSpacing: token.optional().describe("Gap between wrapped rows (requires layoutWrap: WRAP)"),
1737
+ layoutSizingHorizontal: import_zod4.z.enum(["FIXED", "HUG", "FILL"]).optional(),
1738
+ layoutSizingVertical: import_zod4.z.enum(["FIXED", "HUG", "FILL"]).optional(),
1739
+ layoutPositioning: import_zod4.z.enum(["AUTO", "ABSOLUTE"]).optional().describe("ABSOLUTE = floating inside auto-layout parent"),
1740
+ minWidth: import_zod4.z.coerce.number().optional().describe("Min width for responsive auto-layout"),
1741
+ maxWidth: import_zod4.z.coerce.number().optional().describe("Max width for responsive auto-layout"),
1742
+ minHeight: import_zod4.z.coerce.number().optional().describe("Min height for responsive auto-layout"),
1743
+ maxHeight: import_zod4.z.coerce.number().optional().describe("Max height for responsive auto-layout"),
1744
+ fontSize: import_zod4.z.number().optional().describe("Font size"),
1745
+ fontFamily: import_zod4.z.string().optional().describe("Font family"),
1746
+ fontStyle: import_zod4.z.string().optional().describe('Font variant e.g. "Bold", "Italic" \u2014 overrides fontWeight'),
1747
+ fontWeight: import_zod4.z.number().optional().describe("100-900. Ignored when fontStyle is set."),
1748
+ fontColor: colorRgba.optional().describe("Shorthand \u2014 sets text color (auto-binds to matching variable/style)"),
1749
+ fontColorVariableName: import_zod4.z.string().optional().describe("Bind color variable by name e.g. 'text/primary'"),
1750
+ fontColorStyleName: import_zod4.z.string().optional().describe("Apply paint style \u2014 overrides fontColor"),
1751
+ textStyleId: import_zod4.z.string().optional().describe("Apply text style by ID \u2014 overrides fontSize/fontWeight"),
1752
+ textStyleName: import_zod4.z.string().optional().describe("Text style by name (case-insensitive)"),
1753
+ textAlignHorizontal: import_zod4.z.enum(["LEFT", "CENTER", "RIGHT", "JUSTIFIED"]).optional(),
1754
+ textAlignVertical: import_zod4.z.enum(["TOP", "CENTER", "BOTTOM"]).optional(),
1755
+ textAutoResize: import_zod4.z.enum(["NONE", "WIDTH_AND_HEIGHT", "HEIGHT", "TRUNCATE"]).optional(),
1756
+ id: import_zod4.z.string().describe("Instance node ID"),
1757
+ properties: import_zod4.z.record(import_zod4.z.string(), import_zod4.z.unknown()).optional().describe("Component property key\u2192value map"),
1758
+ componentProperties: import_zod4.z.record(import_zod4.z.string(), import_zod4.z.unknown()).optional().describe("Alias for properties (matches instances.get response shape)"),
1759
+ name: import_zod4.z.string().optional().describe("Rename node"),
1760
+ rotation: import_zod4.z.number().optional().describe("Degrees (0-360)"),
1761
+ x: import_zod4.z.number().optional(),
1762
+ y: import_zod4.z.number().optional(),
1763
+ width: import_zod4.z.number().optional(),
1764
+ height: import_zod4.z.number().optional(),
1765
+ clearFill: import_zod4.z.boolean().optional().describe("Remove all fills"),
1766
+ effects: import_zod4.z.array(import_zod4.z.record(import_zod4.z.string(), import_zod4.z.unknown())).optional().describe("Effect array (DROP_SHADOW, INNER_SHADOW, LAYER_BLUR, BACKGROUND_BLUR)"),
1767
+ constraints: import_zod4.z.object({
1768
+ horizontal: import_zod4.z.enum(["MIN", "CENTER", "MAX", "STRETCH", "SCALE"]),
1769
+ vertical: import_zod4.z.enum(["MIN", "CENTER", "MAX", "STRETCH", "SCALE"])
1770
+ }).optional(),
1771
+ bindings: import_zod4.z.array(import_zod4.z.record(import_zod4.z.string(), import_zod4.z.unknown())).optional().describe("Bind variables to properties. field path examples: 'fills/0/color', 'strokes/0/color', 'opacity', 'width', 'cornerRadius', 'itemSpacing'."),
1772
+ explicitMode: import_zod4.z.record(import_zod4.z.string(), import_zod4.z.unknown()).optional().describe("Pin variable mode \u2014 use { collectionName, modeName } (preferred) or { collectionId, modeId }"),
1773
+ exportSettings: import_zod4.z.array(import_zod4.z.record(import_zod4.z.string(), import_zod4.z.unknown())).optional().describe("Export settings")
1774
+ }).passthrough();
1775
+ try {
1776
+ params.items = import_zod4.z.array(itemSchema).parse(params.items);
1777
+ } catch (e) {
1778
+ if (e instanceof import_zod4.z.ZodError) {
1779
+ throw new Error(e.issues.map((i) => {
1780
+ const path = i.path.join(".");
1781
+ const shape = itemSchema instanceof import_zod4.z.ZodObject ? itemSchema.shape : null;
1782
+ const desc = shape?.[i.path[1]]?.description;
1783
+ return path + ": " + i.message + (desc ? " (expected: " + desc + ")" : "");
1784
+ }).join("; "));
1785
+ }
1786
+ throw e;
1787
+ }
1788
+ }
1789
+ if (m === "swap") {
1790
+ const itemSchema = import_zod4.z.object({
1791
+ id: import_zod4.z.string().describe("Instance node ID"),
1792
+ componentId: import_zod4.z.string().describe("New component or component set ID")
1793
+ }).passthrough();
1794
+ try {
1795
+ params.items = import_zod4.z.array(itemSchema).parse(params.items);
1796
+ } catch (e) {
1797
+ if (e instanceof import_zod4.z.ZodError) {
1798
+ throw new Error(e.issues.map((i) => {
1799
+ const path = i.path.join(".");
1800
+ const shape = itemSchema instanceof import_zod4.z.ZodObject ? itemSchema.shape : null;
1801
+ const desc = shape?.[i.path[1]]?.description;
1802
+ return path + ": " + i.message + (desc ? " (expected: " + desc + ")" : "");
1803
+ }).join("; "));
1804
+ }
1805
+ throw e;
1806
+ }
1807
+ }
1808
+ if (m === "detach") {
1809
+ const itemSchema = import_zod4.z.object({
1810
+ id: import_zod4.z.string().describe("Instance node ID")
1811
+ }).passthrough();
1812
+ try {
1813
+ params.items = import_zod4.z.array(itemSchema).parse(params.items);
1814
+ } catch (e) {
1815
+ if (e instanceof import_zod4.z.ZodError) {
1816
+ throw new Error(e.issues.map((i) => {
1817
+ const path = i.path.join(".");
1818
+ const shape = itemSchema instanceof import_zod4.z.ZodObject ? itemSchema.shape : null;
1819
+ const desc = shape?.[i.path[1]]?.description;
1820
+ return path + ": " + i.message + (desc ? " (expected: " + desc + ")" : "");
1821
+ }).join("; "));
1822
+ }
1823
+ throw e;
1824
+ }
1825
+ }
1826
+ if (m === "reset_overrides") {
1827
+ const itemSchema = import_zod4.z.object({
1828
+ id: import_zod4.z.string().describe("Instance node ID")
1829
+ }).passthrough();
1830
+ try {
1831
+ params.items = import_zod4.z.array(itemSchema).parse(params.items);
1832
+ } catch (e) {
1833
+ if (e instanceof import_zod4.z.ZodError) {
1834
+ throw new Error(e.issues.map((i) => {
1835
+ const path = i.path.join(".");
1836
+ const shape = itemSchema instanceof import_zod4.z.ZodObject ? itemSchema.shape : null;
1837
+ const desc = shape?.[i.path[1]]?.description;
1838
+ return path + ": " + i.message + (desc ? " (expected: " + desc + ")" : "");
1839
+ }).join("; "));
1840
+ }
1841
+ throw e;
1842
+ }
1843
+ }
1844
+ },
1845
+ commandMap: { "list": "instances.list", "delete": "instances.delete", "clone": "instances.clone", "audit": "instances.audit", "reparent": "instances.reparent", "get": "instances.get", "create": "instances.create", "update": "instances.update", "swap": "instances.swap", "detach": "instances.detach", "reset_overrides": "instances.reset_overrides" }
1846
+ },
539
1847
  {
540
- name: "get_available_fonts",
541
- description: "Get available fonts in Figma. Optionally filter by query string.",
542
- schema: { query: import_zod12.z.string().optional().describe("Filter fonts by name (case-insensitive). Omit to list all fonts.") },
543
- tier: "read"
544
- }
545
- ];
546
-
547
- // src/tools/defs/lint.ts
548
- var import_zod13 = require("zod");
549
- var lintRules = import_zod13.z.enum([
550
- "no-autolayout",
551
- "shape-instead-of-frame",
552
- "hardcoded-color",
553
- "no-text-style",
554
- "fixed-in-autolayout",
555
- "default-name",
556
- "empty-container",
557
- "stale-text-name",
558
- "no-text-property",
559
- "wcag-contrast",
560
- "wcag-contrast-enhanced",
561
- "wcag-non-text-contrast",
562
- "wcag-target-size",
563
- "wcag-text-size",
564
- "wcag-line-height",
565
- "wcag",
566
- "all"
567
- ]);
568
- var tools12 = [
1848
+ name: "lint",
1849
+ description: '/** Run design quality and accessibility checks. Use method "help" for detailed parameter docs. */\n check (nodeId?, rules?, maxDepth?, maxFindings?) \u2192 { nodeId, nodeName, categories, warning? } // Run design linter on a node tree\n fix (items: { nodeId: string; layoutMode?: "VERTICAL" | "HORIZONTAL"; itemSpacing?: number }[], depth?) \u2192 { results: ("ok" | {error})[] } // Auto-fix frames to auto-layout\n// Lint runs automated design quality and accessibility checks on a node tree.',
1850
+ schema: (caps2) => filterMethodsByTier({
1851
+ method: import_zod4.z.enum(["check", "fix", "help"]),
1852
+ nodeId: import_zod4.z.string().optional().describe("Node ID to lint. If omitted: 1 selected node \u2192 lints that node, 2+ selected \u2192 lints entire page (not the selection), 0 selected \u2192 error. Always pass nodeId explicitly for reliable targeting."),
1853
+ rules: flexJson(import_zod4.z.array(import_zod4.z.string())).optional().describe('Rules to run. Default: ["all"]. Categories: "component", "composition", "token", "naming", "wcag"/"accessibility". Or specific rule names.'),
1854
+ maxDepth: import_zod4.z.coerce.number().optional().describe("Max tree depth (default: 10)"),
1855
+ maxFindings: import_zod4.z.coerce.number().optional().describe("Max findings (default: 50)"),
1856
+ items: flexJson(import_zod4.z.array(import_zod4.z.record(import_zod4.z.string(), import_zod4.z.unknown()))).optional().describe("Array of {nodeId, layoutMode?, itemSpacing?}"),
1857
+ depth: import_zod4.z.coerce.number().optional().describe("Response detail for fixed nodes: omit for stubs, 0=properties, -1=full tree"),
1858
+ topic: import_zod4.z.string().optional().describe('Help topic \u2014 method name for endpoint help, e.g. "create"')
1859
+ }, caps2, { "check": "read", "fix": "edit", "help": "read" }),
1860
+ tier: "read",
1861
+ validate: (params) => {
1862
+ const m = params.method;
1863
+ if (!params.items) return;
1864
+ if (m === "fix") {
1865
+ for (const it of params.items) {
1866
+ if (it.id !== void 0 && it.nodeId === void 0) {
1867
+ it.nodeId = it.id;
1868
+ delete it.id;
1869
+ }
1870
+ }
1871
+ const itemSchema = import_zod4.z.object({
1872
+ nodeId: import_zod4.z.string().describe("Frame node ID"),
1873
+ layoutMode: import_zod4.z.enum(["VERTICAL", "HORIZONTAL"]).optional().describe("Direction (default: auto-detected)"),
1874
+ itemSpacing: import_zod4.z.coerce.number().optional().describe("Spacing between children")
1875
+ }).passthrough();
1876
+ try {
1877
+ params.items = import_zod4.z.array(itemSchema).parse(params.items);
1878
+ } catch (e) {
1879
+ if (e instanceof import_zod4.z.ZodError) {
1880
+ throw new Error(e.issues.map((i) => {
1881
+ const path = i.path.join(".");
1882
+ const shape = itemSchema instanceof import_zod4.z.ZodObject ? itemSchema.shape : null;
1883
+ const desc = shape?.[i.path[1]]?.description;
1884
+ return path + ": " + i.message + (desc ? " (expected: " + desc + ")" : "");
1885
+ }).join("; "));
1886
+ }
1887
+ throw e;
1888
+ }
1889
+ }
1890
+ },
1891
+ commandMap: { "check": "lint.check", "fix": "lint.fix" }
1892
+ },
569
1893
  {
570
- name: "lint_node",
571
- description: "Run design linter on a node tree. Returns issues grouped by category with affected node IDs and fix instructions. Lint child nodes individually for large trees.",
572
- schema: {
573
- nodeId: import_zod13.z.string().optional().describe("Node ID to lint. Omit to lint current selection."),
574
- rules: flexJson(import_zod13.z.array(lintRules)).optional().describe('Rules to run. Default: ["all"]. Options: no-autolayout, shape-instead-of-frame, hardcoded-color, no-text-style, fixed-in-autolayout, default-name, empty-container, stale-text-name, no-text-property, all, wcag-contrast, wcag-contrast-enhanced, wcag-non-text-contrast, wcag-target-size, wcag-text-size, wcag-line-height, wcag'),
575
- maxDepth: import_zod13.z.coerce.number().optional().describe("Max depth to recurse (default: 10)"),
576
- maxFindings: import_zod13.z.coerce.number().optional().describe("Stop after N findings (default: 50)")
1894
+ name: "prototyping",
1895
+ description: '/** Manage prototype interactions, reactions, and navigation flows. Use method "help" for detailed parameter docs. */\n get (id) \u2192 { reactions?, overflowDirection? } // Get reactions and overflow direction on a node\n add (id, trigger: ON_CLICK|ON_HOVER|ON_PRESS|ON_DRAG|AFTER_TIMEOUT|MOUSE_ENTER|MOUSE_LEAVE|ON_KEY_DOWN, triggerDelay?, triggerKeyCodes?, triggerDevice?: KEYBOARD|XBOX_ONE|PS4|SWITCH_PRO, destination?, navigation?: NAVIGATE|SWAP|OVERLAY|SCROLL_TO|CHANGE_TO, transition?: DISSOLVE|SMART_ANIMATE|MOVE_IN|MOVE_OUT|PUSH|SLIDE_IN|SLIDE_OUT|INSTANT, transitionDirection?: LEFT|RIGHT|TOP|BOTTOM, duration?, easing?: EASE_IN|EASE_OUT|EASE_IN_AND_OUT|LINEAR|GENTLE|QUICK|BOUNCY|SLOW, actionType?: NODE|BACK|CLOSE|URL|SET_VARIABLE_MODE, url?, collectionName?, modeName?, resetScrollPosition?, actions?) \u2192 { results: "ok"[] } // Add a prototype reaction to a node\n set (id, reactions) \u2192 { results: "ok"[] } // Replace all reactions on a node (raw reactions array)\n remove (id, index) \u2192 { results: "ok"[] } // Remove a reaction from a node by index\n// Reactions wire up interactions: trigger (ON_CLICK, ON_HOVER, ...) \u2192 action (navigate, swap, overlay).\n// Common patterns: button ON_CLICK \u2192 NAVIGATE to detail frame; card ON_HOVER \u2192 CHANGE_TO hover variant.\n// Multi-action: pass actions[] array to run multiple actions on one trigger (e.g. navigate + set variable mode).',
1896
+ schema: (caps2) => filterMethodsByTier({
1897
+ method: import_zod4.z.enum(["get", "add", "set", "remove", "help"]),
1898
+ id: import_zod4.z.string().optional().describe("Node ID"),
1899
+ trigger: import_zod4.z.enum(["ON_CLICK", "ON_HOVER", "ON_PRESS", "ON_DRAG", "AFTER_TIMEOUT", "MOUSE_ENTER", "MOUSE_LEAVE", "ON_KEY_DOWN"]).optional().describe("Trigger type"),
1900
+ triggerDelay: import_zod4.z.coerce.number().optional().describe("Delay in ms for AFTER_TIMEOUT / MOUSE_ENTER / MOUSE_LEAVE triggers"),
1901
+ triggerKeyCodes: import_zod4.z.unknown().optional().describe("Key codes for ON_KEY_DOWN trigger"),
1902
+ triggerDevice: import_zod4.z.enum(["KEYBOARD", "XBOX_ONE", "PS4", "SWITCH_PRO"]).optional().describe("Device for ON_KEY_DOWN (default: KEYBOARD)"),
1903
+ destination: import_zod4.z.string().optional().describe("Target node ID (required for NODE actions)"),
1904
+ navigation: import_zod4.z.enum(["NAVIGATE", "SWAP", "OVERLAY", "SCROLL_TO", "CHANGE_TO"]).optional().describe("Navigation type (default: NAVIGATE)"),
1905
+ transition: import_zod4.z.enum(["DISSOLVE", "SMART_ANIMATE", "MOVE_IN", "MOVE_OUT", "PUSH", "SLIDE_IN", "SLIDE_OUT", "INSTANT"]).optional().describe("Transition animation (default: DISSOLVE). INSTANT = no animation."),
1906
+ transitionDirection: import_zod4.z.enum(["LEFT", "RIGHT", "TOP", "BOTTOM"]).optional().describe("Direction for MOVE_IN, MOVE_OUT, PUSH, SLIDE_IN, SLIDE_OUT"),
1907
+ duration: import_zod4.z.coerce.number().optional().describe("Transition duration in seconds (default: 0.3)"),
1908
+ easing: import_zod4.z.enum(["EASE_IN", "EASE_OUT", "EASE_IN_AND_OUT", "LINEAR", "GENTLE", "QUICK", "BOUNCY", "SLOW"]).optional().describe("Easing function (default: EASE_OUT)"),
1909
+ actionType: import_zod4.z.enum(["NODE", "BACK", "CLOSE", "URL", "SET_VARIABLE_MODE"]).optional().describe("Action type (default: NODE). SET_VARIABLE_MODE switches a variable collection mode."),
1910
+ url: import_zod4.z.string().optional().describe("URL for URL action type"),
1911
+ collectionName: import_zod4.z.string().optional().describe("Variable collection name (for SET_VARIABLE_MODE)"),
1912
+ modeName: import_zod4.z.string().optional().describe("Mode name to switch to (for SET_VARIABLE_MODE)"),
1913
+ resetScrollPosition: flexBool(import_zod4.z.boolean()).optional().describe("Reset scroll position on navigate (default: true)"),
1914
+ actions: flexJson(import_zod4.z.array(import_zod4.z.record(import_zod4.z.string(), import_zod4.z.unknown()))).optional().describe("Multi-action: [{actionType, destination?, navigation?, collectionName?, modeName?, ...}]. Overrides single-action params."),
1915
+ reactions: flexJson(import_zod4.z.array(import_zod4.z.record(import_zod4.z.string(), import_zod4.z.unknown()))).optional().describe("Full reactions array \u2014 [{trigger:{type}, actions:[{type, destinationId, navigation, transition}]}]"),
1916
+ index: import_zod4.z.coerce.number().optional().describe("Reaction index (0-based)"),
1917
+ topic: import_zod4.z.string().optional().describe('Help topic \u2014 method name for endpoint help, e.g. "create"')
1918
+ }, caps2, { "get": "read", "add": "edit", "set": "edit", "remove": "edit", "help": "read" }),
1919
+ tier: "read",
1920
+ validate: (params) => {
1921
+ const m = params.method;
1922
+ if (m === "get") {
1923
+ if (params.id === void 0) throw new Error('get requires "id"');
1924
+ }
1925
+ if (m === "add") {
1926
+ if (params.id === void 0) throw new Error('add requires "id"');
1927
+ if (params.trigger === void 0) throw new Error('add requires "trigger"');
1928
+ }
1929
+ if (m === "set") {
1930
+ if (params.id === void 0) throw new Error('set requires "id"');
1931
+ if (params.reactions === void 0) throw new Error('set requires "reactions"');
1932
+ }
1933
+ if (m === "remove") {
1934
+ if (params.id === void 0) throw new Error('remove requires "id"');
1935
+ if (params.index === void 0) throw new Error('remove requires "index"');
1936
+ }
577
1937
  },
578
- tier: "read"
1938
+ commandMap: { "get": "prototyping.get", "add": "prototyping.add", "set": "prototyping.set", "remove": "prototyping.remove" }
579
1939
  },
580
1940
  {
581
- name: "lint_fix_autolayout",
582
- description: "Auto-fix: convert frames with multiple children to auto-layout. Takes node IDs from lint_node 'no-autolayout' results.",
583
- schema: {
584
- items: flexJson(import_zod13.z.array(import_zod13.z.object({
585
- nodeId,
586
- layoutMode: import_zod13.z.enum(["HORIZONTAL", "VERTICAL"]).optional().describe("Layout direction (default: auto-detect based on child positions)"),
587
- itemSpacing: import_zod13.z.coerce.number().optional().describe("Spacing between children (default: 0)")
588
- }))).describe("Array of frames to convert to auto-layout"),
589
- depth
1941
+ name: "selection",
1942
+ description: '/** Read and set the current Figma selection. Use method "help" for detailed parameter docs. */\n get (depth?, verbose?) \u2192 { results, _truncated?, _notice? } // Get the current selection\n set (nodeIds) \u2192 { count, selectedNodes, notFoundIds? } // Set selection to nodes and scroll viewport to show them\n// Selection is the set of nodes currently highlighted in the Figma canvas.\n// get returns the current selection. Without depth, returns stubs ({id, name, type}). With depth=0, returns full properties.\n// _truncated: true when the response was cut short due to node budget limits. Use depth=0 or specific fields to reduce payload.\n// set replaces the entire selection AND scrolls the viewport to show the selected nodes.',
1943
+ schema: (caps2) => filterMethodsByTier({
1944
+ method: import_zod4.z.enum(["get", "set", "help"]),
1945
+ depth: import_zod4.z.coerce.number().optional().describe("Child recursion depth. Omit for stubs only, 0=selected nodes' properties, -1=unlimited."),
1946
+ verbose: import_zod4.z.boolean().optional().describe("Include all properties (bounding box, constraints, text style details). Default false \u2014 returns slim, actionable output."),
1947
+ nodeIds: flexJson(import_zod4.z.array(import_zod4.z.string())).optional().describe('Array of node IDs to select. Example: ["1:2","1:3"]'),
1948
+ topic: import_zod4.z.string().optional().describe('Help topic \u2014 method name for endpoint help, e.g. "create"')
1949
+ }, caps2, { "get": "read", "set": "read", "help": "read" }),
1950
+ tier: "read",
1951
+ validate: (params) => {
1952
+ const m = params.method;
1953
+ if (m === "set") {
1954
+ if (params.nodeIds === void 0) throw new Error('set requires "nodeIds"');
1955
+ }
590
1956
  },
591
- tier: "edit"
592
- }
593
- ];
594
-
595
- // src/tools/defs/styles.ts
596
- var import_zod15 = require("zod");
597
-
598
- // src/tools/endpoint.ts
599
- var import_zod14 = require("zod");
600
- var DEFAULT_TIERS = {
601
- get: "read",
602
- list: "read",
603
- create: "create",
604
- update: "edit",
605
- delete: "edit"
606
- };
607
- function endpointSchema(methods, capsOrExtra, extraOrTiers, methodTiers) {
608
- let caps2;
609
- let extra;
610
- if (capsOrExtra && "create" in capsOrExtra && "edit" in capsOrExtra && typeof capsOrExtra.create === "boolean") {
611
- caps2 = capsOrExtra;
612
- extra = extraOrTiers;
613
- } else {
614
- extra = capsOrExtra;
615
- }
616
- let filtered = methods;
617
- if (caps2) {
618
- const tiers = { ...DEFAULT_TIERS, ...methodTiers };
619
- filtered = methods.filter((m) => {
620
- const tier = tiers[m] ?? "edit";
621
- if (tier === "read") return true;
622
- if (tier === "create") return caps2.create;
623
- if (tier === "edit") return caps2.edit;
624
- return false;
625
- });
626
- }
627
- const schema = {
628
- method: import_zod14.z.enum(filtered)
629
- };
630
- if (filtered.includes("get") || filtered.includes("delete")) {
631
- schema.id = import_zod14.z.string().optional().describe("Resource ID (get, delete)");
632
- }
633
- if (filtered.includes("get") || filtered.includes("list")) {
634
- schema.fields = flexJson(import_zod14.z.array(import_zod14.z.string())).optional().describe('Property whitelist (get/list). Identity fields (id, name, type) always included. Omit for stubs on list, full detail on get. Pass ["*"] for all fields.');
635
- }
636
- if (filtered.includes("list")) {
637
- schema.offset = import_zod14.z.coerce.number().optional().describe("Skip N items for pagination (default 0)");
638
- schema.limit = import_zod14.z.coerce.number().optional().describe("Max items per page (default 100)");
639
- }
640
- return { ...schema, ...extra };
641
- }
642
-
643
- // src/tools/defs/styles.ts
644
- var paintStyleItem = import_zod15.z.object({
645
- name: import_zod15.z.string().describe("Style name"),
646
- color: flexJson(colorRgba).describe("Color.")
647
- });
648
- var textStyleItem = import_zod15.z.object({
649
- name: import_zod15.z.string().describe("Style name"),
650
- fontFamily: import_zod15.z.string().describe("Font family"),
651
- fontStyle: import_zod15.z.string().optional().describe("Font style (default: Regular)"),
652
- fontSize: import_zod15.z.coerce.number().describe("Font size"),
653
- lineHeight: flexNum(import_zod15.z.union([
654
- import_zod15.z.number(),
655
- import_zod15.z.object({ value: import_zod15.z.coerce.number(), unit: import_zod15.z.enum(["PIXELS", "PERCENT", "AUTO"]) })
656
- ])).optional().describe("Line height \u2014 number (px) or {value, unit}. Default: auto."),
657
- letterSpacing: flexNum(import_zod15.z.union([
658
- import_zod15.z.number(),
659
- import_zod15.z.object({ value: import_zod15.z.coerce.number(), unit: import_zod15.z.enum(["PIXELS", "PERCENT"]) })
660
- ])).optional().describe("Letter spacing \u2014 number (px) or {value, unit}. Default: 0."),
661
- textCase: import_zod15.z.enum(["ORIGINAL", "UPPER", "LOWER", "TITLE"]).optional(),
662
- textDecoration: import_zod15.z.enum(["NONE", "UNDERLINE", "STRIKETHROUGH"]).optional()
663
- });
664
- var effectStyleItem = import_zod15.z.object({
665
- name: import_zod15.z.string().describe("Style name"),
666
- effects: flexJson(import_zod15.z.array(effectEntry)).describe("Array of effects")
667
- });
668
- var patchBase = {
669
- id: import_zod15.z.string().describe("Style ID or name (case-insensitive match)"),
670
- name: import_zod15.z.string().optional().describe("Rename the style")
671
- };
672
- var patchPaintItem = import_zod15.z.object({
673
- ...patchBase,
674
- color: flexJson(colorRgba).optional().describe("New color.")
675
- });
676
- var patchTextItem = import_zod15.z.object({
677
- ...patchBase,
678
- fontFamily: import_zod15.z.string().optional().describe("Font family"),
679
- fontStyle: import_zod15.z.string().optional().describe("Font style, e.g. Regular, Bold"),
680
- fontSize: import_zod15.z.coerce.number().optional().describe("Font size"),
681
- lineHeight: flexNum(import_zod15.z.union([
682
- import_zod15.z.number(),
683
- import_zod15.z.object({ value: import_zod15.z.coerce.number(), unit: import_zod15.z.enum(["PIXELS", "PERCENT", "AUTO"]) })
684
- ])).optional().describe("Line height \u2014 number (px) or {value, unit}"),
685
- letterSpacing: flexNum(import_zod15.z.union([
686
- import_zod15.z.number(),
687
- import_zod15.z.object({ value: import_zod15.z.coerce.number(), unit: import_zod15.z.enum(["PIXELS", "PERCENT"]) })
688
- ])).optional().describe("Letter spacing \u2014 number (px) or {value, unit}"),
689
- textCase: import_zod15.z.enum(["ORIGINAL", "UPPER", "LOWER", "TITLE"]).optional(),
690
- textDecoration: import_zod15.z.enum(["NONE", "UNDERLINE", "STRIKETHROUGH"]).optional()
691
- });
692
- var patchEffectItem = import_zod15.z.object({
693
- ...patchBase,
694
- effects: flexJson(import_zod15.z.array(effectEntry)).optional().describe("Array of effects")
695
- });
696
- var patchAnyItem = import_zod15.z.object({
697
- ...patchBase,
698
- color: flexJson(colorRgba).optional(),
699
- fontFamily: import_zod15.z.string().optional(),
700
- fontStyle: import_zod15.z.string().optional(),
701
- fontSize: import_zod15.z.coerce.number().optional(),
702
- lineHeight: flexNum(import_zod15.z.union([
703
- import_zod15.z.number(),
704
- import_zod15.z.object({ value: import_zod15.z.coerce.number(), unit: import_zod15.z.enum(["PIXELS", "PERCENT", "AUTO"]) })
705
- ])).optional(),
706
- letterSpacing: flexNum(import_zod15.z.union([
707
- import_zod15.z.number(),
708
- import_zod15.z.object({ value: import_zod15.z.coerce.number(), unit: import_zod15.z.enum(["PIXELS", "PERCENT"]) })
709
- ])).optional(),
710
- textCase: import_zod15.z.enum(["ORIGINAL", "UPPER", "LOWER", "TITLE"]).optional(),
711
- textDecoration: import_zod15.z.enum(["NONE", "UNDERLINE", "STRIKETHROUGH"]).optional(),
712
- effects: flexJson(import_zod15.z.array(effectEntry)).optional()
713
- });
714
- var createSchemas = {
715
- paint: paintStyleItem,
716
- text: textStyleItem,
717
- effect: effectStyleItem
718
- };
719
- var updateSchemas = {
720
- paint: patchPaintItem,
721
- text: patchTextItem,
722
- effect: patchEffectItem
723
- };
724
- var tools13 = [
1957
+ commandMap: { "get": "selection.get", "set": "selection.set" }
1958
+ },
725
1959
  {
726
1960
  name: "styles",
727
- description: "CRUD endpoint for local styles (paint, text, effect).\n list \u2192 {type?, fields?, offset?, limit?} \u2192 {totalCount, items: [{id, name, type, ...}]}\n get \u2192 {id, fields?} \u2192 style object (full detail; fields to filter)\n create \u2192 {type, items: [...]} \u2192 {results: [{id}, ...]}\n update \u2192 {type?, items: [{id, ...}]} \u2192 {results: ['ok'|{warning}, ...]}\n delete \u2192 {id} or {items: [{id}, ...]} \u2192 'ok' or {results: ['ok', ...]}",
728
- schema: (caps2) => endpointSchema(
729
- ["create", "get", "list", "update", "delete"],
730
- caps2,
731
- {
732
- type: import_zod15.z.enum(["paint", "text", "effect"]).optional().describe("Style type. Required for create. Filters list by type. Optional for update (strict per-type validation; omit to auto-detect)."),
733
- items: flexJson(import_zod15.z.array(import_zod15.z.any())).optional().describe("Create: [{name, color}] (paint), [{name, fontFamily, fontSize, ...}] (text), [{name, effects}] (effect). Update: [{id, ...fields}]. Delete (batch): [{id}, ...]."),
734
- depth
1961
+ description: '/** CRUD for local paint, text, effect, and grid styles. Use method "help" for detailed parameter docs. */\n list (type?: paint|text|effect|grid, fields?, offset?, limit?) \u2192 { totalCount, items } // List local styles with optional type filter\n get (id, fields?) \u2192 { id, name, type, paints?, fontFamily?, fontSize?, effects?, layoutGrids? } // Get full style detail by ID\n create (type: paint|text|effect|grid, items: (PaintItem | TextItem | EffectItem | GridItem)[]) \u2192 { results: {id}[] } // Create local styles\n update (type?: paint|text|effect|grid, items: PatchStyleItem[]) \u2192 { results: ("ok" | {error})[] } // Update styles by ID or name\n delete (id?, items?: { id: string }[]) \u2192 { results: "ok"[] } // Delete styles\n// Styles are named, reusable design properties that can be applied to nodes. Four types:\n// paint: a named color (applied to fills/strokes), text: typography settings, effect: shadows/blurs, grid: layout grids.\n// All ID params accept both IDs and display names (case-insensitive). Use whichever you have.',
1962
+ schema: (caps2) => filterMethodsByTier({
1963
+ method: import_zod4.z.enum(["list", "get", "create", "update", "delete", "help"]),
1964
+ type: import_zod4.z.enum(["paint", "text", "effect", "grid"]).optional().describe("Filter by style type"),
1965
+ fields: flexJson(import_zod4.z.array(import_zod4.z.string())).optional().describe('Property whitelist. Identity fields (id, name, type) always included. Omit for stubs on list, full on get. Pass ["*"] for all.'),
1966
+ offset: import_zod4.z.coerce.number().optional().default(0).describe("Skip N items for pagination (default 0)"),
1967
+ limit: import_zod4.z.coerce.number().optional().default(100).describe("Max items per page (default 100)"),
1968
+ id: import_zod4.z.string().optional().describe("Style ID or name"),
1969
+ items: flexJson(import_zod4.z.array(import_zod4.z.record(import_zod4.z.string(), import_zod4.z.unknown()))).optional().describe("Array of {id, ...properties} to create/update/delete"),
1970
+ topic: import_zod4.z.string().optional().describe('Help topic \u2014 method name for endpoint help, e.g. "create"')
1971
+ }, caps2, { "list": "read", "get": "read", "create": "create", "update": "edit", "delete": "edit", "help": "read" }),
1972
+ tier: "read",
1973
+ validate: (params) => {
1974
+ const m = params.method;
1975
+ if (m === "get") {
1976
+ if (params.id === void 0) throw new Error('get requires "id"');
735
1977
  }
736
- ),
1978
+ if (!params.items) return;
1979
+ if (m === "create") {
1980
+ const schemas = {
1981
+ "paint": import_zod4.z.object({
1982
+ name: import_zod4.z.string().describe("Style name"),
1983
+ color: colorRgba.describe("Color value"),
1984
+ colorVariableName: import_zod4.z.string().optional().describe("Bind to a COLOR variable by name (style tracks the variable)"),
1985
+ description: import_zod4.z.string().optional().describe("Style description")
1986
+ }).passthrough(),
1987
+ "text": import_zod4.z.object({
1988
+ name: import_zod4.z.string().describe("Style name"),
1989
+ fontFamily: import_zod4.z.string().describe("Font family"),
1990
+ fontStyle: import_zod4.z.string().optional().describe("Font style (default: Regular)"),
1991
+ fontSize: import_zod4.z.coerce.number().describe("Font size"),
1992
+ lineHeight: lineHeight.optional(),
1993
+ letterSpacing: letterSpacing.optional(),
1994
+ textCase: import_zod4.z.enum(["ORIGINAL", "UPPER", "LOWER", "TITLE", "SMALL_CAPS", "SMALL_CAPS_FORCED"]).optional(),
1995
+ textDecoration: import_zod4.z.enum(["NONE", "UNDERLINE", "STRIKETHROUGH"]).optional(),
1996
+ paragraphIndent: import_zod4.z.coerce.number().optional().describe("Paragraph indent (px)"),
1997
+ paragraphSpacing: import_zod4.z.coerce.number().optional().describe("Paragraph spacing (px)"),
1998
+ leadingTrim: import_zod4.z.enum(["CAP_HEIGHT", "NONE"]).optional().describe("Leading trim mode"),
1999
+ description: import_zod4.z.string().optional().describe("Style description")
2000
+ }).passthrough(),
2001
+ "effect": import_zod4.z.object({
2002
+ name: import_zod4.z.string().describe("Style name"),
2003
+ effects: flexJson(import_zod4.z.array(import_zod4.z.record(import_zod4.z.string(), import_zod4.z.unknown()))).describe("Array of Effect objects"),
2004
+ description: import_zod4.z.string().optional().describe("Style description")
2005
+ }).passthrough(),
2006
+ "grid": import_zod4.z.object({
2007
+ name: import_zod4.z.string().describe("Style name"),
2008
+ layoutGrids: flexJson(import_zod4.z.array(import_zod4.z.record(import_zod4.z.string(), import_zod4.z.unknown()))).describe("Array of LayoutGrid objects"),
2009
+ description: import_zod4.z.string().optional().describe("Style description")
2010
+ }).passthrough()
2011
+ };
2012
+ const s = params.type && schemas[params.type];
2013
+ if (s) {
2014
+ try {
2015
+ params.items = import_zod4.z.array(s).parse(params.items);
2016
+ } catch (e) {
2017
+ if (e instanceof import_zod4.z.ZodError) {
2018
+ throw new Error(e.issues.map((i) => {
2019
+ const path = i.path.join(".");
2020
+ const shape = s instanceof import_zod4.z.ZodObject ? s.shape : null;
2021
+ const desc = shape?.[i.path[1]]?.description;
2022
+ return path + ": " + i.message + (desc ? " (expected: " + desc + ")" : "");
2023
+ }).join("; "));
2024
+ }
2025
+ throw e;
2026
+ }
2027
+ }
2028
+ }
2029
+ if (m === "update") {
2030
+ const itemSchema = import_zod4.z.object({
2031
+ id: import_zod4.z.string().describe("Style ID or name"),
2032
+ name: import_zod4.z.string().optional().describe("Rename the style"),
2033
+ description: import_zod4.z.string().optional().describe("Style description"),
2034
+ color: colorRgba.optional().describe("New color (paint styles)"),
2035
+ colorVariableName: import_zod4.z.string().optional().describe("Bind to a COLOR variable by name (paint styles)"),
2036
+ fontFamily: import_zod4.z.string().optional(),
2037
+ fontStyle: import_zod4.z.string().optional(),
2038
+ fontSize: import_zod4.z.coerce.number().optional(),
2039
+ lineHeight: lineHeight.optional(),
2040
+ letterSpacing: letterSpacing.optional(),
2041
+ textCase: import_zod4.z.enum(["ORIGINAL", "UPPER", "LOWER", "TITLE", "SMALL_CAPS", "SMALL_CAPS_FORCED"]).optional(),
2042
+ textDecoration: import_zod4.z.enum(["NONE", "UNDERLINE", "STRIKETHROUGH"]).optional(),
2043
+ paragraphIndent: import_zod4.z.coerce.number().optional().describe("Paragraph indent (px)"),
2044
+ paragraphSpacing: import_zod4.z.coerce.number().optional().describe("Paragraph spacing (px)"),
2045
+ leadingTrim: import_zod4.z.enum(["CAP_HEIGHT", "NONE"]).optional(),
2046
+ effects: flexJson(import_zod4.z.array(import_zod4.z.record(import_zod4.z.string(), import_zod4.z.unknown()))).optional().describe("Array of Effect objects"),
2047
+ layoutGrids: flexJson(import_zod4.z.array(import_zod4.z.record(import_zod4.z.string(), import_zod4.z.unknown()))).optional().describe("Array of LayoutGrid objects (grid styles)")
2048
+ }).passthrough();
2049
+ try {
2050
+ params.items = import_zod4.z.array(itemSchema).parse(params.items);
2051
+ } catch (e) {
2052
+ if (e instanceof import_zod4.z.ZodError) {
2053
+ throw new Error(e.issues.map((i) => {
2054
+ const path = i.path.join(".");
2055
+ const shape = itemSchema instanceof import_zod4.z.ZodObject ? itemSchema.shape : null;
2056
+ const desc = shape?.[i.path[1]]?.description;
2057
+ return path + ": " + i.message + (desc ? " (expected: " + desc + ")" : "");
2058
+ }).join("; "));
2059
+ }
2060
+ throw e;
2061
+ }
2062
+ }
2063
+ if (m === "delete") {
2064
+ const itemSchema = import_zod4.z.object({
2065
+ id: import_zod4.z.string().describe("Style ID or name")
2066
+ }).passthrough();
2067
+ try {
2068
+ params.items = import_zod4.z.array(itemSchema).parse(params.items);
2069
+ } catch (e) {
2070
+ if (e instanceof import_zod4.z.ZodError) {
2071
+ throw new Error(e.issues.map((i) => {
2072
+ const path = i.path.join(".");
2073
+ const shape = itemSchema instanceof import_zod4.z.ZodObject ? itemSchema.shape : null;
2074
+ const desc = shape?.[i.path[1]]?.description;
2075
+ return path + ": " + i.message + (desc ? " (expected: " + desc + ")" : "");
2076
+ }).join("; "));
2077
+ }
2078
+ throw e;
2079
+ }
2080
+ }
2081
+ },
2082
+ commandMap: { "list": "styles.list", "get": "styles.get", "create": "styles.create", "update": "styles.update", "delete": "styles.delete" }
2083
+ },
2084
+ {
2085
+ name: "text",
2086
+ description: '/** Create and manage text nodes. Use method "help" for detailed parameter docs. */\n get (id, fields?, depth?, verbose?) \u2192 { results: Node[], _truncated?, _notice? } // Get serialized node data\n list (query?, types?, parentId?, fields?, offset?, limit?) \u2192 { totalCount, returned?, offset?, limit?, results } // Search for nodes (returns stubs only \u2014 use get with depth for full properties)\n update (items: PatchItem[]) \u2192 { results: ("ok" | {error})[] } // Patch node properties\n delete (id?, items?: { id?: string }[]) \u2192 { results: "ok"[] } // Delete nodes\n clone (id, parentId?, x?, y?, depth?) \u2192 { results: {id}[] } // Duplicate nodes\n audit (id, rules?, maxDepth?, maxFindings?) \u2192 { nodeId?, nodeName?, categories? } // Run lint on a node \u2014 returns severity-ranked findings\n reparent (items: { id: string; parentId: string; index?: number }[]) \u2192 { results: "ok"[] } // Move nodes into a new parent\n create (items: TextItem[], depth?) \u2192 { results: {id}[] } // Create text nodes\n set_content(items: { nodeId: string; text: string }[], depth?) \u2192 { results: "ok"[] } // Replace text content on existing text nodes\n scan (items: { nodeId: string; limit?: number; includePath?: boolean; includeGeometry?: boolean }[]) \u2192 { results: ("ok" | {error})[] } // Scan all text nodes within a subtree\n// depth: omit \u2192 id+name stubs | 0 \u2192 props + child stubs | N \u2192 recurse N | -1 \u2192 full tree\n// fields: whitelist e.g. ["fills","opacity"] \u2014 id, name, type always included. Pass ["*"] for all.\n// layoutSizingHorizontal/Vertical: FIXED | HUG | FILL \u2014 how the node sizes within auto-layout.\n// Colors: fillVariableName/strokeVariableName bind by name \u2014 preferred over raw color values.\n// Note: node-based endpoints (frames, text, instances, components) use `results` as the list key.\n// Standalone endpoints (styles, variables, variable_collections) use `items`. Components.list uses `items` (catalog view).',
2087
+ schema: (caps2) => filterMethodsByTier({
2088
+ method: import_zod4.z.enum(["get", "list", "update", "delete", "clone", "audit", "reparent", "create", "set_content", "scan", "help"]),
2089
+ id: import_zod4.z.string().optional().describe("Node ID"),
2090
+ fields: flexJson(import_zod4.z.array(import_zod4.z.string())).optional().describe('Property whitelist. Identity fields (id, name, type) always included. Omit for stubs on list, full on get. Pass ["*"] for all.'),
2091
+ depth: import_zod4.z.coerce.number().optional().describe("Response detail: omit for id+name only. 0=properties + child stubs. N=recurse N levels. -1=unlimited."),
2092
+ verbose: import_zod4.z.boolean().optional().describe("Include all properties (bounding box, constraints, text style details). Default false \u2014 returns slim, actionable output."),
2093
+ query: import_zod4.z.string().optional().describe("Name search query (case-insensitive substring match)"),
2094
+ types: flexJson(import_zod4.z.array(import_zod4.z.string())).optional().describe('Filter by node types (e.g. ["FRAME", "TEXT"])'),
2095
+ parentId: import_zod4.z.string().optional().describe("Search only within this subtree"),
2096
+ offset: import_zod4.z.coerce.number().optional().default(0).describe("Skip N items for pagination (default 0)"),
2097
+ limit: import_zod4.z.coerce.number().optional().default(100).describe("Max items per page (default 100)"),
2098
+ items: flexJson(import_zod4.z.array(import_zod4.z.record(import_zod4.z.string(), import_zod4.z.unknown()))).optional().describe("Array of {id, ...properties} to update/delete/reparent/create/set_content/scan"),
2099
+ x: import_zod4.z.coerce.number().optional().describe("X position (default: 0)"),
2100
+ y: import_zod4.z.coerce.number().optional().describe("Y position (default: 0)"),
2101
+ rules: flexJson(import_zod4.z.array(import_zod4.z.string())).optional().describe('Rules to check. Default: ["all"]. Categories: "component", "composition", "token", "naming", "wcag".'),
2102
+ maxDepth: import_zod4.z.coerce.number().optional().describe("Max tree depth (default: 10)"),
2103
+ maxFindings: import_zod4.z.coerce.number().optional().describe("Max findings (default: 50)"),
2104
+ topic: import_zod4.z.string().optional().describe('Help topic \u2014 method name for endpoint help, e.g. "create"')
2105
+ }, caps2, { "get": "read", "list": "read", "update": "edit", "delete": "edit", "clone": "create", "audit": "read", "reparent": "edit", "create": "create", "set_content": "edit", "scan": "read", "help": "read" }),
737
2106
  tier: "read",
738
2107
  validate: (params) => {
739
- if (params.items) {
740
- const map = params.method === "update" ? updateSchemas : createSchemas;
741
- const itemSchema = params.type && map[params.type] || patchAnyItem;
742
- params.items = import_zod15.z.array(itemSchema).parse(params.items);
2108
+ const m = params.method;
2109
+ if (!params.items) return;
2110
+ if (m === "create") {
2111
+ for (const it of params.items) {
2112
+ if (it.characters !== void 0 && it.text === void 0) {
2113
+ it.text = it.characters;
2114
+ delete it.characters;
2115
+ }
2116
+ }
2117
+ const itemSchema = import_zod4.z.object({
2118
+ text: import_zod4.z.string().optional().describe("Text content"),
2119
+ name: import_zod4.z.string().optional().describe("Layer name"),
2120
+ x: import_zod4.z.number().optional(),
2121
+ y: import_zod4.z.number().optional(),
2122
+ width: import_zod4.z.number().optional().describe("Fixed width in px \u2014 implies layoutSizingHorizontal: FIXED and textAutoResize: HEIGHT"),
2123
+ parentId: import_zod4.z.string().optional().describe("Parent node ID. Omit to place on current page."),
2124
+ fontFamily: import_zod4.z.string().optional().describe("Font family (default: Inter). Use fonts.list to find installed fonts."),
2125
+ fontStyle: import_zod4.z.string().optional().describe('Font variant e.g. "Bold", "Italic" \u2014 overrides fontWeight'),
2126
+ fontSize: import_zod4.z.number().optional().describe("Font size (default: 14)"),
2127
+ fontWeight: import_zod4.z.number().optional().describe("100-900 (default: 400). Ignored when fontStyle is set."),
2128
+ fills: import_zod4.z.array(import_zod4.z.record(import_zod4.z.string(), import_zod4.z.unknown())).optional().describe("Text color paints \u2014 e.g. [{type: 'SOLID', color: '#hex'}]. Same as node fills."),
2129
+ fontColor: colorRgba.optional().describe("Shorthand \u2014 sets text color (auto-binds to matching variable/style)"),
2130
+ fontColorVariableName: import_zod4.z.string().optional().describe("Bind color variable by name e.g. 'text/primary'"),
2131
+ fontColorStyleName: import_zod4.z.string().optional().describe("Apply paint style \u2014 overrides fontColor"),
2132
+ textStyleId: import_zod4.z.string().optional().describe("Text style ID or name (case-insensitive) \u2014 overrides fontSize/fontWeight"),
2133
+ textStyleName: import_zod4.z.string().optional().describe("Alias for textStyleId \u2014 accepts name (case-insensitive)"),
2134
+ textAlignHorizontal: import_zod4.z.enum(["LEFT", "CENTER", "RIGHT", "JUSTIFIED"]).optional(),
2135
+ textAlignVertical: import_zod4.z.enum(["TOP", "CENTER", "BOTTOM"]).optional(),
2136
+ layoutSizingHorizontal: import_zod4.z.enum(["FIXED", "HUG", "FILL"]).optional(),
2137
+ layoutSizingVertical: import_zod4.z.enum(["FIXED", "HUG", "FILL"]).optional(),
2138
+ textAutoResize: import_zod4.z.enum(["NONE", "WIDTH_AND_HEIGHT", "HEIGHT", "TRUNCATE"]).optional().describe("NONE (fixed box), WIDTH_AND_HEIGHT (grow both), HEIGHT (fixed width, auto height), TRUNCATE (fixed + ellipsis)"),
2139
+ componentPropertyName: import_zod4.z.string().optional().describe("Bind to a component TEXT property by name. Walks up ancestors to find the nearest component, or targets the component specified by componentId. For deeply nested text, consider using components(method:'create', type:'from_node') with exposeText:true instead \u2014 it auto-discovers and binds all text nodes."),
2140
+ componentId: import_zod4.z.string().optional().describe("Target component ID for componentPropertyName binding. When omitted, walks up ancestors to find the nearest COMPONENT or COMPONENT_SET.")
2141
+ }).passthrough();
2142
+ try {
2143
+ params.items = import_zod4.z.array(itemSchema).parse(params.items);
2144
+ } catch (e) {
2145
+ if (e instanceof import_zod4.z.ZodError) {
2146
+ throw new Error(e.issues.map((i) => {
2147
+ const path = i.path.join(".");
2148
+ const shape = itemSchema instanceof import_zod4.z.ZodObject ? itemSchema.shape : null;
2149
+ const desc = shape?.[i.path[1]]?.description;
2150
+ return path + ": " + i.message + (desc ? " (expected: " + desc + ")" : "");
2151
+ }).join("; "));
2152
+ }
2153
+ throw e;
2154
+ }
743
2155
  }
744
- }
745
- }
746
- ];
747
-
748
- // src/tools/defs/variables.ts
749
- var import_zod16 = require("zod");
750
- var collectionCreateItem = import_zod16.z.object({
751
- name: import_zod16.z.string().describe("Collection name")
752
- });
753
- var addModeItem = import_zod16.z.object({
754
- collectionId: import_zod16.z.string().describe("Collection ID"),
755
- name: import_zod16.z.string().describe("Mode name")
756
- });
757
- var renameModeItem = import_zod16.z.object({
758
- collectionId: import_zod16.z.string().describe("Collection ID"),
759
- modeId: import_zod16.z.string().describe("Mode ID"),
760
- name: import_zod16.z.string().describe("New name")
761
- });
762
- var removeModeItem = import_zod16.z.object({
763
- collectionId: import_zod16.z.string().describe("Collection ID"),
764
- modeId: import_zod16.z.string().describe("Mode ID")
765
- });
766
- var deleteCollectionItem = import_zod16.z.object({
767
- id: import_zod16.z.string().describe("Collection ID")
768
- });
769
- var collectionMethodSchemas = {
770
- create: collectionCreateItem,
771
- delete: deleteCollectionItem,
772
- add_mode: addModeItem,
773
- rename_mode: renameModeItem,
774
- remove_mode: removeModeItem
775
- };
776
- var variableCreateItem = import_zod16.z.object({
777
- collectionId: import_zod16.z.string().describe("Variable collection ID"),
778
- name: import_zod16.z.string().describe("Variable name"),
779
- resolvedType: import_zod16.z.enum(["COLOR", "FLOAT", "STRING", "BOOLEAN"]).describe("Variable type")
780
- });
781
- var variableUpdateItem = import_zod16.z.object({
782
- id: import_zod16.z.string().describe("Variable ID (full ID, e.g. VariableID:1:6)"),
783
- modeId: import_zod16.z.string().describe("Mode ID"),
784
- value: flexJson(import_zod16.z.union([
785
- import_zod16.z.number(),
786
- import_zod16.z.boolean(),
787
- colorRgba
788
- ])).describe('Value: number, boolean, or color (hex "#RRGGBB" or {r,g,b,a?} 0-1)')
789
- });
790
- var variableMethodSchemas = {
791
- create: variableCreateItem,
792
- update: variableUpdateItem
793
- };
794
- var bindingItem = import_zod16.z.object({
795
- nodeId: import_zod16.z.string().describe("Node ID"),
796
- field: import_zod16.z.string().describe("Property field (e.g., 'opacity', 'fills/0/color')"),
797
- variableId: import_zod16.z.string().describe("Variable ID (use full ID from create_variable response, e.g. VariableID:1:6)")
798
- });
799
- var setExplicitModeItem = import_zod16.z.object({
800
- nodeId,
801
- collectionId: import_zod16.z.string().describe("Variable collection ID"),
802
- modeId: import_zod16.z.string().describe("Mode ID to pin (e.g. Dark mode)")
803
- });
804
- var vcMethodTiers = {
805
- add_mode: "create",
806
- rename_mode: "edit",
807
- remove_mode: "edit"
808
- };
809
- var tools14 = [
2156
+ if (m === "set_content") {
2157
+ for (const it of params.items) {
2158
+ if (it.id !== void 0 && it.nodeId === void 0) {
2159
+ it.nodeId = it.id;
2160
+ delete it.id;
2161
+ }
2162
+ if (it.characters !== void 0 && it.text === void 0) {
2163
+ it.text = it.characters;
2164
+ delete it.characters;
2165
+ }
2166
+ }
2167
+ const itemSchema = import_zod4.z.object({
2168
+ nodeId: import_zod4.z.string().describe("Text node ID"),
2169
+ text: import_zod4.z.string().describe("New text content")
2170
+ }).passthrough();
2171
+ try {
2172
+ params.items = import_zod4.z.array(itemSchema).parse(params.items);
2173
+ } catch (e) {
2174
+ if (e instanceof import_zod4.z.ZodError) {
2175
+ throw new Error(e.issues.map((i) => {
2176
+ const path = i.path.join(".");
2177
+ const shape = itemSchema instanceof import_zod4.z.ZodObject ? itemSchema.shape : null;
2178
+ const desc = shape?.[i.path[1]]?.description;
2179
+ return path + ": " + i.message + (desc ? " (expected: " + desc + ")" : "");
2180
+ }).join("; "));
2181
+ }
2182
+ throw e;
2183
+ }
2184
+ }
2185
+ },
2186
+ commandMap: { "get": "text.get", "list": "text.list", "update": "text.update", "delete": "text.delete", "clone": "text.clone", "audit": "text.audit", "reparent": "text.reparent", "create": "text.create", "set_content": "text.set_content", "scan": "text.scan" }
2187
+ },
810
2188
  {
811
2189
  name: "variable_collections",
812
- description: `CRUD endpoint for variable collections + mode management.
813
- create \u2192 {items: [{name}]} \u2192 {results: [{id, modes, defaultModeId}]}
814
- get \u2192 {id, fields?} \u2192 collection object
815
- list \u2192 {fields?, offset?, limit?} \u2192 paginated stubs
816
- delete \u2192 {id} or {items: [{id}]} \u2192 'ok' or {results: ['ok', ...]}
817
- add_mode \u2192 {items: [{collectionId, name}]} \u2192 {results: [{modeId, modes}]}
818
- rename_mode \u2192 {items: [{collectionId, modeId, name}]} \u2192 {results: [{modes}]}
819
- remove_mode \u2192 {items: [{collectionId, modeId}]} \u2192 {results: [{modes}]}`,
820
- schema: (caps2) => endpointSchema(
821
- ["create", "get", "list", "delete", "add_mode", "rename_mode", "remove_mode"],
822
- caps2,
823
- {
824
- items: flexJson(import_zod16.z.array(import_zod16.z.any())).optional().describe("create: [{name}]. delete (batch): [{id}]. add_mode: [{collectionId, name}]. rename_mode: [{collectionId, modeId, name}]. remove_mode: [{collectionId, modeId}].")
825
- },
826
- vcMethodTiers
827
- ),
2190
+ description: '/** CRUD for variable collections \u2014 the document-level API for design tokens. Use method "help" for detailed parameter docs. */\n list (fields?, offset?, limit?) \u2192 { totalCount, items } // List variable collections\n get (id, fields?) \u2192 { id?, name?, modes?, variables? } // Get collection with all variables and values (full document)\n create (items: { name: string; modes?: string[]; variables?: { name: string; type: "COLOR" | "FLOAT" | "STRING" | "BOOLEAN"; value?: number | boolean | string | Color | {type: "VARIABLE_ALIAS", name: string}; valuesByMode?: Record<string, unknown>; description?: string; scopes?: string[] }[] }[]) \u2192 { results: {id}[] } // Create a collection with modes and variables in one call\n update (items: { id: string; name: string }[]) \u2192 { results: ("ok" | {error})[] } // Rename collections\n delete (id?, items?: { id: string }[]) \u2192 { results: "ok"[] } // Delete collections\n add_mode (items: { collectionId: string; name: string }[]) \u2192 { results: {modeId}[] } // Add a mode to a collection\n rename_mode(items: { collectionId: string; modeId: string; name: string }[]) \u2192 { results: ("ok" | {error})[] } // Rename a mode\n remove_mode(items: { collectionId: string; modeId: string }[]) \u2192 { results: "ok"[] } // Remove a mode from a collection\n// Variable collections group design tokens and define their modes (e.g. Light/Dark, Desktop/Mobile).\n// All ID params accept both IDs and display names.\n// Shared types:\n// Color: hex "#FF0000" or {r: 0-1, g: 0-1, b: 0-1, a?: 0-1}',
2191
+ schema: (caps2) => filterMethodsByTier({
2192
+ method: import_zod4.z.enum(["list", "get", "create", "update", "delete", "add_mode", "rename_mode", "remove_mode", "help"]),
2193
+ fields: flexJson(import_zod4.z.array(import_zod4.z.string())).optional().describe('Property whitelist. Identity fields (id, name, type) always included. Omit for stubs on list, full on get. Pass ["*"] for all.'),
2194
+ offset: import_zod4.z.coerce.number().optional().default(0).describe("Skip N items for pagination (default 0)"),
2195
+ limit: import_zod4.z.coerce.number().optional().default(100).describe("Max items per page (default 100)"),
2196
+ id: import_zod4.z.string().optional().describe("Collection ID or name"),
2197
+ items: flexJson(import_zod4.z.array(import_zod4.z.record(import_zod4.z.string(), import_zod4.z.unknown()))).optional().describe("Array of {id, ...properties} to create/update/delete/add_mode/rename_mode/remove_mode"),
2198
+ topic: import_zod4.z.string().optional().describe('Help topic \u2014 method name for endpoint help, e.g. "create"')
2199
+ }, caps2, { "list": "read", "get": "read", "create": "create", "update": "edit", "delete": "edit", "add_mode": "create", "rename_mode": "edit", "remove_mode": "edit", "help": "read" }),
828
2200
  tier: "read",
829
2201
  validate: (params) => {
830
- if (params.items) {
831
- const schema = collectionMethodSchemas[params.method];
832
- if (schema) params.items = import_zod16.z.array(schema).parse(params.items);
2202
+ const m = params.method;
2203
+ if (m === "get") {
2204
+ if (params.id === void 0) throw new Error('get requires "id"');
833
2205
  }
834
- }
2206
+ if (!params.items) return;
2207
+ if (m === "create") {
2208
+ const itemSchema = import_zod4.z.object({
2209
+ name: import_zod4.z.string().describe("Collection name"),
2210
+ modes: flexJson(import_zod4.z.array(import_zod4.z.string())).optional().describe("Mode names (e.g. ['Light', 'Dark']). Omit for single-mode collection."),
2211
+ variables: flexJson(import_zod4.z.array(import_zod4.z.record(import_zod4.z.string(), import_zod4.z.unknown()))).optional().describe("Variables to create inside this collection")
2212
+ }).passthrough();
2213
+ try {
2214
+ params.items = import_zod4.z.array(itemSchema).parse(params.items);
2215
+ } catch (e) {
2216
+ if (e instanceof import_zod4.z.ZodError) {
2217
+ throw new Error(e.issues.map((i) => {
2218
+ const path = i.path.join(".");
2219
+ const shape = itemSchema instanceof import_zod4.z.ZodObject ? itemSchema.shape : null;
2220
+ const desc = shape?.[i.path[1]]?.description;
2221
+ return path + ": " + i.message + (desc ? " (expected: " + desc + ")" : "");
2222
+ }).join("; "));
2223
+ }
2224
+ throw e;
2225
+ }
2226
+ }
2227
+ if (m === "update") {
2228
+ const itemSchema = import_zod4.z.object({
2229
+ id: import_zod4.z.string().describe("Collection ID or name"),
2230
+ name: import_zod4.z.string().describe("New name")
2231
+ }).passthrough();
2232
+ try {
2233
+ params.items = import_zod4.z.array(itemSchema).parse(params.items);
2234
+ } catch (e) {
2235
+ if (e instanceof import_zod4.z.ZodError) {
2236
+ throw new Error(e.issues.map((i) => {
2237
+ const path = i.path.join(".");
2238
+ const shape = itemSchema instanceof import_zod4.z.ZodObject ? itemSchema.shape : null;
2239
+ const desc = shape?.[i.path[1]]?.description;
2240
+ return path + ": " + i.message + (desc ? " (expected: " + desc + ")" : "");
2241
+ }).join("; "));
2242
+ }
2243
+ throw e;
2244
+ }
2245
+ }
2246
+ if (m === "delete") {
2247
+ const itemSchema = import_zod4.z.object({
2248
+ id: import_zod4.z.string()
2249
+ }).passthrough();
2250
+ try {
2251
+ params.items = import_zod4.z.array(itemSchema).parse(params.items);
2252
+ } catch (e) {
2253
+ if (e instanceof import_zod4.z.ZodError) {
2254
+ throw new Error(e.issues.map((i) => {
2255
+ const path = i.path.join(".");
2256
+ const shape = itemSchema instanceof import_zod4.z.ZodObject ? itemSchema.shape : null;
2257
+ const desc = shape?.[i.path[1]]?.description;
2258
+ return path + ": " + i.message + (desc ? " (expected: " + desc + ")" : "");
2259
+ }).join("; "));
2260
+ }
2261
+ throw e;
2262
+ }
2263
+ }
2264
+ if (m === "add_mode") {
2265
+ const itemSchema = import_zod4.z.object({
2266
+ collectionId: import_zod4.z.string().describe("Collection ID or name"),
2267
+ name: import_zod4.z.string().describe("Mode name")
2268
+ }).passthrough();
2269
+ try {
2270
+ params.items = import_zod4.z.array(itemSchema).parse(params.items);
2271
+ } catch (e) {
2272
+ if (e instanceof import_zod4.z.ZodError) {
2273
+ throw new Error(e.issues.map((i) => {
2274
+ const path = i.path.join(".");
2275
+ const shape = itemSchema instanceof import_zod4.z.ZodObject ? itemSchema.shape : null;
2276
+ const desc = shape?.[i.path[1]]?.description;
2277
+ return path + ": " + i.message + (desc ? " (expected: " + desc + ")" : "");
2278
+ }).join("; "));
2279
+ }
2280
+ throw e;
2281
+ }
2282
+ }
2283
+ if (m === "rename_mode") {
2284
+ const itemSchema = import_zod4.z.object({
2285
+ collectionId: import_zod4.z.string().describe("Collection ID or name"),
2286
+ modeId: import_zod4.z.string().describe('Mode ID or name (e.g. "Dark")'),
2287
+ name: import_zod4.z.string().describe("New name")
2288
+ }).passthrough();
2289
+ try {
2290
+ params.items = import_zod4.z.array(itemSchema).parse(params.items);
2291
+ } catch (e) {
2292
+ if (e instanceof import_zod4.z.ZodError) {
2293
+ throw new Error(e.issues.map((i) => {
2294
+ const path = i.path.join(".");
2295
+ const shape = itemSchema instanceof import_zod4.z.ZodObject ? itemSchema.shape : null;
2296
+ const desc = shape?.[i.path[1]]?.description;
2297
+ return path + ": " + i.message + (desc ? " (expected: " + desc + ")" : "");
2298
+ }).join("; "));
2299
+ }
2300
+ throw e;
2301
+ }
2302
+ }
2303
+ if (m === "remove_mode") {
2304
+ const itemSchema = import_zod4.z.object({
2305
+ collectionId: import_zod4.z.string().describe("Collection ID or name"),
2306
+ modeId: import_zod4.z.string().describe('Mode ID or name (e.g. "Dark")')
2307
+ }).passthrough();
2308
+ try {
2309
+ params.items = import_zod4.z.array(itemSchema).parse(params.items);
2310
+ } catch (e) {
2311
+ if (e instanceof import_zod4.z.ZodError) {
2312
+ throw new Error(e.issues.map((i) => {
2313
+ const path = i.path.join(".");
2314
+ const shape = itemSchema instanceof import_zod4.z.ZodObject ? itemSchema.shape : null;
2315
+ const desc = shape?.[i.path[1]]?.description;
2316
+ return path + ": " + i.message + (desc ? " (expected: " + desc + ")" : "");
2317
+ }).join("; "));
2318
+ }
2319
+ throw e;
2320
+ }
2321
+ }
2322
+ },
2323
+ commandMap: { "list": "variable_collections.list", "get": "variable_collections.get", "create": "variable_collections.create", "update": "variable_collections.update", "delete": "variable_collections.delete", "add_mode": "variable_collections.add_mode", "rename_mode": "variable_collections.rename_mode", "remove_mode": "variable_collections.remove_mode" }
835
2324
  },
836
2325
  {
837
2326
  name: "variables",
838
- description: `CRUD endpoint for design variables.
839
- create \u2192 {items: [{collectionId, name, resolvedType}]} \u2192 {results: [{id}]}
840
- get \u2192 {id, fields?} \u2192 variable object (full detail)
841
- list \u2192 {type?, collectionId?, fields?, offset?, limit?} \u2192 paginated stubs (fields for detail)
842
- update \u2192 {items: [{id, modeId, value}]} \u2192 {results: ['ok', ...]}`,
843
- schema: (caps2) => endpointSchema(
844
- ["create", "get", "list", "update"],
845
- caps2,
846
- {
847
- items: flexJson(import_zod16.z.array(import_zod16.z.any())).optional().describe("create: [{collectionId, name, resolvedType}]. update: [{id, modeId, value}]."),
848
- type: import_zod16.z.enum(["COLOR", "FLOAT", "STRING", "BOOLEAN"]).optional().describe("Filter list by variable type."),
849
- collectionId: import_zod16.z.string().optional().describe("Filter list by collection ID.")
2327
+ description: '/** Search and update design variables within a collection. Use method "help" for detailed parameter docs. */\n list (collectionId, query?, type?: COLOR|FLOAT|STRING|BOOLEAN, fields?, offset?, limit?) \u2192 { totalCount, items } // Search variables within a collection\n get (name, collectionId, fields?) \u2192 { name, type, collectionId, valuesByMode: Record<string, number | boolean | string | Color | {type: "VARIABLE_ALIAS", name: string}>, description?, scopes? } // Get variable detail by name\n create (collectionId, items: VariableCreateItem[]) \u2192 { results: {name}[] } // Create variables in a collection. Use valuesByMode for multi-mode, or value for default mode only.\n update (collectionId, items: VariableUpdateItem[]) \u2192 { results: ("ok" | {error})[] } // Update variable metadata and/or set values\n delete (collectionId, name?, items?: { name: string }[]) \u2192 { results: "ok"[] } // Delete variables\n// Search and update variables within a collection. collectionId is required on all methods.\n// Use variable_collections to create full token sets (collection + modes + variables in one call).\n// Shared types:\n// Color: hex "#FF0000" or {r: 0-1, g: 0-1, b: 0-1, a?: 0-1}',
2328
+ schema: (caps2) => filterMethodsByTier({
2329
+ method: import_zod4.z.enum(["list", "get", "create", "update", "delete", "help"]),
2330
+ collectionId: import_zod4.z.string().optional().describe("Collection ID or name"),
2331
+ query: import_zod4.z.string().optional().describe("Search query \u2014 prefix match first, then substring fallback"),
2332
+ type: import_zod4.z.enum(["COLOR", "FLOAT", "STRING", "BOOLEAN"]).optional().describe("Filter by variable type"),
2333
+ fields: flexJson(import_zod4.z.array(import_zod4.z.string())).optional().describe('Property whitelist. Identity fields (id, name, type) always included. Omit for stubs on list, full on get. Pass ["*"] for all.'),
2334
+ offset: import_zod4.z.coerce.number().optional().default(0).describe("Skip N items for pagination (default 0)"),
2335
+ limit: import_zod4.z.coerce.number().optional().default(100).describe("Max items per page (default 100)"),
2336
+ name: import_zod4.z.string().optional().describe("Variable name (unique within collection)"),
2337
+ items: flexJson(import_zod4.z.array(import_zod4.z.record(import_zod4.z.string(), import_zod4.z.unknown()))).optional().describe("Array of {id, ...properties} to create/update/delete"),
2338
+ topic: import_zod4.z.string().optional().describe('Help topic \u2014 method name for endpoint help, e.g. "create"')
2339
+ }, caps2, { "list": "read", "get": "read", "create": "create", "update": "edit", "delete": "edit", "help": "read" }),
2340
+ tier: "read",
2341
+ validate: (params) => {
2342
+ const m = params.method;
2343
+ if (m === "list") {
2344
+ if (params.collectionId === void 0) throw new Error('list requires "collectionId"');
2345
+ }
2346
+ if (m === "get") {
2347
+ if (params.name === void 0) throw new Error('get requires "name"');
2348
+ if (params.collectionId === void 0) throw new Error('get requires "collectionId"');
2349
+ }
2350
+ if (m === "create") {
2351
+ if (params.collectionId === void 0) throw new Error('create requires "collectionId"');
2352
+ }
2353
+ if (m === "update") {
2354
+ if (params.collectionId === void 0) throw new Error('update requires "collectionId"');
2355
+ }
2356
+ if (m === "delete") {
2357
+ if (params.collectionId === void 0) throw new Error('delete requires "collectionId"');
2358
+ }
2359
+ if (!params.items) return;
2360
+ if (m === "create") {
2361
+ const itemSchema = import_zod4.z.object({
2362
+ name: import_zod4.z.string().describe("Variable name (must be unique within collection)"),
2363
+ type: import_zod4.z.enum(["COLOR", "FLOAT", "STRING", "BOOLEAN"]).describe("Variable type"),
2364
+ value: variableValue.optional().describe("Shorthand \u2014 sets the default mode value. Use valuesByMode for multi-mode."),
2365
+ valuesByMode: import_zod4.z.record(import_zod4.z.string(), import_zod4.z.unknown()).optional().describe('Values keyed by mode name (e.g. {"Light": "#FFF", "Dark": "#111"}). Takes precedence over value.'),
2366
+ description: import_zod4.z.string().optional().describe("Variable description"),
2367
+ scopes: flexJson(import_zod4.z.array(import_zod4.z.string())).optional().describe("Restrict where variable can be applied (default: ALL_SCOPES)")
2368
+ }).passthrough();
2369
+ try {
2370
+ params.items = import_zod4.z.array(itemSchema).parse(params.items);
2371
+ } catch (e) {
2372
+ if (e instanceof import_zod4.z.ZodError) {
2373
+ throw new Error(e.issues.map((i) => {
2374
+ const path = i.path.join(".");
2375
+ const shape = itemSchema instanceof import_zod4.z.ZodObject ? itemSchema.shape : null;
2376
+ const desc = shape?.[i.path[1]]?.description;
2377
+ return path + ": " + i.message + (desc ? " (expected: " + desc + ")" : "");
2378
+ }).join("; "));
2379
+ }
2380
+ throw e;
2381
+ }
2382
+ }
2383
+ if (m === "update") {
2384
+ const itemSchema = import_zod4.z.object({
2385
+ name: import_zod4.z.string().describe("Variable name"),
2386
+ rename: import_zod4.z.string().optional().describe("Rename the variable"),
2387
+ description: import_zod4.z.string().optional().describe("Set description"),
2388
+ scopes: flexJson(import_zod4.z.array(import_zod4.z.string())).optional().describe("Update scopes"),
2389
+ value: variableValue.optional().describe("Shorthand \u2014 sets the default mode value. Use valuesByMode for multi-mode."),
2390
+ valuesByMode: import_zod4.z.record(import_zod4.z.string(), import_zod4.z.unknown()).optional().describe("Values keyed by mode name. Takes precedence over value.")
2391
+ }).passthrough();
2392
+ try {
2393
+ params.items = import_zod4.z.array(itemSchema).parse(params.items);
2394
+ } catch (e) {
2395
+ if (e instanceof import_zod4.z.ZodError) {
2396
+ throw new Error(e.issues.map((i) => {
2397
+ const path = i.path.join(".");
2398
+ const shape = itemSchema instanceof import_zod4.z.ZodObject ? itemSchema.shape : null;
2399
+ const desc = shape?.[i.path[1]]?.description;
2400
+ return path + ": " + i.message + (desc ? " (expected: " + desc + ")" : "");
2401
+ }).join("; "));
2402
+ }
2403
+ throw e;
2404
+ }
2405
+ }
2406
+ if (m === "delete") {
2407
+ const itemSchema = import_zod4.z.object({
2408
+ name: import_zod4.z.string()
2409
+ }).passthrough();
2410
+ try {
2411
+ params.items = import_zod4.z.array(itemSchema).parse(params.items);
2412
+ } catch (e) {
2413
+ if (e instanceof import_zod4.z.ZodError) {
2414
+ throw new Error(e.issues.map((i) => {
2415
+ const path = i.path.join(".");
2416
+ const shape = itemSchema instanceof import_zod4.z.ZodObject ? itemSchema.shape : null;
2417
+ const desc = shape?.[i.path[1]]?.description;
2418
+ return path + ": " + i.message + (desc ? " (expected: " + desc + ")" : "");
2419
+ }).join("; "));
2420
+ }
2421
+ throw e;
2422
+ }
850
2423
  }
851
- ),
2424
+ },
2425
+ commandMap: { "list": "variables.list", "get": "variables.get", "create": "variables.create", "update": "variables.update", "delete": "variables.delete" }
2426
+ },
2427
+ {
2428
+ name: "version_history",
2429
+ description: `/** Save named versions to the Figma file's version history. Use method "help" for detailed parameter docs. */
2430
+ save (title, description?) \u2192 { id } // Save a named version to the file's version history
2431
+ // Version history lets you create named snapshots of a Figma file.
2432
+ // Use this after completing design tasks to create an audit trail of changes.
2433
+ // Equivalent to Figma's File \u2192 Save to Version History (Cmd+Opt+S).`,
2434
+ schema: (caps2) => filterMethodsByTier({
2435
+ method: import_zod4.z.enum(["save", "help"]),
2436
+ title: import_zod4.z.string().optional().describe('Version title (e.g., "Added hero sections with website copy")'),
2437
+ description: import_zod4.z.string().optional().describe("Optional longer description of what changed"),
2438
+ topic: import_zod4.z.string().optional().describe('Help topic \u2014 method name for endpoint help, e.g. "create"')
2439
+ }, caps2, { "save": "edit", "help": "read" }),
852
2440
  tier: "read",
853
2441
  validate: (params) => {
854
- if (params.items) {
855
- const schema = variableMethodSchemas[params.method];
856
- if (schema) params.items = import_zod16.z.array(schema).parse(params.items);
2442
+ const m = params.method;
2443
+ if (m === "save") {
2444
+ if (params.title === void 0) throw new Error('save requires "title"');
857
2445
  }
858
- }
2446
+ },
2447
+ commandMap: { "save": "version_history.save" }
2448
+ }
2449
+ ];
2450
+
2451
+ // src/tools/generated/guidelines.ts
2452
+ var guidelinesList = [
2453
+ {
2454
+ "name": "component-structure",
2455
+ "title": "Component Structure"
859
2456
  },
860
2457
  {
861
- name: "set_variable_binding",
862
- description: "Bind variables to node properties. Common fields: 'fills/0/color', 'strokes/0/color', 'opacity', 'topLeftRadius', 'itemSpacing'. Batch: pass multiple items.",
863
- schema: { items: flexJson(import_zod16.z.array(bindingItem)).describe("Array of {nodeId, field, variableId}") },
864
- tier: "edit"
2458
+ "name": "library-components",
2459
+ "title": "Working with Library Components"
865
2460
  },
866
2461
  {
867
- name: "set_explicit_variable_mode",
868
- description: "Pin a variable collection mode on a frame (e.g. show Dark mode). Batch: pass multiple items.",
869
- schema: { items: flexJson(import_zod16.z.array(setExplicitModeItem)).describe("Array of {nodeId, collectionId, modeId}") },
870
- tier: "edit"
2462
+ "name": "responsive-designs",
2463
+ "title": "Responsive Sizing"
871
2464
  },
872
2465
  {
873
- name: "get_node_variables",
874
- description: "Get variable bindings on a node. Returns which variables are bound to fills, strokes, opacity, corner radius, etc.",
875
- schema: { nodeId },
876
- tier: "read"
2466
+ "name": "token-discipline",
2467
+ "title": "Token Discipline"
2468
+ },
2469
+ {
2470
+ "name": "vibma-workflow",
2471
+ "title": "Vibma Workflow"
877
2472
  }
878
2473
  ];
879
-
880
- // src/tools/defs/components.ts
881
- var import_zod17 = require("zod");
882
- var componentItem = import_zod17.z.object({
883
- name: import_zod17.z.string().describe("Component name"),
884
- x: xPos,
885
- y: yPos,
886
- width: import_zod17.z.coerce.number().optional().describe("Width (default: 100)"),
887
- height: import_zod17.z.coerce.number().optional().describe("Height (default: 100)"),
888
- parentId,
889
- fillColor: flexJson(colorRgba).optional().describe("Fill color. Omit for no fill."),
890
- fillStyleName: import_zod17.z.string().optional().describe("Apply a fill paint style by name (case-insensitive)."),
891
- fillVariableId: import_zod17.z.string().optional().describe("Bind a color variable to the fill."),
892
- strokeColor: flexJson(colorRgba).optional().describe("Stroke color. Omit for no stroke."),
893
- strokeStyleName: import_zod17.z.string().optional().describe("Apply a stroke paint style by name."),
894
- strokeVariableId: import_zod17.z.string().optional().describe("Bind a color variable to the stroke."),
895
- strokeWeight: import_zod17.z.coerce.number().positive().optional().describe("Stroke weight (default: 1)"),
896
- cornerRadius: import_zod17.z.coerce.number().optional().describe("Corner radius (default: 0)"),
897
- layoutMode: import_zod17.z.enum(["NONE", "HORIZONTAL", "VERTICAL"]).optional().describe("Layout direction (default: NONE)"),
898
- layoutWrap: import_zod17.z.enum(["NO_WRAP", "WRAP"]).optional().describe("Wrap behavior (default: NO_WRAP)"),
899
- paddingTop: import_zod17.z.coerce.number().optional().describe("Top padding (default: 0)"),
900
- paddingRight: import_zod17.z.coerce.number().optional().describe("Right padding (default: 0)"),
901
- paddingBottom: import_zod17.z.coerce.number().optional().describe("Bottom padding (default: 0)"),
902
- paddingLeft: import_zod17.z.coerce.number().optional().describe("Left padding (default: 0)"),
903
- primaryAxisAlignItems: import_zod17.z.enum(["MIN", "MAX", "CENTER", "SPACE_BETWEEN"]).optional().describe("Primary axis alignment (default: MIN)"),
904
- counterAxisAlignItems: import_zod17.z.enum(["MIN", "MAX", "CENTER", "BASELINE"]).optional().describe("Counter axis alignment (default: MIN)"),
905
- layoutSizingHorizontal: import_zod17.z.enum(["FIXED", "HUG", "FILL"]).optional().describe("Horizontal sizing (default: FIXED)"),
906
- layoutSizingVertical: import_zod17.z.enum(["FIXED", "HUG", "FILL"]).optional().describe("Vertical sizing (default: FIXED)"),
907
- itemSpacing: import_zod17.z.coerce.number().optional().describe("Spacing between children (default: 0)")
908
- });
909
- var fromNodeItem = import_zod17.z.object({
910
- nodeId,
911
- exposeText: flexBool(import_zod17.z.boolean()).default(true).describe("Auto-expose text children as editable TEXT properties and bind them (default: true). Set false to skip.")
912
- });
913
- var combineItem = import_zod17.z.object({
914
- componentIds: flexJson(import_zod17.z.array(import_zod17.z.string())).describe("Component IDs to combine (min 2)"),
915
- name: import_zod17.z.string().optional().describe("Name for the component set. Omit to auto-generate.")
916
- });
917
- var updateComponentItem = import_zod17.z.object({
918
- id: import_zod17.z.string().describe("Component node ID"),
919
- propertyName: import_zod17.z.string().describe("Property name"),
920
- type: import_zod17.z.enum(["BOOLEAN", "TEXT", "INSTANCE_SWAP", "VARIANT"]).describe("Property type"),
921
- defaultValue: flexBool(import_zod17.z.union([import_zod17.z.string(), import_zod17.z.boolean()])).describe("Default value (string for TEXT/VARIANT, boolean for BOOLEAN)"),
922
- preferredValues: flexJson(import_zod17.z.array(import_zod17.z.object({
923
- type: import_zod17.z.enum(["COMPONENT", "COMPONENT_SET"]),
924
- key: import_zod17.z.string()
925
- })).optional()).describe("Preferred values for INSTANCE_SWAP type. Omit for none.")
926
- });
927
- var componentCreateSchemas = {
928
- component: componentItem,
929
- from_node: fromNodeItem,
930
- variant_set: combineItem
2474
+ var guidelinesContent = {
2475
+ "component-structure": '# Component Structure\n\nComponents need correct sizing, property bindings, and token usage to work well as instances.\n\n## Width Constraints\n\nComponents with text content need a width \u2014 otherwise text never wraps.\n\n- Set `width` and `layoutSizingHorizontal:"FIXED"` on cards, panels, list items\n- HUG on both axes is only correct for buttons, badges, icons \u2014 intrinsically-sized elements\n\n## Property Bindings\n\nEvery text node inside a component should be bound to a TEXT property so instances can edit the content.\n\n- On creation: `children:[{type:"text", text:"Title", componentPropertyName:"Title"}]` auto-creates and binds\n- After creation: `frames(method:"update", items:[{id:"<textNodeId>", componentPropertyName:"<propName>"}])`\n- For existing nodes with many text children: `components(method:"create", type:"from_node", exposeText:true)`\n\nOrphaned properties (defined but not bound to any node) should be deleted:\n```\ncomponents(method:"update", items:[{id, propertyName:"<key>", action:"delete"}])\n```\n\n## Variant Sets\n\nGroup related components as variants \u2014 don\'t leave them as separate components.\n\n- Name variants with the property format: `Style=Primary`, `Style=Secondary`\n- Combine: `components(method:"create", type:"variant_set", items:[{componentIds:[...], name:"Button"}])`\n- Instances pick variants via `variantProperties:{"Style":"Primary"}`\n\n## Checking\n\nRun `components(method:"audit", id)` \u2014 checks both lint rules and property bindings in one call.',
2476
+ "library-components": '# Working with Library Components\n\nLibrary components are read-only \u2014 they come from external team libraries and cannot be edited in the current file.\n\n## Reading\n\nWhen reading nodes, library instances appear as stubs with their overridable properties:\n```\n{name: "Header", type: "INSTANCE", componentProperties: {"Platform": "Desktop"}}\n```\n\nLibrary internals are hidden: no `componentId`, no variable names, no style names. You see resolved values (hex colors, numbers) instead.\n\n## Using\n\nPlace library instances via `instances(method:"create", items:[{componentId:"<id>"}])` when you have a local component ID. For library components, instances are already placed by the designer \u2014 interact via `instances(method:"update")` to set properties.\n\n## Customizing\n\nTo edit a library component, clone it into the local file first:\n\n```\ncomponents(method:"clone", id:"<instanceId>")\n```\n\nThis resolves the instance to its source component (or full component set) and creates a local copy with a new ID. Edit the local copy freely \u2014 it is independent of the library.\n\nDo not attempt to `components(method:"get")` or `components(method:"update")` a library component directly \u2014 these will error.\n\n## Overriding Instance Properties\n\nUse `instances(method:"update")` to change overridable properties on library instances:\n```\ninstances(method:"update", items:[{id:"<instanceId>", properties:{"Label":"New Text", "State":"Hover"}}])\n```\n\nProperty names are clean (no hash suffixes needed for update \u2014 the system resolves partial keys).',
2477
+ "responsive-designs": "# Responsive Sizing\n\n## FIXED / FILL / HUG\n\n- **FIXED** \u2014 layout boundaries: page shell, sidebar width, modal max-width\n- **FILL** \u2014 children that adapt to parent: main content area, nav stacks, cards in columns, text that should wrap\n- **HUG** \u2014 content-sized leaves only: icons, badges, pills, button labels\n\n## Component Sizing\n\nComponent roots use `FILL` when placed in a parent \u2014 they adapt to context, not a fixed specimen width.\n\nExample sidebar item:\n- Instance: `FILL` in parent nav stack\n- Icon child: fixed 18x18\n- Label child: `FILL`\n- Badge child: `HUG`\n\n## Text Sizing\n\n- Body text inside containers: `FILL` width, `HUG` height (auto-wraps)\n- Single-line labels: `FILL` horizontal (truncates if needed)\n- Standalone headings: `HUG` is fine\n\nInside auto-layout parents, text defaults to `FILL` horizontal + `HUG` vertical + `textAutoResize: HEIGHT`.",
2478
+ "token-discipline": '# Token Discipline\n\nEvery color, spacing value, and text style should come from a design token \u2014 not hardcoded values.\n\n## Colors\n\nBind fills and strokes to color variables instead of hex values.\n\n- Fill: `fillVariableName:"bg/primary"` or `fillStyleName:"Surface/Primary"`\n- Stroke: `strokeVariableName:"border/default"`\n- Text color: `fontColorVariableName:"text/primary"`\n\nIf no matching variable exists, create one first:\n```\nvariables(method:"create", collectionId:"Colors", items:[{name:"bg/accent", type:"COLOR", valuesByMode:{"Light":"#E8F0FE","Dark":"#1A3A5C"}, scopes:["ALL_FILLS"]}])\n```\n\n## Spacing and Radius\n\nPass a variable name string instead of a number for cornerRadius, padding, itemSpacing, strokeWeight, opacity.\n\n- `cornerRadius:"radius/8"` not `cornerRadius:8`\n- `paddingTop:"space/16"` not `paddingTop:16`\n- `itemSpacing:"space/8"` not `itemSpacing:8`\n\nCreate FLOAT variables with appropriate scopes:\n```\nvariables(method:"create", collectionId:"Metrics", items:[{name:"space/12", type:"FLOAT", value:12, scopes:["GAP","WIDTH_HEIGHT"]}])\n```\n\n## Text Styles\n\nApply text styles by name \u2014 don\'t set fontSize/fontFamily/fontWeight manually.\n\n- `textStyleName:"Body/M"` on text.create or frames.update\n- Create styles with `styles(method:"create", type:"text", items:[{name:"Body/M", fontFamily:"Inter", fontSize:14, lineHeight:{value:20, unit:"PIXELS"}}])`\n\n## Common Scopes\n\nCOLOR variables:\n- `ALL_FILLS` \u2014 background fills\n- `TEXT_FILL` \u2014 text color\n- `STROKE_COLOR` \u2014 borders and outlines\n\nFLOAT variables:\n- `GAP`, `WIDTH_HEIGHT` \u2014 spacing and padding\n- `CORNER_RADIUS` \u2014 border radius\n- `STROKE_FLOAT` \u2014 stroke weight\n- `OPACITY` \u2014 transparency\n\n## Checking\n\nLint rules `hardcoded-color`, `hardcoded-token`, `no-text-style` catch unbound values. Run `audit` on any node to check.',
2479
+ "vibma-workflow": '# Vibma Workflow\n\nWork with the tool in a predictable sequence: read before writing, create parents before children, verify after mutations.\n\n## Build Order\n\n1. `connection.create` \u2192 `connection.get` to verify\n2. Inspect existing structure: `document.get`, `variables.list`, `styles.list`, `components.list`\n3. Create design tokens: variable collections \u2192 variables \u2192 text styles \u2192 effect styles\n4. Create components from tokens\n5. Assemble screens from component instances\n6. Verify with `lint.check` and `frames.export`\n\n## Parent-First Rule\n\nCreate parent containers before children. Dependent creates must be sequential \u2014 never parallelize when the child needs the parent ID.\n\n## Component Creation\n\nBuild components early \u2014 they are the building blocks for screens. A component IS a frame: create it directly with layout properties, then add children.\n\n- Use `components.create(type: "component")` with properties for TEXT, BOOLEAN, INSTANCE_SWAP\n- TEXT properties auto-bind to child text nodes with matching names\n- Group related components into variant sets with `components.create(type: "variant_set")` for state dimensions (Style, Size, State)\n- Use flat components (not variant sets) for INSTANCE_SWAP slots like icons or avatars\n- Assemble screens from `instances.create`, not by cloning frames\n\n## Placement Rule\n\nAlways pass `x` and `y` for top-level nodes and clones. Do not stack everything at `0,0`.\n\n## Instance Rule\n\nCall `components.get` or `instances.get` to discover property keys (including `#suffix`) before setting overrides. Do not guess property names.\n\n## Verify After Mutations\n\n`"ok"` means the write succeeded, not that the result is correct. Read back the node after clone, swap, mode pinning, or large batch updates.'
931
2480
  };
932
- var instanceCreateItem = import_zod17.z.object({
933
- componentId: import_zod17.z.string().describe("Component or component set ID"),
934
- variantProperties: flexJson(import_zod17.z.record(import_zod17.z.string(), import_zod17.z.string())).optional().describe('Pick variant by properties, e.g. {"Style":"Secondary","Size":"Large"}. Ignored for plain COMPONENT IDs.'),
935
- x: import_zod17.z.coerce.number().optional().describe("X position. Omit to keep default."),
936
- y: import_zod17.z.coerce.number().optional().describe("Y position. Omit to keep default."),
937
- parentId
938
- });
939
- var instanceUpdateItem = import_zod17.z.object({
940
- id: nodeId,
941
- properties: flexJson(import_zod17.z.record(import_zod17.z.string(), import_zod17.z.union([import_zod17.z.string(), import_zod17.z.boolean()]))).describe('Property key\u2192value map, e.g. {"Label#1:0":"Click Me"}')
942
- });
943
- var tools15 = [
944
- {
945
- name: "components",
946
- description: `CRUD endpoint for components.
947
- create \u2192 {type, items, depth?} \u2192 {results: [{id}, ...]}
948
- type 'component': create from scratch with layout/style params
949
- type 'from_node': convert existing nodes to components. Text children are auto-exposed as editable properties by default (exposeText: true) \u2014 instances can set text directly via properties.
950
- type 'variant_set': combine components into variant sets
951
- get \u2192 {id, fields?} \u2192 component object (full detail, field-filterable)
952
- list \u2192 {name?, setsOnly?, fields?, offset?, limit?} \u2192 paginated stubs
953
- update \u2192 {items: [{id, propertyName, type, defaultValue}]} \u2192 creates property AND binds matching text node automatically`,
954
- schema: (caps2) => endpointSchema(
955
- ["create", "get", "list", "update"],
956
- caps2,
957
- {
958
- items: flexJson(import_zod17.z.array(import_zod17.z.any())).optional().describe("create (component): [{name, parentId?, ...layout}]. create (from_node): [{nodeId, exposeText?}]. create (variant_set): [{componentIds, name?}]. update: [{id, propertyName, type, defaultValue}]."),
959
- type: import_zod17.z.enum(["component", "from_node", "variant_set"]).optional().describe("Create type. Required for create: 'component' (from scratch), 'from_node' (convert existing), 'variant_set' (combine as variants)."),
960
- depth,
961
- name: import_zod17.z.string().optional().describe("Filter list by name (case-insensitive substring)."),
962
- setsOnly: flexBool(import_zod17.z.boolean()).optional().describe("If true, list returns only COMPONENT_SET nodes.")
963
- }
964
- ),
965
- tier: "read",
966
- validate: (params) => {
967
- if (params.items) {
968
- if (params.method === "create") {
969
- const schema = params.type && componentCreateSchemas[params.type];
970
- if (!schema) throw new Error(`create requires type: component, from_node, or variant_set`);
971
- params.items = import_zod17.z.array(schema).parse(params.items);
972
- } else if (params.method === "update") {
973
- params.items = import_zod17.z.array(updateComponentItem).parse(params.items);
974
- }
975
- }
976
- }
977
- },
978
- {
979
- name: "instances",
980
- description: `CRUD endpoint for component instances.
981
- create \u2192 {items: [{componentId, variantProperties?, x?, y?, parentId?}], depth?} \u2192 {results: [{id}]}
982
- get \u2192 {id} \u2192 {mainComponentId, overrides: [{id, fields}]}
983
- update \u2192 {items: [{id, properties}]} \u2192 {results: ['ok', ...]}`,
984
- schema: (caps2) => endpointSchema(
985
- ["create", "get", "update"],
986
- caps2,
987
- {
988
- items: flexJson(import_zod17.z.array(import_zod17.z.any())).optional().describe("create: [{componentId, variantProperties?, x?, y?, parentId?}]. update: [{id, properties}]."),
989
- depth
990
- }
991
- ),
992
- tier: "read",
993
- validate: (params) => {
994
- if (params.items) {
995
- if (params.method === "create") {
996
- params.items = import_zod17.z.array(instanceCreateItem).parse(params.items);
997
- } else if (params.method === "update") {
998
- params.items = import_zod17.z.array(instanceUpdateItem).parse(params.items);
999
- }
1000
- }
2481
+ function resolveGuideline(topic) {
2482
+ if (!topic) {
2483
+ const dir = ["# Design Guidelines", ""];
2484
+ for (const g of guidelinesList) {
2485
+ dir.push(" " + g.name.padEnd(30) + g.title);
1001
2486
  }
2487
+ dir.push("");
2488
+ dir.push('Use guidelines(topic: "<name>") for full guideline text.');
2489
+ return dir.join("\n");
1002
2490
  }
1003
- ];
2491
+ const content = guidelinesContent[topic];
2492
+ if (content) return content;
2493
+ const names = guidelinesList.map((g) => g.name);
2494
+ return "Unknown guideline: " + topic + ". Available: " + names.join(", ");
2495
+ }
1004
2496
 
1005
2497
  // src/tools/prompts.ts
1006
2498
  function registerPrompts(server2) {
@@ -1012,41 +2504,26 @@ function registerPrompts(server2) {
1012
2504
  role: "assistant",
1013
2505
  content: {
1014
2506
  type: "text",
1015
- text: `When working with Figma designs, follow these best practices:
2507
+ text: `Figma design best practices:
1016
2508
 
1017
- 1. Understand Before Creating:
1018
- - Use get_document_info() to see pages and current page
1019
- - Use styles(method: "list") and get_local_variables() to discover existing design tokens
1020
- - Plan layout hierarchy before creating elements
2509
+ 1. Discover First: document(method:"list"), styles(method:"list"), variables(method:"list") \u2014 know what exists before creating.
1021
2510
 
1022
- 2. Use Design Tokens \u2014 Never Hardcode:
1023
- - Colors: use fillStyleName/strokeStyleName (paint styles) or fillVariableId/strokeVariableId (variables)
1024
- - Text: use textStyleName to apply text styles that control font size, weight, and line height together
1025
- - Effects: use effectStyleName to apply shadow/blur styles
1026
- - Only use raw fillColor/fontColor for one-off values not in the design system
2511
+ 2. Design Tokens \u2014 Never Hardcode Colors:
2512
+ - Use fillStyleName/strokeStyleName (paint styles) or fillVariableName/strokeVariableName (variables)
2513
+ - Use textStyleName for typography. Only use raw fillColor/fontColor for one-off values.
1027
2514
 
1028
2515
  3. Auto-Layout First:
1029
- - Use create_frame() with layoutMode: "VERTICAL" or "HORIZONTAL" for every container
1030
- - Set itemSpacing, padding, and alignment at creation time
1031
- - Use layoutSizingHorizontal/Vertical: "FILL" for responsive children
1032
- - Avoid absolute positioning \u2014 let auto-layout handle spacing
1033
-
1034
- 4. Naming Conventions:
1035
- - Use descriptive, semantic names for all elements
1036
- - Name components with Property=Value pattern (e.g. "Size=Small") before components(method: "create", type: "variant_set")
1037
-
1038
- 5. Variable Modes:
1039
- - Use set_explicit_variable_mode() to pin a frame to a specific mode (e.g. Dark)
1040
- - Use get_node_variables() to verify which variables are bound to a node
1041
-
1042
- 6. Quality Check \u2014 Run Lint:
1043
- - After building a section, run lint_node() to catch common issues:
1044
- * hardcoded-color: fills/strokes not using styles or variables
1045
- * no-text-style: text without a text style applied
1046
- * no-autolayout: frames with children but no auto-layout
1047
- * default-name: nodes still named "Frame", "Rectangle", etc.
1048
- - Use lint_fix_autolayout() to auto-fix layout issues
1049
- - Lint early and often \u2014 it is cheaper to fix issues during creation than after`
2516
+ - frames(method:"create", type:"auto_layout") for containers, components(method:"create", type:"component") for reusable elements.
2517
+ - A component IS a frame \u2014 create it directly with layout/fill/stroke params, then add children. No need to create a frame first and convert.
2518
+ - layoutSizingHorizontal/Vertical: "FILL" for responsive children, "HUG" to shrink-wrap.
2519
+
2520
+ 4. Naming: descriptive names for all elements. Property=Value pattern (e.g. "Size=Small") for variant components.
2521
+
2522
+ 5. Lint After Every Section:
2523
+ - Run lint(method:"check") after building. Always attempt to fix warnings unless you understand the specific warning and it's intentional.
2524
+ - Each warning includes a fix instruction with the exact tool call to use \u2014 follow it.
2525
+ - Use lint(method:"fix") to auto-convert frames to auto-layout.
2526
+ - Lint early and often \u2014 cheaper to fix during creation than after.`
1050
2527
  }
1051
2528
  }],
1052
2529
  description: "Best practices for working with Figma designs"
@@ -1060,12 +2537,7 @@ function registerPrompts(server2) {
1060
2537
  role: "assistant",
1061
2538
  content: {
1062
2539
  type: "text",
1063
- text: `When reading Figma designs, follow these best practices:
1064
-
1065
- 1. Start with selection:
1066
- - First use get_selection() to understand the current selection
1067
- - If no selection ask user to select single or multiple nodes
1068
- `
2540
+ text: 'When reading Figma designs, follow these best practices:\n\n1. Start with selection:\n - First use selection(method: "get") to understand the current selection\n - If no selection ask user to select single or multiple nodes'
1069
2541
  }
1070
2542
  }],
1071
2543
  description: "Best practices for reading Figma designs"
@@ -1079,120 +2551,7 @@ function registerPrompts(server2) {
1079
2551
  role: "assistant",
1080
2552
  content: {
1081
2553
  type: "text",
1082
- text: `# Intelligent Text Replacement Strategy
1083
-
1084
- ## 1. Analyze Design & Identify Structure
1085
- - Scan text nodes to understand the overall structure of the design
1086
- - Use AI pattern recognition to identify logical groupings:
1087
- * Tables (rows, columns, headers, cells)
1088
- * Lists (items, headers, nested lists)
1089
- * Card groups (similar cards with recurring text fields)
1090
- * Forms (labels, input fields, validation text)
1091
- * Navigation (menu items, breadcrumbs)
1092
- \`\`\`
1093
- scan_text_nodes(nodeId: "node-id")
1094
- get_node_info(nodeId: "node-id") // optional
1095
- \`\`\`
1096
-
1097
- ## 2. Strategic Chunking for Complex Designs
1098
- - Divide replacement tasks into logical content chunks based on design structure
1099
- - Use one of these chunking strategies that best fits the design:
1100
- * **Structural Chunking**: Table rows/columns, list sections, card groups
1101
- * **Spatial Chunking**: Top-to-bottom, left-to-right in screen areas
1102
- * **Semantic Chunking**: Content related to the same topic or functionality
1103
- * **Component-Based Chunking**: Process similar component instances together
1104
-
1105
- ## 3. Progressive Replacement with Verification
1106
- - Create a safe copy of the node for text replacement
1107
- - Replace text chunk by chunk with continuous progress updates
1108
- - After each chunk is processed:
1109
- * Export that section as a small, manageable image
1110
- * Verify text fits properly and maintain design integrity
1111
- * Fix issues before proceeding to the next chunk
1112
-
1113
- \`\`\`
1114
- // Clone the node to create a safe copy
1115
- clone_node(nodeId: "selected-node-id", x: [new-x], y: [new-y])
1116
-
1117
- // Replace text chunk by chunk
1118
- set_text_content(
1119
- items: [
1120
- { nodeId: "node-id-1", text: "New text 1" },
1121
- // More nodes in this chunk...
1122
- ]
1123
- )
1124
-
1125
- // Verify chunk with small, targeted image exports
1126
- export_node_as_image(nodeId: "chunk-node-id", format: "PNG", scale: 0.5)
1127
- \`\`\`
1128
-
1129
- ## 4. Intelligent Handling for Table Data
1130
- - For tabular content:
1131
- * Process one row or column at a time
1132
- * Maintain alignment and spacing between cells
1133
- * Consider conditional formatting based on cell content
1134
- * Preserve header/data relationships
1135
-
1136
- ## 5. Smart Text Adaptation
1137
- - Adaptively handle text based on container constraints:
1138
- * Auto-detect space constraints and adjust text length
1139
- * Apply line breaks at appropriate linguistic points
1140
- * Maintain text hierarchy and emphasis
1141
- * Consider font scaling for critical content that must fit
1142
-
1143
- ## 6. Progressive Feedback Loop
1144
- - Establish a continuous feedback loop during replacement:
1145
- * Real-time progress updates (0-100%)
1146
- * Small image exports after each chunk for verification
1147
- * Issues identified early and resolved incrementally
1148
- * Quick adjustments applied to subsequent chunks
1149
-
1150
- ## 7. Final Verification & Context-Aware QA
1151
- - After all chunks are processed:
1152
- * Export the entire design at reduced scale for final verification
1153
- * Check for cross-chunk consistency issues
1154
- * Verify proper text flow between different sections
1155
- * Ensure design harmony across the full composition
1156
-
1157
- ## 8. Chunk-Specific Export Scale Guidelines
1158
- - Scale exports appropriately based on chunk size:
1159
- * Small chunks (1-5 elements): scale 1.0
1160
- * Medium chunks (6-20 elements): scale 0.7
1161
- * Large chunks (21-50 elements): scale 0.5
1162
- * Very large chunks (50+ elements): scale 0.3
1163
- * Full design verification: scale 0.2
1164
-
1165
- ## Sample Chunking Strategy for Common Design Types
1166
-
1167
- ### Tables
1168
- - Process by logical rows (5-10 rows per chunk)
1169
- - Alternative: Process by column for columnar analysis
1170
- - Tip: Always include header row in first chunk for reference
1171
-
1172
- ### Card Lists
1173
- - Group 3-5 similar cards per chunk
1174
- - Process entire cards to maintain internal consistency
1175
- - Verify text-to-image ratio within cards after each chunk
1176
-
1177
- ### Forms
1178
- - Group related fields (e.g., "Personal Information", "Payment Details")
1179
- - Process labels and input fields together
1180
- - Ensure validation messages and hints are updated with their fields
1181
-
1182
- ### Navigation & Menus
1183
- - Process hierarchical levels together (main menu, submenu)
1184
- - Respect information architecture relationships
1185
- - Verify menu fit and alignment after replacement
1186
-
1187
- ## Best Practices
1188
- - **Preserve Design Intent**: Always prioritize design integrity
1189
- - **Structural Consistency**: Maintain alignment, spacing, and hierarchy
1190
- - **Visual Feedback**: Verify each chunk visually before proceeding
1191
- - **Incremental Improvement**: Learn from each chunk to improve subsequent ones
1192
- - **Balance Automation & Control**: Let AI handle repetitive replacements but maintain oversight
1193
- - **Respect Content Relationships**: Keep related content consistent across chunks
1194
-
1195
- Remember that text is never just text\u2014it's a core design element that must work harmoniously with the overall composition. This chunk-based strategy allows you to methodically transform text while maintaining design integrity.`
2554
+ text: '# Intelligent Text Replacement Strategy\n\n## 1. Analyze Design & Identify Structure\n- Scan text nodes to understand the overall structure of the design\n- Use AI pattern recognition to identify logical groupings:\n * Tables (rows, columns, headers, cells)\n * Lists (items, headers, nested lists)\n * Card groups (similar cards with recurring text fields)\n * Forms (labels, input fields, validation text)\n * Navigation (menu items, breadcrumbs)\n```\ntext(method: "scan", items: [{nodeId: "node-id"}])\nframes(method: "get", id: "node-id", depth: 1) // optional\n```\n\n## 2. Strategic Chunking for Complex Designs\n- Divide replacement tasks into logical content chunks based on design structure\n- Use one of these chunking strategies that best fits the design:\n * **Structural Chunking**: Table rows/columns, list sections, card groups\n * **Spatial Chunking**: Top-to-bottom, left-to-right in screen areas\n * **Semantic Chunking**: Content related to the same topic or functionality\n * **Component-Based Chunking**: Process similar component instances together\n\n## 3. Progressive Replacement with Verification\n- Create a safe copy of the node for text replacement\n- Replace text chunk by chunk with continuous progress updates\n- After each chunk is processed:\n * Export that section as a small, manageable image\n * Verify text fits properly and maintain design integrity\n * Fix issues before proceeding to the next chunk\n\n```\n// Clone the node to create a safe copy\nframes(method: "clone", id: "selected-node-id", x: [new-x], y: [new-y])\n\n// Replace text chunk by chunk\ntext(method: "set_content", items: [\n { nodeId: "node-id-1", text: "New text 1" },\n // More nodes in this chunk...\n])\n\n// Verify chunk with small, targeted image exports\nframes(method: "export", nodeId: "chunk-node-id", format: "PNG", scale: 0.5)\n```\n\n## 4. Intelligent Handling for Table Data\n- For tabular content:\n * Process one row or column at a time\n * Maintain alignment and spacing between cells\n * Consider conditional formatting based on cell content\n * Preserve header/data relationships\n\n## 5. Smart Text Adaptation\n- Adaptively handle text based on container constraints:\n * Auto-detect space constraints and adjust text length\n * Apply line breaks at appropriate linguistic points\n * Maintain text hierarchy and emphasis\n * Consider font scaling for critical content that must fit\n\n## 6. Progressive Feedback Loop\n- Establish a continuous feedback loop during replacement:\n * Real-time progress updates (0-100%)\n * Small image exports after each chunk for verification\n * Issues identified early and resolved incrementally\n * Quick adjustments applied to subsequent chunks\n\n## 7. Final Verification & Context-Aware QA\n- After all chunks are processed:\n * Export the entire design at reduced scale for final verification\n * Check for cross-chunk consistency issues\n * Verify proper text flow between different sections\n * Ensure design harmony across the full composition\n\n## 8. Chunk-Specific Export Scale Guidelines\n- Scale exports appropriately based on chunk size:\n * Small chunks (1-5 elements): scale 1.0\n * Medium chunks (6-20 elements): scale 0.7\n * Large chunks (21-50 elements): scale 0.5\n * Very large chunks (50+ elements): scale 0.3\n * Full design verification: scale 0.2\n\n## Sample Chunking Strategy for Common Design Types\n\n### Tables\n- Process by logical rows (5-10 rows per chunk)\n- Alternative: Process by column for columnar analysis\n- Tip: Always include header row in first chunk for reference\n\n### Card Lists\n- Group 3-5 similar cards per chunk\n- Process entire cards to maintain internal consistency\n- Verify text-to-image ratio within cards after each chunk\n\n### Forms\n- Group related fields (e.g., "Personal Information", "Payment Details")\n- Process labels and input fields together\n- Ensure validation messages and hints are updated with their fields\n\n### Navigation & Menus\n- Process hierarchical levels together (main menu, submenu)\n- Respect information architecture relationships\n- Verify menu fit and alignment after replacement\n\n## Best Practices\n- **Preserve Design Intent**: Always prioritize design integrity\n- **Structural Consistency**: Maintain alignment, spacing, and hierarchy\n- **Visual Feedback**: Verify each chunk visually before proceeding\n- **Incremental Improvement**: Learn from each chunk to improve subsequent ones\n- **Balance Automation & Control**: Let AI handle repetitive replacements but maintain oversight\n- **Respect Content Relationships**: Keep related content consistent across chunks\n\nRemember that text is never just text \u2014 it\'s a core design element that must work harmoniously with the overall composition. This chunk-based strategy allows you to methodically transform text while maintaining design integrity.'
1196
2555
  }
1197
2556
  }],
1198
2557
  description: "Systematic approach for replacing text in Figma designs"
@@ -1206,32 +2565,10 @@ Remember that text is never just text\u2014it's a core design element that must
1206
2565
  role: "assistant",
1207
2566
  content: {
1208
2567
  type: "text",
1209
- text: `# Swap Component Instance Overrides
1210
-
1211
- ## Overview
1212
- Transfer content overrides from a source instance to target instances.
1213
-
1214
- ## Process
1215
-
1216
- ### 1. Identify Instances
1217
- - Use \`get_selection()\` to identify selected instances
1218
- - Use \`search_nodes(types: ["INSTANCE"])\` to find instances on the page
1219
-
1220
- ### 2. Extract Source Overrides
1221
- - \`instances(method: "get", id: "source-instance-id")\`
1222
- - Returns mainComponentId and per-child override fields (characters, fills, fontSize, etc.)
1223
-
1224
- ### 3. Apply to Targets
1225
- - For text overrides: use \`set_text_content\` on matching child node IDs
1226
- - For style overrides: use \`patch_nodes\` with fill/stroke/text/effects styleName fields
1227
- - Match children by name path \u2014 source and target instances share the same internal structure
1228
-
1229
- ### 4. Verify
1230
- - \`get_node_info(nodeId, depth: 1)\` on target instances
1231
- - \`export_node_as_image\` for visual verification`
2568
+ text: '# Swap Component Instance Overrides\n\n## Overview\nTransfer content overrides from a source instance to target instances.\n\n## Process\n\n### 1. Identify Instances\n- Use `selection(method: "get")` to identify selected instances\n- Use `frames(method: "list", types: ["INSTANCE"])` to find instances on the page\n\n### 2. Extract Source Overrides\n- `instances(method: "get", id: "source-instance-id")`\n- Returns mainComponentId and per-child override fields (characters, fills, fontSize, etc.)\n\n### 3. Apply to Targets\n- For text overrides: use `text(method: "set_content")` on matching child node IDs\n- For style overrides: use `frames(method: "update")` with fill/stroke/text/effects styleName fields\n- Match children by name path \u2014 source and target instances share the same internal structure\n\n### 4. Verify\n- `frames(method: "get", id: "target-id", depth: 1)` on target instances\n- `frames(method: "export", nodeId: "target-id")` for visual verification'
1232
2569
  }
1233
2570
  }],
1234
- description: "Strategy for transferring overrides between component instances in Figma"
2571
+ description: "Guide to swap instance overrides between instances"
1235
2572
  })
1236
2573
  );
1237
2574
  server2.registerPrompt(
@@ -1242,32 +2579,7 @@ Transfer content overrides from a source instance to target instances.
1242
2579
  role: "assistant",
1243
2580
  content: {
1244
2581
  type: "text",
1245
- text: `# Missing Create / Edit Tools
1246
-
1247
- If the user asks you to create or modify something in Figma but you cannot find tools like \`create_frame\`, \`create_text\`, \`patch_nodes\`, \`delete_node\`, or \`set_text_content\`, the MCP server was started without the correct access tier flag.
1248
-
1249
- Vibma filters tools at startup based on CLI flags passed in the MCP config \`args\` array:
1250
-
1251
- | Flag | Tools available |
1252
- |------|----------------|
1253
- | _(none)_ | Read-only (inspect, search, export) |
1254
- | \`--create\` | Read + creation tools |
1255
- | \`--edit\` | All tools (read + create + edit + delete) |
1256
-
1257
- Ask the user to add \`--edit\` (or \`--create\`) to their MCP config args:
1258
-
1259
- \`\`\`json
1260
- {
1261
- "mcpServers": {
1262
- "Vibma": {
1263
- "command": "npx",
1264
- "args": ["-y", "@ufira/vibma", "--edit"]
1265
- }
1266
- }
1267
- }
1268
- \`\`\`
1269
-
1270
- After updating, the user must restart their AI tool or reload MCP servers \u2014 stdio-based servers cannot hot-reload.`
2582
+ text: '# Missing Create / Edit Tools\n\nIf the user asks you to create or modify something in Figma but you cannot find create/edit methods on endpoint tools, the MCP server was started without the correct access tier flag.\n\nVibma filters available methods at startup based on CLI flags passed in the MCP config `args` array:\n\n| Flag | Methods available |\n|------|-----------------|\n| _(none)_ | Read-only (get, list, check, scan, export) |\n| `--create` | Read + creation methods (create, clone) |\n| `--edit` | All methods (read + create + update + delete) |\n\nAsk the user to add `--edit` (or `--create`) to their MCP config args:\n\n```json\n{\n "mcpServers": {\n "Vibma": {\n "command": "npx",\n "args": ["-y", "@ufira/vibma", "--edit"]\n }\n }\n}\n```\n\nAfter updating, the user must restart their AI tool or reload MCP servers \u2014 stdio-based servers cannot hot-reload.'
1271
2583
  }
1272
2584
  }],
1273
2585
  description: "Why create or edit tools are missing and how to fix it"
@@ -1276,24 +2588,39 @@ After updating, the user must restart their AI tool or reload MCP servers \u2014
1276
2588
  }
1277
2589
 
1278
2590
  // src/tools/mcp-registry.ts
1279
- var allTools = [
1280
- ...tools,
1281
- ...tools2,
1282
- ...tools3,
1283
- ...tools4,
1284
- ...tools5,
1285
- ...tools6,
1286
- ...tools7,
1287
- ...tools8,
1288
- ...tools9,
1289
- ...tools10,
1290
- ...tools11,
1291
- ...tools12,
1292
- ...tools13,
1293
- ...tools14,
1294
- ...tools15
1295
- ];
2591
+ var endpointTools = tools.filter((t) => t.name !== "connection");
2592
+ var framesTool = endpointTools.find((t) => t.name === "frames");
2593
+ if (framesTool) {
2594
+ framesTool.methodFormatters = {
2595
+ export: (result) => {
2596
+ const r = result;
2597
+ if (r.isString) {
2598
+ return { content: [{ type: "text", text: r.imageData }] };
2599
+ }
2600
+ return {
2601
+ content: [{ type: "image", data: r.imageData, mimeType: r.mimeType || "image/png" }]
2602
+ };
2603
+ }
2604
+ };
2605
+ }
2606
+ var allTools = [...endpointTools];
1296
2607
  function registerAllTools(server2, sendCommand, caps2) {
2608
+ server2.registerTool("help", {
2609
+ description: 'Get help on any endpoint or method. Lists all endpoints, their methods, and detailed parameter docs.\nExamples: help() \u2192 directory, help(topic: "components") \u2192 endpoint details, help(topic: "components.create") \u2192 method params.',
2610
+ inputSchema: {
2611
+ topic: import_zod5.z.string().optional().describe('Endpoint or endpoint.method name, e.g. "components" or "components.create"')
2612
+ }
2613
+ }, async (params) => {
2614
+ return { content: [{ type: "text", text: resolveHelp(params.topic) }] };
2615
+ });
2616
+ server2.registerTool("guidelines", {
2617
+ description: 'Design guidelines for building quality Figma designs. Covers layout, responsiveness, tokens, components, accessibility, naming, and workflow.\nExamples: guidelines() \u2192 list topics, guidelines(topic: "responsive-designs") \u2192 full guideline.',
2618
+ inputSchema: {
2619
+ topic: import_zod5.z.string().optional().describe('Guideline topic name, e.g. "responsive-designs" or "token-discipline"')
2620
+ }
2621
+ }, async (params) => {
2622
+ return { content: [{ type: "text", text: resolveGuideline(params.topic) }] };
2623
+ });
1297
2624
  registerTools(server2, sendCommand, caps2, allTools);
1298
2625
  registerPrompts(server2);
1299
2626
  }
@@ -1344,9 +2671,10 @@ var versionWarning = null;
1344
2671
  var args = process.argv.slice(2);
1345
2672
  var serverArg = args.find((a) => a.startsWith("--server="));
1346
2673
  var portArg = args.find((a) => a.startsWith("--port="));
1347
- var serverUrl = serverArg ? serverArg.split("=")[1] : "localhost";
2674
+ var serverUrl = serverArg ? serverArg.split("=")[1] : process.env.VIBMA_SERVER || "localhost";
1348
2675
  if (portArg) activePort = parseInt(portArg.split("=")[1]);
1349
- var WS_URL = serverUrl === "localhost" ? `ws://${serverUrl}` : `wss://${serverUrl}`;
2676
+ var isLocal = /^(localhost|127\.0\.0\.1|host\.docker\.internal|0\.0\.0\.0)(:|$)/.test(serverUrl) || serverUrl.endsWith(".local");
2677
+ var WS_URL = isLocal ? `ws://${serverUrl}` : `wss://${serverUrl}`;
1350
2678
  var caps = {
1351
2679
  create: args.includes("--create") || args.includes("--edit"),
1352
2680
  edit: args.includes("--edit")
@@ -1357,7 +2685,7 @@ function connectToFigma(port = activePort) {
1357
2685
  logger.info("Already connected to Figma");
1358
2686
  return;
1359
2687
  }
1360
- const wsUrl = serverUrl === "localhost" ? `${WS_URL}:${port}` : WS_URL;
2688
+ const wsUrl = isLocal ? `${WS_URL}:${port}` : WS_URL;
1361
2689
  logger.info(`Connecting to Figma socket server at ${wsUrl}...`);
1362
2690
  ws = new import_ws.default(wsUrl);
1363
2691
  ws.on("open", () => {
@@ -1447,7 +2775,7 @@ function connectToFigma(port = activePort) {
1447
2775
  pendingRequests.delete(id);
1448
2776
  }
1449
2777
  if (rejected) {
1450
- logger.info("Not reconnecting \u2014 channel role was rejected. Call join_channel to retry.");
2778
+ logger.info('Not reconnecting \u2014 channel role was rejected. Call connection(method: "create") to retry.');
1451
2779
  } else {
1452
2780
  logger.info("Attempting to reconnect in 2 seconds...");
1453
2781
  setTimeout(() => connectToFigma(port), 2e3);
@@ -1482,7 +2810,7 @@ function sendCommandToFigma(command, params = {}, timeoutMs = 3e4) {
1482
2810
  }
1483
2811
  const requiresChannel = command !== "join";
1484
2812
  if (requiresChannel && !currentChannel) {
1485
- reject(new Error("No channel joined. Call join_channel first with the channel name shown in the Figma plugin panel."));
2813
+ reject(new Error('No channel joined. Call connection(method: "create") first with the channel name shown in the Figma plugin panel.'));
1486
2814
  return;
1487
2815
  }
1488
2816
  const id = (0, import_uuid.v4)();
@@ -1518,105 +2846,73 @@ var server = new import_mcp.McpServer({
1518
2846
  name: "VibmaMCP",
1519
2847
  version: "1.0.0"
1520
2848
  });
2849
+ var connectionDef = tools.find((t) => t.name === "connection");
2850
+ var connectionSchema = typeof connectionDef.schema === "function" ? connectionDef.schema(caps) : connectionDef.schema;
1521
2851
  server.registerTool(
1522
- "join_channel",
1523
- {
1524
- description: "REQUIRED FIRST STEP: Join a channel before using any other tool. The channel name is shown in the Figma plugin UI (defaults to 'vibma' if not customised). After joining, call `ping` to verify the Figma plugin is connected. All subsequent commands are sent through this channel.",
1525
- inputSchema: { channel: import_zod18.z.string().describe("The channel name displayed in the Figma plugin panel. Defaults to 'vibma' if omitted.").default("vibma") }
1526
- },
1527
- async ({ channel }) => {
2852
+ "connection",
2853
+ { description: connectionDef.description, inputSchema: connectionSchema },
2854
+ async (params) => {
2855
+ const method = params.method;
1528
2856
  try {
1529
- await joinChannel(channel);
1530
- await new Promise((r) => setTimeout(r, 200));
1531
- let msg = `Joined channel "${channel}" on port ${activePort}. Call \`ping\` now to verify the Figma plugin is connected.`;
1532
- if (versionWarning) msg += `
2857
+ if (method === "help") {
2858
+ const text = resolveEndpointHelp("connection", params.topic) ?? "No help available for connection";
2859
+ return { content: [{ type: "text", text }] };
2860
+ }
2861
+ if (method === "create") {
2862
+ const channel = params.channel || "vibma";
2863
+ await joinChannel(channel);
2864
+ await new Promise((r) => setTimeout(r, 200));
2865
+ let msg = `Joined channel "${channel}" on port ${activePort}. Call connection(method: "get") to verify the Figma plugin is connected.`;
2866
+ if (versionWarning) msg += `
1533
2867
 
1534
2868
  \u26A0\uFE0F ${versionWarning}
1535
2869
  See "Version mismatch" in CARRYME.md or DRAGME.md for update steps.`;
1536
- return {
1537
- content: [{ type: "text", text: msg }]
1538
- };
1539
- } catch (error) {
1540
- return {
1541
- content: [{
1542
- type: "text",
1543
- text: `Error joining channel: ${error instanceof Error ? error.message : String(error)}. MCP is using port ${activePort} \u2014 confirm the relay is running on the same port.`
1544
- }]
1545
- };
1546
- }
1547
- }
1548
- );
1549
- server.registerTool(
1550
- "channel_info",
1551
- {
1552
- description: "Debug: inspect which clients (MCP, plugin) are connected to each relay channel. Useful for diagnosing connection issues. Does not require an active channel."
1553
- },
1554
- async () => {
1555
- try {
1556
- const url = serverUrl === "localhost" ? `http://localhost:${activePort}/channels` : `https://${serverUrl}/channels`;
1557
- const response = await fetch(url);
1558
- if (!response.ok) {
1559
- return { content: [{ type: "text", text: `Relay returned ${response.status}: ${await response.text()}` }] };
2870
+ msg += "\n\nWelcome to Vibma! As you work, the MCP will give you warnings when it spots issues \u2014 hardcoded colors, missing auto-layout, unbound tokens, etc. Following these best practices will reduce the noise from the MCP and help you create a well-structured design system that designers enjoy working with.";
2871
+ return { content: [{ type: "text", text: msg }] };
1560
2872
  }
1561
- const data = await response.json();
1562
- return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
1563
- } catch (error) {
1564
- return {
1565
- content: [{
1566
- type: "text",
1567
- text: `Could not reach relay at port ${activePort}: ${error instanceof Error ? error.message : String(error)}`
1568
- }]
1569
- };
1570
- }
1571
- }
1572
- );
1573
- server.registerTool(
1574
- "reset_tunnel",
1575
- {
1576
- description: "DESTRUCTIVE: Factory-reset a channel on the relay via HTTP, disconnecting ALL occupants (MCP + Figma plugin). Only use this when the channel is stuck or occupied by another MCP \u2014 do NOT use it just to reconnect yourself (use `join_channel` instead). After reset, ask the user to reopen the Vibma plugin in Figma, then call `join_channel` and `ping`.",
1577
- inputSchema: {
1578
- channel: import_zod18.z.string().describe("Channel to reset. Defaults to 'vibma'.").default("vibma")
1579
- }
1580
- },
1581
- async ({ channel }) => {
1582
- const targetChannel = channel || currentChannel || "vibma";
1583
- try {
1584
- const url = serverUrl === "localhost" ? `http://localhost:${activePort}/channels/${encodeURIComponent(targetChannel)}` : `https://${serverUrl}/channels/${encodeURIComponent(targetChannel)}`;
1585
- const res = await fetch(url, { method: "DELETE" });
1586
- const body = await res.json();
1587
- for (const [reqId, request] of pendingRequests.entries()) {
1588
- clearTimeout(request.timeout);
1589
- request.reject(new Error("Tunnel reset by user"));
1590
- pendingRequests.delete(reqId);
2873
+ if (method === "list") {
2874
+ const url = isLocal ? `http://${serverUrl}:${activePort}/channels` : `https://${serverUrl}/channels`;
2875
+ const response = await fetch(url);
2876
+ if (!response.ok) return { content: [{ type: "text", text: `Relay returned ${response.status}: ${await response.text()}` }] };
2877
+ const data = await response.json();
2878
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
1591
2879
  }
1592
- if (ws) {
1593
- const old = ws;
1594
- ws = null;
1595
- old.removeAllListeners();
1596
- old.close(1e3, "Tunnel reset");
2880
+ if (method === "delete") {
2881
+ const targetChannel = params.channel || currentChannel || "vibma";
2882
+ const url = isLocal ? `http://${serverUrl}:${activePort}/channels/${encodeURIComponent(targetChannel)}` : `https://${serverUrl}/channels/${encodeURIComponent(targetChannel)}`;
2883
+ const res = await fetch(url, { method: "DELETE" });
2884
+ const body = await res.json();
2885
+ for (const [reqId, request] of pendingRequests.entries()) {
2886
+ clearTimeout(request.timeout);
2887
+ request.reject(new Error("Tunnel reset by user"));
2888
+ pendingRequests.delete(reqId);
2889
+ }
2890
+ if (ws) {
2891
+ const old = ws;
2892
+ ws = null;
2893
+ old.removeAllListeners();
2894
+ old.close(1e3, "Tunnel reset");
2895
+ }
2896
+ currentChannel = null;
2897
+ rejected = false;
2898
+ connectToFigma();
2899
+ await new Promise((resolve) => setTimeout(resolve, 1e3));
2900
+ const connected = ws && ws.readyState === import_ws.default.OPEN;
2901
+ return {
2902
+ content: [{
2903
+ type: "text",
2904
+ text: connected ? `Tunnel reset: ${body.message}. Reconnected on port ${activePort}.
2905
+
2906
+ IMPORTANT: The Figma plugin was also disconnected. Ask the user to reopen the Vibma plugin, then call connection(method: "create") followed by connection(method: "get").` : `Tunnel reset: ${body.message}. Reconnection in progress.
2907
+
2908
+ IMPORTANT: The Figma plugin was also disconnected. Ask the user to reopen the Vibma plugin, then call connection(method: "create") to retry.`
2909
+ }]
2910
+ };
1597
2911
  }
1598
- currentChannel = null;
1599
- rejected = false;
1600
- connectToFigma();
1601
- await new Promise((resolve) => setTimeout(resolve, 1e3));
1602
- const connected = ws && ws.readyState === import_ws.default.OPEN;
1603
- return {
1604
- content: [{
1605
- type: "text",
1606
- text: connected ? `Tunnel reset: ${body.message}. Reconnected on port ${activePort}.
1607
-
1608
- IMPORTANT: The Figma plugin was also disconnected. Ask the user to reopen the Vibma plugin in Figma (or click "Reset tunnel" in the plugin panel). Then call \`join_channel\` followed by \`ping\`.` : `Tunnel reset: ${body.message}. Reconnection in progress.
1609
-
1610
- IMPORTANT: The Figma plugin was also disconnected. Ask the user to reopen the Vibma plugin in Figma (or click "Reset tunnel" in the plugin panel). Then call \`join_channel\` to retry.`
1611
- }]
1612
- };
2912
+ const result = await sendCommandToFigma("connection.get", params, 5e3);
2913
+ return { content: [{ type: "text", text: JSON.stringify(result) }] };
1613
2914
  } catch (error) {
1614
- return {
1615
- content: [{
1616
- type: "text",
1617
- text: `Error resetting tunnel: ${error instanceof Error ? error.message : String(error)}`
1618
- }]
1619
- };
2915
+ return { content: [{ type: "text", text: `connection.${method} error: ${error instanceof Error ? error.message : String(error)}` }] };
1620
2916
  }
1621
2917
  }
1622
2918
  );