@ufira/vibma 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils/color.ts","../src/utils/serialize-node.ts","../src/tools/helpers.ts","../src/mcp.ts","../src/tools/document.ts","../src/tools/types.ts","../src/tools/selection.ts","../src/utils/coercion.ts","../src/tools/node-info.ts","../src/tools/create-shape.ts","../src/tools/schemas.ts","../src/tools/create-frame.ts","../src/tools/create-text.ts","../src/tools/modify-node.ts","../src/tools/fill-stroke.ts","../src/tools/update-frame.ts","../src/tools/effects.ts","../src/tools/text.ts","../src/tools/fonts.ts","../src/tools/components.ts","../src/tools/styles.ts","../src/tools/variables.ts","../src/tools/lint.ts","../src/tools/connection.ts","../src/tools/prompts.ts","../src/tools/mcp-registry.ts"],"sourcesContent":["/** Convert RGBA (0-1 float) to hex string. Handles both server and plugin contexts. */\nexport function rgbaToHex(color: any): string {\n if (typeof color === \"string\" && color.startsWith(\"#\")) return color;\n\n const r = Math.round(color.r * 255);\n const g = Math.round(color.g * 255);\n const b = Math.round(color.b * 255);\n const a = color.a !== undefined ? Math.round(color.a * 255) : 255;\n\n const hex = [r, g, b].map((x) => x.toString(16).padStart(2, \"0\")).join(\"\");\n return a === 255 ? `#${hex}` : `#${hex}${a.toString(16).padStart(2, \"0\")}`;\n}\n","import { rgbaToHex } from \"./color\";\n\n/**\n * Serialize a Figma plugin node to a plain object using only the plugin API.\n * This replaces the exportAsync({ format: \"JSON_REST_V1\" }) + filterFigmaNode\n * approach, which returned REST API IDs that could differ from plugin node.id.\n *\n * @param node - A Figma plugin BaseNode\n * @param depth - Child recursion depth. -1 = unlimited, 0 = stubs only.\n */\nexport function serializeNode(\n node: BaseNode,\n depth: number = -1,\n currentDepth: number = 0,\n): any {\n // VECTORs: always a stub — no useful extractable properties\n if (node.type === \"VECTOR\") {\n return { id: node.id, name: node.name, type: node.type };\n }\n\n const out: any = {\n id: node.id,\n name: node.name,\n type: node.type,\n };\n\n // Parent info at root level\n if (currentDepth === 0 && node.parent) {\n out.parentId = node.parent.id;\n out.parentName = node.parent.name;\n out.parentType = node.parent.type;\n }\n\n // ── Fills ──────────────────────────────────────────────────────\n if (\"fills\" in node) {\n const fills = (node as any).fills;\n if (fills !== figma.mixed && Array.isArray(fills) && fills.length > 0) {\n out.fills = fills.map(serializePaint);\n }\n }\n\n // ── Strokes ────────────────────────────────────────────────────\n if (\"strokes\" in node) {\n const strokes = (node as any).strokes;\n if (Array.isArray(strokes) && strokes.length > 0) {\n out.strokes = strokes.map(serializePaint);\n }\n }\n\n // ── Corner radius ─────────────────────────────────────────────\n if (\"cornerRadius\" in node) {\n const cr = (node as any).cornerRadius;\n if (cr !== undefined && cr !== figma.mixed) out.cornerRadius = cr;\n }\n\n // ── Bounding box ──────────────────────────────────────────────\n if (\"absoluteBoundingBox\" in node && (node as any).absoluteBoundingBox) {\n out.absoluteBoundingBox = (node as any).absoluteBoundingBox;\n } else if (\"absoluteTransform\" in node && \"width\" in node) {\n const t = (node as any).absoluteTransform;\n if (t) {\n out.absoluteBoundingBox = {\n x: t[0][2], y: t[1][2],\n width: (node as any).width,\n height: (node as any).height,\n };\n }\n }\n\n // ── Text content ──────────────────────────────────────────────\n if (\"characters\" in node) {\n out.characters = (node as any).characters;\n }\n\n // ── Instance → source component ───────────────────────────────\n if (node.type === \"INSTANCE\") {\n const inst = node as InstanceNode;\n if (inst.mainComponent) {\n out.componentId = inst.mainComponent.id;\n out.componentName = inst.mainComponent.name;\n }\n }\n\n // ── Text style ────────────────────────────────────────────────\n if (node.type === \"TEXT\") {\n const t = node as TextNode;\n const style: any = {};\n if (t.fontName !== figma.mixed) {\n style.fontFamily = (t.fontName as FontName).family;\n style.fontStyle = (t.fontName as FontName).style;\n }\n if (t.fontSize !== figma.mixed) style.fontSize = t.fontSize;\n if (t.textAlignHorizontal) style.textAlignHorizontal = t.textAlignHorizontal;\n if (t.letterSpacing !== figma.mixed) {\n const ls = t.letterSpacing as LetterSpacing;\n style.letterSpacing = ls.unit === \"PIXELS\" ? ls.value : ls;\n }\n if (t.lineHeight !== figma.mixed) {\n const lh = t.lineHeight as LineHeight;\n if (lh.unit === \"PIXELS\") style.lineHeightPx = lh.value;\n else if (lh.unit !== \"AUTO\") style.lineHeight = lh;\n }\n if (Object.keys(style).length > 0) out.style = style;\n }\n\n // ── Effects ───────────────────────────────────────────────────\n if (\"effects\" in node) {\n const effects = (node as any).effects;\n if (Array.isArray(effects) && effects.length > 0) {\n out.effects = effects.map((e: any) => {\n const eff: any = { type: e.type, visible: e.visible };\n if (e.radius !== undefined) eff.radius = e.radius;\n if (e.color) eff.color = rgbaToHex(e.color);\n if (e.offset) eff.offset = e.offset;\n if (e.spread !== undefined) eff.spread = e.spread;\n if (e.blendMode) eff.blendMode = e.blendMode;\n return eff;\n });\n }\n }\n\n // ── Layout ────────────────────────────────────────────────────\n if (\"layoutMode\" in node) {\n const lm = (node as any).layoutMode;\n if (lm && lm !== \"NONE\") out.layoutMode = lm;\n }\n if (\"itemSpacing\" in node) {\n const is = (node as any).itemSpacing;\n if (is !== undefined) out.itemSpacing = is;\n }\n if (\"paddingLeft\" in node) {\n const n = node as any;\n if (n.paddingLeft || n.paddingRight || n.paddingTop || n.paddingBottom) {\n out.padding = {\n left: n.paddingLeft, right: n.paddingRight,\n top: n.paddingTop, bottom: n.paddingBottom,\n };\n }\n }\n\n // ── Opacity / visibility ──────────────────────────────────────\n if (\"opacity\" in node) {\n const op = (node as any).opacity;\n if (op !== undefined && op !== 1) out.opacity = op;\n }\n if (\"visible\" in node) {\n out.visible = (node as any).visible;\n }\n\n // ── Constraints ───────────────────────────────────────────────\n if (\"constraints\" in node) {\n out.constraints = (node as any).constraints;\n }\n\n // ── Children ──────────────────────────────────────────────────\n if (\"children\" in node) {\n const children = (node as any).children as readonly BaseNode[];\n if (depth >= 0 && currentDepth >= depth) {\n // Stubs only\n out.children = children.map((c: BaseNode) => ({\n id: c.id, name: c.name, type: c.type,\n }));\n } else {\n out.children = children.map((c: BaseNode) =>\n serializeNode(c, depth, currentDepth + 1),\n );\n }\n }\n\n return out;\n}\n\n// ── Paint serialization ───────────────────────────────────────────\n\nfunction serializePaint(paint: any): any {\n const p: any = { type: paint.type };\n if (paint.visible !== undefined) p.visible = paint.visible;\n if (paint.opacity !== undefined) p.opacity = paint.opacity;\n if (paint.blendMode) p.blendMode = paint.blendMode;\n if (paint.color) {\n // Plugin API: color = {r,g,b}, opacity separate. Merge for hex.\n p.color = rgbaToHex({ ...paint.color, a: paint.opacity ?? 1 });\n }\n if (paint.gradientStops) {\n p.gradientStops = paint.gradientStops.map((stop: any) => ({\n position: stop.position,\n color: rgbaToHex(stop.color),\n }));\n }\n if (paint.gradientTransform) p.gradientTransform = paint.gradientTransform;\n if (paint.scaleMode) p.scaleMode = paint.scaleMode;\n return p;\n}\n","import { serializeNode } from \"../utils/serialize-node\";\n\n// ─── Figma Handler Utilities ────────────────────────────────────\n// Shared helpers for plugin-side (Figma) handler functions.\n\n/**\n * Snapshot a node using plugin API serialization.\n * Returns null if node not found.\n */\nexport async function nodeSnapshot(id: string, depth: number): Promise<any> {\n const node = await figma.getNodeByIdAsync(id);\n if (!node) return null;\n return serializeNode(node, depth);\n}\n\n/**\n * Process batch items with optional depth enrichment.\n * Reads `items` (array) and `depth` (number|undefined) from params.\n * If depth is defined and a result has an `id`, merges node snapshot into the result.\n */\nexport async function batchHandler(\n params: any,\n fn: (item: any) => Promise<any>,\n): Promise<any> {\n const items = params.items || [params];\n const depth = params.depth;\n const results = [];\n const warningSet = new Set<string>();\n for (const item of items) {\n try {\n let result = await fn(item);\n if (depth !== undefined && result?.id) {\n const snapshot = await nodeSnapshot(result.id, depth);\n if (snapshot) result = { ...result, ...snapshot };\n }\n // Hoist warnings to batch level (deduplicated)\n if (result?.warning) {\n warningSet.add(result.warning);\n delete result.warning;\n }\n // Replace empty objects with \"ok\" for readability\n if (result && typeof result === \"object\" && Object.keys(result).length === 0) {\n results.push(\"ok\");\n } else {\n results.push(result);\n }\n } catch (e: any) {\n results.push({ error: e.message });\n }\n }\n const out: any = { results };\n if (warningSet.size > 0) out.warnings = [...warningSet];\n return out;\n}\n\n/**\n * Append a node to a parent (by ID) or the current page.\n * Returns the parent node if parentId was given, null otherwise.\n */\nexport async function appendToParent(node: SceneNode, parentId?: string): Promise<BaseNode | null> {\n if (parentId) {\n const parent = await figma.getNodeByIdAsync(parentId);\n if (!parent) throw new Error(`Parent not found: ${parentId}`);\n if (!(\"appendChild\" in parent))\n throw new Error(`Parent does not support children: ${parentId}. Only FRAME, COMPONENT, GROUP, SECTION, and PAGE nodes can have children.`);\n (parent as any).appendChild(node);\n return parent;\n }\n figma.currentPage.appendChild(node);\n return null;\n}\n\n/**\n * Build a solid paint from an RGBA color object (channels 0-1).\n */\nexport function solidPaint(c: any) {\n return { type: \"SOLID\" as const, color: { r: c.r ?? 0, g: c.g ?? 0, b: c.b ?? 0 }, opacity: c.a ?? 1 };\n}\n\n/**\n * Resolve a variable by ID with scan fallback.\n * Direct lookup can fail for recently-created variables.\n */\nexport async function findVariableById(id: string): Promise<any> {\n const direct = await figma.variables.getVariableByIdAsync(id);\n if (direct) return direct;\n const all = await figma.variables.getLocalVariablesAsync();\n return all.find(v => v.id === id) || null;\n}\n\n/**\n * Format a \"style not found\" hint that includes available style names\n * so the agent can self-correct (e.g. \"Heading\" → \"Heading/H2\").\n */\nexport function styleNotFoundHint(param: string, value: string, available: string[], limit = 20): string {\n if (available.length === 0) return `${param} '${value}' not found (no local styles of this type exist).`;\n const names = available.slice(0, limit);\n const suffix = available.length > limit ? `, … and ${available.length - limit} more` : \"\";\n return `${param} '${value}' not found. Available: [${names.join(\", \")}${suffix}]`;\n}\n\n/**\n * Check if a hardcoded color matches any local paint style.\n * Returns a hint suggesting the exact style name if matched,\n * or a prompt to create a paint style if no match.\n */\nexport async function suggestStyleForColor(\n color: { r: number, g: number, b: number, a?: number },\n styleParam: string,\n): Promise<string> {\n const hex = `#${[color.r, color.g, color.b].map(v => Math.round((v ?? 0) * 255).toString(16).padStart(2, \"0\")).join(\"\")}`;\n const styles = await figma.getLocalPaintStylesAsync();\n const eps = 0.02;\n for (const style of styles) {\n const paints = style.paints;\n if (paints.length === 1 && paints[0].type === \"SOLID\") {\n const sc = (paints[0] as SolidPaint).color;\n const so = (paints[0] as SolidPaint).opacity ?? 1;\n if (Math.abs(sc.r - (color.r ?? 0)) < eps &&\n Math.abs(sc.g - (color.g ?? 0)) < eps &&\n Math.abs(sc.b - (color.b ?? 0)) < eps &&\n Math.abs(so - (color.a ?? 1)) < eps) {\n return `Hardcoded color ${hex} matches style '${style.name}'. Use ${styleParam}: '${style.name}' to link to the design token.`;\n }\n }\n }\n return `Hardcoded color ${hex} has no matching paint style. Create one with create_paint_style, then use ${styleParam} for design token consistency.`;\n}\n\n/**\n * Check if manual font properties match any local text style.\n * Returns a hint suggesting the matching style name if found,\n * or a prompt to create a text style if no match.\n */\nexport async function suggestTextStyle(\n fontSize: number,\n fontWeight: number,\n): Promise<string> {\n const styles = await figma.getLocalTextStylesAsync();\n const matching = styles.filter(s => s.fontSize === fontSize);\n if (matching.length > 0) {\n const names = matching.map(s => s.name).slice(0, 5);\n return `Manual font (${fontSize}px / ${fontWeight}w) — text styles at same size: [${names.join(\", \")}]. Use textStyleName to link to a design token.`;\n }\n return `Manual font (${fontSize}px / ${fontWeight}w) has no text style. Create one with create_text_style, then use textStyleName for design token consistency.`;\n}\n","#!/usr/bin/env node\n\nimport { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { z } from \"zod\";\nimport WebSocket from \"ws\";\nimport { v4 as uuidv4 } from \"uuid\";\nimport { registerAllTools } from \"./tools/mcp-registry\";\n\n// ─── Logger (stderr so it doesn't pollute MCP stdio) ────────────\nconst logger = {\n info: (msg: string) => process.stderr.write(`[INFO] ${msg}\\n`),\n debug: (msg: string) => process.stderr.write(`[DEBUG] ${msg}\\n`),\n warn: (msg: string) => process.stderr.write(`[WARN] ${msg}\\n`),\n error: (msg: string) => process.stderr.write(`[ERROR] ${msg}\\n`),\n log: (msg: string) => process.stderr.write(`[LOG] ${msg}\\n`),\n};\n\n// ─── Types ───────────────────────────────────────────────────────\n\ninterface FigmaResponse {\n id: string;\n result?: any;\n error?: string;\n}\n\ninterface CommandProgressUpdate {\n type: \"command_progress\";\n commandId: string;\n commandType: string;\n status: \"started\" | \"in_progress\" | \"completed\" | \"error\";\n progress: number;\n totalItems: number;\n processedItems: number;\n currentChunk?: number;\n totalChunks?: number;\n chunkSize?: number;\n message: string;\n payload?: any;\n timestamp: number;\n}\n\n// ─── WebSocket state ─────────────────────────────────────────────\n\nlet ws: WebSocket | null = null;\nconst pendingRequests = new Map<\n string,\n {\n resolve: (value: unknown) => void;\n reject: (reason: unknown) => void;\n timeout: ReturnType<typeof setTimeout>;\n lastActivity: number;\n }\n>();\nlet currentChannel: string | null = null;\nlet activePort: number = parseInt(process.env.VIBMA_PORT || \"3055\");\n\n// CLI args\nconst args = process.argv.slice(2);\nconst serverArg = args.find((a) => a.startsWith(\"--server=\"));\nconst portArg = args.find((a) => a.startsWith(\"--port=\"));\nconst serverUrl = serverArg ? serverArg.split(\"=\")[1] : \"localhost\";\nif (portArg) activePort = parseInt(portArg.split(\"=\")[1]);\nconst WS_URL = serverUrl === \"localhost\" ? `ws://${serverUrl}` : `wss://${serverUrl}`;\n\n// ─── WebSocket connection ────────────────────────────────────────\n\nfunction connectToFigma(port: number = activePort) {\n activePort = port;\n if (ws && ws.readyState === WebSocket.OPEN) {\n logger.info(\"Already connected to Figma\");\n return;\n }\n\n const wsUrl = serverUrl === \"localhost\" ? `${WS_URL}:${port}` : WS_URL;\n logger.info(`Connecting to Figma socket server at ${wsUrl}...`);\n ws = new WebSocket(wsUrl);\n\n ws.on(\"open\", () => {\n logger.info(\"Connected to Figma socket server\");\n currentChannel = null;\n });\n\n ws.on(\"message\", (data: any) => {\n try {\n const json = JSON.parse(data) as any;\n\n // Handle progress updates\n if (json.type === \"progress_update\") {\n const progressData = json.message.data as CommandProgressUpdate;\n const requestId = json.id || \"\";\n\n if (requestId && pendingRequests.has(requestId)) {\n const request = pendingRequests.get(requestId)!;\n request.lastActivity = Date.now();\n clearTimeout(request.timeout);\n request.timeout = setTimeout(() => {\n if (pendingRequests.has(requestId)) {\n logger.error(`Request ${requestId} timed out after extended period of inactivity`);\n pendingRequests.delete(requestId);\n request.reject(new Error(\"Request to Figma timed out\"));\n }\n }, 60000);\n logger.info(`Progress update for ${progressData.commandType}: ${progressData.progress}% - ${progressData.message}`);\n if (progressData.status === \"completed\" && progressData.progress === 100) {\n logger.info(`Operation ${progressData.commandType} completed, waiting for final result`);\n }\n }\n return;\n }\n\n // Handle regular responses\n const myResponse = json.message;\n logger.debug(`Received message: ${JSON.stringify(myResponse)}`);\n\n if (myResponse.id && pendingRequests.has(myResponse.id) && myResponse.result) {\n const request = pendingRequests.get(myResponse.id)!;\n clearTimeout(request.timeout);\n if (myResponse.error) {\n logger.error(`Error from Figma: ${myResponse.error}`);\n request.reject(new Error(myResponse.error));\n } else {\n request.resolve(myResponse.result);\n }\n pendingRequests.delete(myResponse.id);\n } else {\n logger.info(`Received broadcast message: ${JSON.stringify(myResponse)}`);\n }\n } catch (error) {\n logger.error(`Error parsing message: ${error instanceof Error ? error.message : String(error)}`);\n }\n });\n\n ws.on(\"error\", (error) => {\n logger.error(`Socket error: ${error}`);\n });\n\n ws.on(\"close\", () => {\n logger.info(\"Disconnected from Figma socket server\");\n ws = null;\n for (const [id, request] of pendingRequests.entries()) {\n clearTimeout(request.timeout);\n request.reject(new Error(\"Connection closed\"));\n pendingRequests.delete(id);\n }\n logger.info(\"Attempting to reconnect in 2 seconds...\");\n setTimeout(() => connectToFigma(port), 2000);\n });\n}\n\n// ─── Channel management ──────────────────────────────────────────\n\nasync function joinChannel(channelName: string): Promise<void> {\n if (!ws || ws.readyState !== WebSocket.OPEN) {\n throw new Error(\"Not connected to Figma\");\n }\n try {\n await sendCommandToFigma(\"join\", { channel: channelName });\n currentChannel = channelName;\n logger.info(`Joined channel: ${channelName}`);\n } catch (error) {\n logger.error(`Failed to join channel: ${error instanceof Error ? error.message : String(error)}`);\n throw error;\n }\n}\n\n// ─── Send command to Figma ───────────────────────────────────────\n\nfunction sendCommandToFigma(\n command: string,\n params: unknown = {},\n timeoutMs: number = 30000\n): Promise<unknown> {\n return new Promise((resolve, reject) => {\n if (!ws || ws.readyState !== WebSocket.OPEN) {\n connectToFigma();\n reject(new Error(\"Not connected to Figma. Attempting to connect...\"));\n return;\n }\n\n const requiresChannel = command !== \"join\";\n if (requiresChannel && !currentChannel) {\n reject(new Error(\"No channel joined. Call join_channel first with the channel name shown in the Figma plugin panel.\"));\n return;\n }\n\n const id = uuidv4();\n const request = {\n id,\n type: command === \"join\" ? \"join\" : \"message\",\n ...(command === \"join\" ? { channel: (params as any).channel } : { channel: currentChannel }),\n message: {\n id,\n command,\n params: {\n ...(params as any),\n commandId: id,\n },\n },\n };\n\n const timeout = setTimeout(() => {\n if (pendingRequests.has(id)) {\n pendingRequests.delete(id);\n logger.error(`Request ${id} to Figma timed out after ${timeoutMs / 1000} seconds`);\n reject(new Error(\n `Request to Figma timed out. This usually means the Figma plugin is not connected to the relay. ` +\n `MCP is using port ${activePort}, channel \"${currentChannel}\". ` +\n `Check the Figma plugin window: the port and channel name must match.`\n ));\n }\n }, timeoutMs);\n\n pendingRequests.set(id, { resolve, reject, timeout, lastActivity: Date.now() });\n logger.info(`Sending command to Figma: ${command}`);\n logger.debug(`Request details: ${JSON.stringify(request)}`);\n ws.send(JSON.stringify(request));\n });\n}\n\n// ─── MCP Server bootstrap ────────────────────────────────────────\n\nconst server = new McpServer({\n name: \"VibmaMCP\",\n version: \"1.0.0\",\n});\n\n// Register the join_channel tool directly (it uses local state)\nserver.tool(\n \"join_channel\",\n \"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.\",\n { channel: z.string().describe(\"The channel name displayed in the Figma plugin panel. Defaults to 'vibma' if omitted.\").default(\"vibma\") },\n async ({ channel }: any) => {\n try {\n await joinChannel(channel);\n return {\n content: [{ type: \"text\", text: `Joined channel \"${channel}\" on port ${activePort}. Call \\`ping\\` now to verify the Figma plugin is connected.` }],\n };\n } catch (error) {\n return {\n content: [{\n type: \"text\",\n text: `Error joining channel: ${error instanceof Error ? error.message : String(error)}. MCP is using port ${activePort} — confirm the relay is running on the same port.`,\n }],\n };\n }\n }\n);\n\n// Register all per-tool-file tools and prompts\nregisterAllTools(server, sendCommandToFigma);\n\n// ─── Start ───────────────────────────────────────────────────────\n\nasync function main() {\n try {\n connectToFigma();\n } catch (error) {\n logger.warn(`Could not connect to Figma initially: ${error instanceof Error ? error.message : String(error)}`);\n logger.warn(\"Will try to connect when the first command is sent\");\n }\n\n const transport = new StdioServerTransport();\n await server.connect(transport);\n logger.info(\"FigmaMCP server running on stdio\");\n}\n\nmain().catch((error) => {\n logger.error(`Error starting FigmaMCP server: ${error instanceof Error ? error.message : String(error)}`);\n process.exit(1);\n});\n","import { z } from \"zod\";\nimport type { McpServer, SendCommandFn } from \"./types\";\nimport { mcpJson, mcpError } from \"./types\";\n\n// ─── MCP Registration ────────────────────────────────────────────\n\nexport function registerMcpTools(server: McpServer, sendCommand: SendCommandFn) {\n server.tool(\n \"get_document_info\",\n \"Get the document name, current page, and list of all pages.\",\n {},\n async () => {\n try { return mcpJson(await sendCommand(\"get_document_info\")); }\n catch (e) { return mcpError(\"Error getting document info\", e); }\n }\n );\n\n server.tool(\n \"get_current_page\",\n \"Get the current page info and its top-level children. Always safe — never touches unloaded pages.\",\n {},\n async () => {\n try { return mcpJson(await sendCommand(\"get_current_page\")); }\n catch (e) { return mcpError(\"Error getting current page\", e); }\n }\n );\n\n server.tool(\n \"get_pages\",\n \"Get all pages in the document with their IDs, names, and child counts.\",\n {},\n async () => {\n try { return mcpJson(await sendCommand(\"get_pages\")); }\n catch (e) { return mcpError(\"Error getting pages\", e); }\n }\n );\n\n server.tool(\n \"set_current_page\",\n \"Switch to a different page. Provide either pageId or pageName.\",\n {\n pageId: z.string().optional().describe(\"The page ID to switch to\"),\n pageName: z.string().optional().describe(\"The page name (case-insensitive, partial match)\"),\n },\n async (params: any) => {\n try { return mcpJson(await sendCommand(\"set_current_page\", params)); }\n catch (e) { return mcpError(\"Error setting current page\", e); }\n }\n );\n\n server.tool(\n \"create_page\",\n \"Create a new page in the document\",\n { name: z.string().optional().describe(\"Name for the new page (default: 'New Page')\") },\n async ({ name }: any) => {\n try { return mcpJson(await sendCommand(\"create_page\", { name })); }\n catch (e) { return mcpError(\"Error creating page\", e); }\n }\n );\n\n server.tool(\n \"rename_page\",\n \"Rename a page. Defaults to current page if no pageId given.\",\n {\n newName: z.string().describe(\"New name for the page\"),\n pageId: z.string().optional().describe(\"Page ID (default: current page)\"),\n },\n async (params: any) => {\n try { return mcpJson(await sendCommand(\"rename_page\", params)); }\n catch (e) { return mcpError(\"Error renaming page\", e); }\n }\n );\n}\n\n// ─── Figma Handlers ──────────────────────────────────────────────\n\nasync function getDocumentInfo() {\n return {\n name: figma.root.name,\n currentPageId: figma.currentPage.id,\n pages: figma.root.children.map((p: any) => (\n { id: p.id, name: p.name }\n )),\n };\n}\n\nasync function getCurrentPage() {\n await figma.currentPage.loadAsync();\n const page = figma.currentPage;\n return {\n id: page.id,\n name: page.name,\n children: page.children.map((node: any) => ({ id: node.id, name: node.name, type: node.type })),\n };\n}\n\nasync function getPages() {\n return {\n currentPageId: figma.currentPage.id,\n pages: figma.root.children.map((p: any) => (\n { id: p.id, name: p.name }\n )),\n };\n}\n\nasync function setCurrentPage(params: any) {\n let page: any;\n if (params.pageId) {\n page = await figma.getNodeByIdAsync(params.pageId);\n if (!page || page.type !== \"PAGE\") throw new Error(`Page not found: ${params.pageId}`);\n } else if (params.pageName) {\n const name = params.pageName.toLowerCase();\n page = figma.root.children.find((p: any) => p.name.toLowerCase() === name);\n if (!page) page = figma.root.children.find((p: any) => p.name.toLowerCase().includes(name));\n if (!page) {\n const available = figma.root.children.map((p: any) => p.name);\n throw new Error(`Page not found: '${params.pageName}'. Available pages: [${available.join(\", \")}]`);\n }\n }\n await figma.setCurrentPageAsync(page);\n return { id: page.id, name: page.name };\n}\n\nasync function createPage(params: any) {\n const name = params?.name || \"New Page\";\n const page = figma.createPage();\n page.name = name;\n return { id: page.id };\n}\n\nasync function renamePage(params: any) {\n if (!params?.newName) throw new Error(\"Missing newName parameter\");\n let page: any;\n if (params.pageId) {\n page = await figma.getNodeByIdAsync(params.pageId);\n if (!page || page.type !== \"PAGE\") throw new Error(`Page not found: ${params.pageId}`);\n } else {\n page = figma.currentPage;\n }\n page.name = params.newName;\n return \"ok\";\n}\n\nexport const figmaHandlers: Record<string, (params: any) => Promise<any>> = {\n get_document_info: getDocumentInfo,\n get_current_page: getCurrentPage,\n get_pages: getPages,\n set_current_page: setCurrentPage,\n create_page: createPage,\n rename_page: renamePage,\n};\n","import type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\n\n/** Function signature for sending commands to Figma via WebSocket */\nexport type SendCommandFn = (command: string, params?: unknown, timeoutMs?: number) => Promise<unknown>;\n\n/** Re-export McpServer type for tool files */\nexport type { McpServer };\n\n/** Standard batch result from Figma handlers */\nexport interface BatchResult<T = any> {\n results: Array<T & { error?: string }>;\n}\n\n/** Max response size in characters (~12K tokens). Prevents LLM client-side truncation that corrupts JSON. */\nconst MAX_RESPONSE_CHARS = 50_000;\n\n/** Format a successful MCP response (JSON). Returns a clean error if response exceeds safe size. */\nexport function mcpJson(data: unknown) {\n const text = JSON.stringify(data);\n if (text.length <= MAX_RESPONSE_CHARS) {\n return { content: [{ type: \"text\" as const, text }] };\n }\n return {\n content: [{\n type: \"text\" as const,\n text: JSON.stringify({\n _error: \"response_too_large\",\n _sizeKB: Math.round(text.length / 1024),\n warning: \"Response exceeds safe size. Use 'depth', 'fields', 'limit', or 'summaryOnly' parameters to reduce response size.\",\n }),\n }],\n };\n}\n\n/** Format an error MCP response */\nexport function mcpError(prefix: string, error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n return { content: [{ type: \"text\" as const, text: `${prefix}: ${msg}` }] };\n}\n","import { z } from \"zod\";\nimport { flexJson } from \"../utils/coercion\";\nimport type { McpServer, SendCommandFn } from \"./types\";\nimport { mcpJson, mcpError } from \"./types\";\n\n// ─── MCP Registration ────────────────────────────────────────────\n\nexport function registerMcpTools(server: McpServer, sendCommand: SendCommandFn) {\n server.tool(\n \"get_selection\",\n \"Get information about the current selection in Figma\",\n {},\n async () => {\n try { return mcpJson(await sendCommand(\"get_selection\")); }\n catch (e) { return mcpError(\"Error getting selection\", e); }\n }\n );\n\n server.tool(\n \"read_my_design\",\n \"Get detailed information about the current selection, including all node details. Use depth to control traversal.\",\n { depth: z.coerce.number().optional().describe(\"Levels of children to recurse. 0=selection only, -1 or omit for unlimited.\") },\n async ({ depth }: any) => {\n try { return mcpJson(await sendCommand(\"read_my_design\", { depth })); }\n catch (e) { return mcpError(\"Error reading design\", e); }\n }\n );\n\n server.tool(\n \"set_selection\",\n \"Set selection to nodes and scroll viewport to show them. Also works as focus (single node).\",\n {\n nodeIds: flexJson(z.array(z.string())).describe('Array of node IDs to select. Example: [\"1:2\",\"1:3\"]'),\n },\n async ({ nodeIds }: any) => {\n try { return mcpJson(await sendCommand(\"set_selection\", { nodeIds })); }\n catch (e) { return mcpError(\"Error setting selection\", e); }\n }\n );\n\n server.tool(\n \"zoom_into_view\",\n \"Zoom the viewport to fit specific nodes (like pressing Shift+1)\",\n {\n nodeIds: flexJson(z.array(z.string())).describe(\"Array of node IDs to zoom into\"),\n },\n async ({ nodeIds }: any) => {\n try { return mcpJson(await sendCommand(\"zoom_into_view\", { nodeIds })); }\n catch (e) { return mcpError(\"Error zooming\", e); }\n }\n );\n\n server.tool(\n \"set_viewport\",\n \"Set viewport center position and/or zoom level\",\n {\n center: flexJson(z.object({ x: z.coerce.number(), y: z.coerce.number() })).optional().describe(\"Viewport center point. Omit to keep current center.\"),\n zoom: z.coerce.number().optional().describe(\"Zoom level (1 = 100%). Omit to keep current zoom.\"),\n },\n async (params: any) => {\n try { return mcpJson(await sendCommand(\"set_viewport\", params)); }\n catch (e) { return mcpError(\"Error setting viewport\", e); }\n }\n );\n}\n\n// ─── Figma Handlers ──────────────────────────────────────────────\n\nasync function getSelection() {\n return {\n selectionCount: figma.currentPage.selection.length,\n selection: figma.currentPage.selection.map((node) => ({\n id: node.id,\n name: node.name,\n type: node.type,\n visible: node.visible,\n })),\n };\n}\n\nasync function readMyDesign(params: any) {\n const sel = figma.currentPage.selection;\n if (sel.length === 0) {\n return { selectionCount: 0, warning: \"Nothing selected. Use set_selection to select nodes first, or use get_node_info with specific node IDs.\" };\n }\n\n const { serializeNode } = await import(\"../utils/serialize-node\");\n const depth = params?.depth;\n const responses = sel.map((node) => ({\n nodeId: node.id,\n document: serializeNode(node, depth !== undefined ? depth : -1),\n }));\n return { selectionCount: responses.length, nodes: responses };\n}\n\nasync function setSelection(params: any) {\n const nodeIds = params?.nodeIds;\n if (!nodeIds || !Array.isArray(nodeIds) || nodeIds.length === 0) {\n throw new Error(\"Missing or empty nodeIds\");\n }\n\n const nodes: SceneNode[] = [];\n const notFound: string[] = [];\n for (const id of nodeIds) {\n const node = await figma.getNodeByIdAsync(id);\n if (node) nodes.push(node as SceneNode);\n else notFound.push(id);\n }\n if (nodes.length === 0) throw new Error(`No valid nodes found: ${nodeIds.join(\", \")}`);\n\n figma.currentPage.selection = nodes;\n figma.viewport.scrollAndZoomIntoView(nodes);\n\n return {\n count: nodes.length,\n selectedNodes: nodes.map((n) => ({ name: n.name, id: n.id })),\n notFoundIds: notFound.length > 0 ? notFound : undefined,\n };\n}\n\nasync function zoomIntoView(params: any) {\n if (!params?.nodeIds?.length) throw new Error(\"Missing nodeIds\");\n const nodes: SceneNode[] = [];\n const notFound: string[] = [];\n for (const id of params.nodeIds) {\n const node = await figma.getNodeByIdAsync(id);\n if (node) nodes.push(node as SceneNode);\n else notFound.push(id);\n }\n if (nodes.length === 0) throw new Error(\"None of the specified nodes were found\");\n figma.viewport.scrollAndZoomIntoView(nodes);\n return {\n viewportCenter: figma.viewport.center,\n viewportZoom: figma.viewport.zoom,\n nodeCount: nodes.length,\n notFound: notFound.length > 0 ? notFound : undefined,\n };\n}\n\nasync function setViewport(params: any) {\n if (!params) throw new Error(\"Missing parameters\");\n if (params.center) figma.viewport.center = { x: params.center.x, y: params.center.y };\n if (params.zoom !== undefined) figma.viewport.zoom = params.zoom;\n return { center: figma.viewport.center, zoom: figma.viewport.zoom, bounds: figma.viewport.bounds };\n}\n\nexport const figmaHandlers: Record<string, (params: any) => Promise<any>> = {\n get_selection: getSelection,\n read_my_design: readMyDesign,\n set_selection: setSelection,\n // Legacy aliases for backward compat\n set_focus: async (params: any) => setSelection({ nodeIds: [params.nodeId] }),\n set_selections: setSelection,\n zoom_into_view: zoomIntoView,\n set_viewport: setViewport,\n};\n","import { z } from \"zod\";\n\n// AI agents (Claude, GPT, etc.) frequently pass numbers as strings\n// (\"10\" instead of 10), booleans as strings (\"true\" instead of true),\n// and objects/arrays as JSON strings. These helpers add resilient\n// coercion so tools don't fail on valid-but-mistyped input.\n\n/** Coerce \"true\"/\"false\"/\"1\"/\"0\" strings to boolean */\nexport const flexBool = <T extends z.ZodTypeAny>(inner: T) =>\n z.preprocess((v) => {\n if (v === \"true\" || v === \"1\") return true;\n if (v === \"false\" || v === \"0\") return false;\n return v;\n }, inner);\n\n/** Coerce JSON strings to parsed values (for objects/arrays that agents may stringify) */\nexport const flexJson = <T extends z.ZodTypeAny>(inner: T) =>\n z.preprocess((v) => {\n if (typeof v === \"string\") {\n try {\n return JSON.parse(v);\n } catch {\n return v;\n }\n }\n return v;\n }, inner);\n\n/** Coerce numeric strings only when they're valid numbers (safe for use inside unions) */\nexport const flexNum = <T extends z.ZodTypeAny>(inner: T) =>\n z.preprocess((v) => {\n if (typeof v === \"string\") {\n const n = Number(v);\n if (!isNaN(n) && v.trim() !== \"\") return n;\n }\n return v;\n }, inner);\n","import { z } from \"zod\";\nimport { flexJson, flexBool } from \"../utils/coercion\";\nimport { serializeNode } from \"../utils/serialize-node\";\nimport type { McpServer, SendCommandFn } from \"./types\";\nimport { mcpJson, mcpError } from \"./types\";\n\n// ─── MCP Registration ────────────────────────────────────────────\n\nexport function registerMcpTools(server: McpServer, sendCommand: SendCommandFn) {\n server.tool(\n \"get_node_info\",\n \"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).\",\n {\n nodeIds: flexJson(z.array(z.string())).describe('Array of node IDs. Example: [\"1:2\",\"1:3\"]'),\n depth: z.coerce.number().optional().describe(\"Child recursion depth (default: unlimited). 0=stubs only.\"),\n fields: flexJson(z.array(z.string())).optional().describe('Whitelist of property names to include. Always includes id, name, type. Example: [\"absoluteBoundingBox\",\"layoutMode\",\"fills\"]. Omit to return all properties.'),\n },\n async (params: any) => {\n try {\n const result = await sendCommand(\"get_node_info\", params);\n return mcpJson(result);\n } catch (e) { return mcpError(\"Error getting node info\", e); }\n }\n );\n\n server.tool(\n \"get_node_css\",\n \"Get CSS properties for a node (useful for dev handoff)\",\n { nodeId: z.string().describe(\"The node ID to get CSS for\") },\n async ({ nodeId }: any) => {\n try { return mcpJson(await sendCommand(\"get_node_css\", { nodeId })); }\n catch (e) { return mcpError(\"Error getting CSS\", e); }\n }\n );\n\n server.tool(\n \"search_nodes\",\n \"Search for nodes by layer name and/or type. Searches current page only — use set_current_page to switch pages first. Matches layer names (text nodes are often auto-named from their content). Returns paginated results.\",\n {\n query: z.string().optional().describe(\"Name search (case-insensitive substring). Omit to match all names.\"),\n types: flexJson(z.array(z.string())).optional().describe('Filter by types. Example: [\"FRAME\",\"TEXT\"]. Omit to match all types.'),\n scopeNodeId: z.string().optional().describe(\"Node ID to search within (defaults to current page)\"),\n caseSensitive: flexBool(z.boolean()).optional().describe(\"Case-sensitive name match (default false)\"),\n limit: z.coerce.number().optional().describe(\"Max results (default 50)\"),\n offset: z.coerce.number().optional().describe(\"Skip N results for pagination (default 0)\"),\n },\n async (params: any) => {\n try { return mcpJson(await sendCommand(\"search_nodes\", params)); }\n catch (e) { return mcpError(\"Error searching nodes\", e); }\n }\n );\n\n server.tool(\n \"export_node_as_image\",\n \"Export a node as an image from Figma\",\n {\n nodeId: z.string().describe(\"The node ID to export\"),\n format: z.enum([\"PNG\", \"JPG\", \"SVG\", \"PDF\"]).optional().describe(\"Export format (default: PNG)\"),\n scale: z.coerce.number().positive().optional().describe(\"Export scale (default: 1)\"),\n },\n async ({ nodeId, format, scale }: any) => {\n try {\n const result = await sendCommand(\"export_node_as_image\", { nodeId, format, scale }) as any;\n return {\n content: [{ type: \"image\", data: result.imageData, mimeType: result.mimeType || \"image/png\" }],\n };\n } catch (e) { return mcpError(\"Error exporting image\", e); }\n }\n );\n}\n\n// ─── Figma Handlers ──────────────────────────────────────────────\n\n/**\n * Recursively strip keys from a filtered node, keeping only `fields` + identity keys.\n * Stubs (objects with only id/name/type) are left untouched.\n */\nfunction pickFields(node: any, keep: Set<string>): any {\n if (!node || typeof node !== \"object\") return node;\n const out: any = {};\n for (const key of Object.keys(node)) {\n if (keep.has(key)) {\n out[key] = key === \"children\" && Array.isArray(node.children)\n ? node.children.map((c: any) => pickFields(c, keep))\n : node[key];\n }\n }\n return out;\n}\n\nasync function getNodeInfo(params: any) {\n const nodeIds: string[] = params.nodeIds || (params.nodeId ? [params.nodeId] : []);\n const depth = params.depth;\n const fields = params.fields;\n\n // Build fields whitelist (always include identity keys)\n const keep = fields?.length\n ? new Set<string>([...fields, \"id\", \"name\", \"type\", \"children\", \"parentId\", \"parentName\", \"parentType\"])\n : null;\n\n const results = await Promise.all(\n nodeIds.map(async (nodeId: string) => {\n const node = await figma.getNodeByIdAsync(nodeId);\n if (!node) return { nodeId, error: `Node not found: ${nodeId}` };\n\n let serialized = serializeNode(node, depth !== undefined ? depth : -1);\n\n if (keep && serialized) serialized = pickFields(serialized, keep);\n\n return serialized;\n })\n );\n\n return { results };\n}\n\nasync function getNodeCss(params: any) {\n if (!params?.nodeId) throw new Error(\"Missing nodeId\");\n const node = await figma.getNodeByIdAsync(params.nodeId);\n if (!node) throw new Error(`Node not found: ${params.nodeId}`);\n if (!(\"getCSSAsync\" in node)) throw new Error(\"Node does not support CSS export\");\n const css = await (node as any).getCSSAsync();\n return { id: node.id, name: node.name, css };\n}\n\nasync function searchNodes(params: any) {\n if (!params) throw new Error(\"Missing parameters\");\n\n let scopeNode: any;\n if (params.scopeNodeId) {\n scopeNode = await figma.getNodeByIdAsync(params.scopeNodeId);\n if (!scopeNode) throw new Error(`Scope node not found: ${params.scopeNodeId}`);\n } else {\n await figma.currentPage.loadAsync();\n scopeNode = figma.currentPage;\n }\n if (!(\"findAll\" in scopeNode)) throw new Error(\"Scope node does not support searching\");\n\n let results: any[];\n if (params.types && !params.query) {\n results = scopeNode.findAllWithCriteria({ types: params.types });\n } else {\n results = scopeNode.findAll((node: any) => {\n if (params.types?.length && !params.types.includes(node.type)) return false;\n if (params.query) {\n const q = params.query.toLowerCase();\n return params.caseSensitive ? node.name.includes(params.query) : node.name.toLowerCase().includes(q);\n }\n return true;\n });\n }\n\n const totalCount = results.length;\n const limit = params.limit || 50;\n const offset = params.offset || 0;\n results = results.slice(offset, offset + limit);\n\n return {\n totalCount,\n returned: results.length,\n offset,\n limit,\n results: results.map((node: any) => {\n const entry: any = { id: node.id, name: node.name, type: node.type };\n if (node.parent) { entry.parentId = node.parent.id; entry.parentName = node.parent.name; }\n if (\"absoluteBoundingBox\" in node && node.absoluteBoundingBox) {\n entry.bounds = node.absoluteBoundingBox;\n } else if (\"x\" in node) {\n entry.x = node.x; entry.y = node.y;\n if (\"width\" in node) { entry.width = node.width; entry.height = node.height; }\n }\n return entry;\n }),\n };\n}\n\nasync function exportNodeAsImage(params: any) {\n const { customBase64Encode } = await import(\"../utils/base64\");\n const { nodeId, scale = 1 } = params || {};\n const format = params.format || \"PNG\";\n if (!nodeId) throw new Error(\"Missing nodeId\");\n\n const node = await figma.getNodeByIdAsync(nodeId);\n if (!node) throw new Error(`Node not found: ${nodeId}`);\n if (!(\"exportAsync\" in node)) throw new Error(`Node does not support export: ${nodeId}`);\n\n const bytes = await (node as any).exportAsync({\n format,\n constraint: { type: \"SCALE\", value: scale },\n });\n\n const mimeMap: Record<string, string> = {\n PNG: \"image/png\", JPG: \"image/jpeg\", SVG: \"image/svg+xml\", PDF: \"application/pdf\",\n };\n\n return {\n nodeId, format, scale,\n mimeType: mimeMap[format] || \"application/octet-stream\",\n imageData: customBase64Encode(bytes),\n };\n}\n\nexport const figmaHandlers: Record<string, (params: any) => Promise<any>> = {\n get_node_info: getNodeInfo,\n // Legacy single-node alias\n get_nodes_info: async (params: any) => getNodeInfo({ nodeIds: params.nodeIds, depth: params.depth }),\n get_node_css: getNodeCss,\n search_nodes: searchNodes,\n export_node_as_image: exportNodeAsImage,\n};\n","import { z } from \"zod\";\nimport { flexJson } from \"../utils/coercion\";\nimport * as S from \"./schemas\";\nimport type { McpServer, SendCommandFn } from \"./types\";\nimport { mcpJson, mcpError } from \"./types\";\nimport { batchHandler, appendToParent } from \"./helpers\";\n\n// ─── Schemas ─────────────────────────────────────────────────────\n\nconst rectItem = z.object({\n name: z.string().optional().describe(\"Name (default: 'Rectangle')\"),\n x: S.xPos,\n y: S.yPos,\n width: z.coerce.number().optional().describe(\"Width (default: 100)\"),\n height: z.coerce.number().optional().describe(\"Height (default: 100)\"),\n parentId: S.parentId,\n});\n\nconst ellipseItem = z.object({\n name: z.string().optional().describe(\"Layer name (default: 'Ellipse')\"),\n x: S.xPos,\n y: S.yPos,\n width: z.coerce.number().optional().describe(\"Width (default: 100)\"),\n height: z.coerce.number().optional().describe(\"Height (default: 100)\"),\n parentId: S.parentId,\n});\n\nconst lineItem = z.object({\n name: z.string().optional().describe(\"Layer name (default: 'Line')\"),\n x: S.xPos,\n y: S.yPos,\n length: z.coerce.number().optional().describe(\"Length (default: 100)\"),\n rotation: z.coerce.number().optional().describe(\"Rotation in degrees (default: 0)\"),\n parentId: S.parentId,\n});\n\nconst sectionItem = z.object({\n name: z.string().optional().describe(\"Name (default: 'Section')\"),\n x: S.xPos,\n y: S.yPos,\n width: z.coerce.number().optional().describe(\"Width (default: 500)\"),\n height: z.coerce.number().optional().describe(\"Height (default: 500)\"),\n parentId: S.parentId,\n});\n\nconst svgItem = z.object({\n svg: z.string().describe(\"SVG markup string\"),\n name: z.string().optional().describe(\"Layer name (default: 'SVG')\"),\n x: S.xPos,\n y: S.yPos,\n parentId: S.parentId,\n});\n\nconst boolOpItem = z.object({\n nodeIds: flexJson(z.array(z.string())).describe(\"Array of node IDs (min 2)\"),\n operation: z.enum([\"UNION\", \"INTERSECT\", \"SUBTRACT\", \"EXCLUDE\"]).describe(\"Boolean operation type\"),\n name: z.string().optional().describe(\"Name for the result. Omit to auto-generate.\"),\n});\n\n// ─── MCP Registration ────────────────────────────────────────────\n\nexport function registerMcpTools(server: McpServer, sendCommand: SendCommandFn) {\n server.tool(\n \"create_rectangle\",\n \"Create rectangles (leaf nodes — cannot have children). For containers/cards/panels, use create_frame instead. Batch: pass multiple items.\",\n { items: flexJson(z.array(rectItem)).describe(\"Array of rectangles to create\"), depth: S.depth },\n async (params: any) => {\n try { return mcpJson(await sendCommand(\"create_rectangle\", params)); }\n catch (e) { return mcpError(\"Error creating rectangles\", e); }\n }\n );\n\n server.tool(\n \"create_ellipse\",\n \"Create ellipses (leaf nodes — cannot have children). For circular containers, use create_frame with cornerRadius instead. Batch: pass multiple items.\",\n { items: flexJson(z.array(ellipseItem)).describe(\"Array of ellipses to create\"), depth: S.depth },\n async (params: any) => {\n try { return mcpJson(await sendCommand(\"create_ellipse\", params)); }\n catch (e) { return mcpError(\"Error creating ellipses\", e); }\n }\n );\n\n server.tool(\n \"create_line\",\n \"Create lines (leaf nodes — cannot have children). For dividers inside layouts, use create_frame with a thin height and fill color instead. Batch: pass multiple items.\",\n { items: flexJson(z.array(lineItem)).describe(\"Array of lines to create\"), depth: S.depth },\n async (params: any) => {\n try { return mcpJson(await sendCommand(\"create_line\", params)); }\n catch (e) { return mcpError(\"Error creating lines\", e); }\n }\n );\n\n server.tool(\n \"create_section\",\n \"Create section nodes to organize content on the canvas.\",\n { items: flexJson(z.array(sectionItem)).describe(\"Array of sections to create\"), depth: S.depth },\n async (params: any) => {\n try { return mcpJson(await sendCommand(\"create_section\", params)); }\n catch (e) { return mcpError(\"Error creating sections\", e); }\n }\n );\n\n server.tool(\n \"create_node_from_svg\",\n \"Create nodes from SVG strings.\",\n { items: flexJson(z.array(svgItem)).describe(\"Array of SVG items to create\"), depth: S.depth },\n async (params: any) => {\n try { return mcpJson(await sendCommand(\"create_node_from_svg\", params)); }\n catch (e) { return mcpError(\"Error creating SVG nodes\", e); }\n }\n );\n\n server.tool(\n \"create_boolean_operation\",\n \"Create a boolean operation (union, intersect, subtract, exclude) from multiple nodes.\",\n { items: flexJson(z.array(boolOpItem)).describe(\"Array of boolean operations to create\"), depth: S.depth },\n async (params: any) => {\n try { return mcpJson(await sendCommand(\"create_boolean_operation\", params)); }\n catch (e) { return mcpError(\"Error creating boolean operations\", e); }\n }\n );\n}\n\n// ─── Figma Handlers ──────────────────────────────────────────────\n\nasync function createSingleRect(p: any) {\n const rect = figma.createRectangle();\n rect.x = p.x ?? 0;\n rect.y = p.y ?? 0;\n rect.resize(p.width ?? 100, p.height ?? 100);\n rect.name = p.name || \"Rectangle\";\n await appendToParent(rect, p.parentId);\n return { id: rect.id };\n}\n\nasync function createSingleEllipse(p: any) {\n const el = figma.createEllipse();\n el.x = p.x ?? 0;\n el.y = p.y ?? 0;\n el.resize(p.width ?? 100, p.height ?? 100);\n if (p.name) el.name = p.name;\n await appendToParent(el, p.parentId);\n return { id: el.id };\n}\n\nasync function createSingleLine(p: any) {\n const line = figma.createLine();\n line.x = p.x ?? 0;\n line.y = p.y ?? 0;\n line.resize(p.length ?? 100, 0);\n if (p.rotation) line.rotation = p.rotation;\n if (p.name) line.name = p.name;\n line.strokes = [{ type: \"SOLID\", color: { r: 0, g: 0, b: 0 } }];\n await appendToParent(line, p.parentId);\n return { id: line.id };\n}\n\nasync function createSingleSection(p: any) {\n const section = figma.createSection();\n section.x = p.x ?? 0;\n section.y = p.y ?? 0;\n section.resizeWithoutConstraints(p.width ?? 500, p.height ?? 500);\n section.name = p.name || \"Section\";\n await appendToParent(section, p.parentId);\n return { id: section.id };\n}\n\nasync function createSingleSvg(p: any) {\n const node = figma.createNodeFromSvg(p.svg);\n node.x = p.x ?? 0;\n node.y = p.y ?? 0;\n if (p.name) node.name = p.name;\n await appendToParent(node, p.parentId);\n return { id: node.id, type: node.type };\n}\n\nasync function createSingleBoolOp(p: any) {\n if (!p.nodeIds?.length || p.nodeIds.length < 2) throw new Error(\"Need at least 2 nodes\");\n const nodes: SceneNode[] = [];\n for (const id of p.nodeIds) {\n const node = await figma.getNodeByIdAsync(id);\n if (!node) throw new Error(`Node not found: ${id}`);\n nodes.push(node as SceneNode);\n }\n const boolOp = figma.createBooleanOperation();\n boolOp.booleanOperation = p.operation;\n for (const node of nodes) boolOp.appendChild(node.clone());\n if (p.name) boolOp.name = p.name;\n return { id: boolOp.id };\n}\n\nexport const figmaHandlers: Record<string, (params: any) => Promise<any>> = {\n create_rectangle: (p) => batchHandler(p, createSingleRect),\n create_ellipse: (p) => batchHandler(p, createSingleEllipse),\n create_line: (p) => batchHandler(p, createSingleLine),\n create_section: (p) => batchHandler(p, createSingleSection),\n create_node_from_svg: (p) => batchHandler(p, createSingleSvg),\n create_boolean_operation: (p) => batchHandler(p, createSingleBoolOp),\n};\n","import { z } from \"zod\";\nimport { flexJson, flexBool } from \"../utils/coercion\";\n\n// ─── Shared Zod Schema Fragments ────────────────────────────────\n// Import as: import * as S from \"./schemas\";\n\n/** Single node ID */\nexport const nodeId = z.string().describe(\"Node ID\");\n\n/** Array of node IDs */\nexport const nodeIds = flexJson(z.array(z.string())).describe(\"Array of node IDs\");\n\n/** Optional parent reference for creation tools */\nexport const parentId = z.string().optional()\n .describe(\"Parent node ID. Omit to place on current page.\");\n\n/**\n * Response depth — controls how much node detail is returned after an operation.\n * Omit for minimal response (id + name only).\n * 0 = node with full properties, children as stubs.\n * N = recurse N levels of children with full properties.\n * -1 = unlimited recursion.\n */\nexport const depth = z.coerce.number().optional()\n .describe(\"Response detail: omit for id+name only. 0=properties + child stubs. N=recurse N levels. -1=unlimited.\");\n\n/** X position for creation tools */\nexport const xPos = z.coerce.number().optional().describe(\"X position (default: 0)\");\n\n/** Y position for creation tools */\nexport const yPos = z.coerce.number().optional().describe(\"Y position (default: 0)\");\n\n/** Parse hex color string (#RGB, #RRGGBB, #RRGGBBAA) to {r,g,b,a} 0-1 */\nfunction parseHex(hex: string): { r: number; g: number; b: number; a?: number } | null {\n const m = hex.match(/^#?([0-9a-f]{3,8})$/i);\n if (!m) return null;\n let h = m[1];\n if (h.length === 3) h = h[0]+h[0]+h[1]+h[1]+h[2]+h[2];\n if (h.length === 4) h = h[0]+h[0]+h[1]+h[1]+h[2]+h[2]+h[3]+h[3];\n if (h.length !== 6 && h.length !== 8) return null;\n const r = parseInt(h.slice(0, 2), 16) / 255;\n const g = parseInt(h.slice(2, 4), 16) / 255;\n const b = parseInt(h.slice(4, 6), 16) / 255;\n if (h.length === 8) return { r, g, b, a: parseInt(h.slice(6, 8), 16) / 255 };\n return { r, g, b };\n}\n\n/** RGBA color — accepts {r,g,b,a?} object (0-1) or hex string (#RGB, #RRGGBB, #RRGGBBAA) */\nexport const colorRgba = z.preprocess((v) => {\n if (typeof v === \"string\") return parseHex(v) ?? v;\n return v;\n}, z.object({\n r: z.coerce.number().min(0).max(1),\n g: z.coerce.number().min(0).max(1),\n b: z.coerce.number().min(0).max(1),\n a: z.coerce.number().min(0).max(1).optional(),\n}));\n\n/** Single effect entry — shared by set_effects and create_effect_style */\nexport const effectEntry = z.object({\n type: z.enum([\"DROP_SHADOW\", \"INNER_SHADOW\", \"LAYER_BLUR\", \"BACKGROUND_BLUR\"]),\n color: flexJson(colorRgba).optional(),\n offset: flexJson(z.object({ x: z.coerce.number(), y: z.coerce.number() })).optional(),\n radius: z.coerce.number(),\n spread: z.coerce.number().optional(),\n visible: flexBool(z.boolean()).optional(),\n blendMode: z.string().optional(),\n});\n","import { z } from \"zod\";\nimport { flexJson } from \"../utils/coercion\";\nimport * as S from \"./schemas\";\nimport type { McpServer, SendCommandFn } from \"./types\";\nimport { mcpJson, mcpError } from \"./types\";\nimport { batchHandler, appendToParent, solidPaint, styleNotFoundHint, suggestStyleForColor, findVariableById } from \"./helpers\";\n\n// ─── Schema ──────────────────────────────────────────────────────\n\nconst frameItem = z.object({\n name: z.string().optional().describe(\"Frame name (default: 'Frame')\"),\n x: S.xPos,\n y: S.yPos,\n width: z.coerce.number().optional().describe(\"Width (default: 100)\"),\n height: z.coerce.number().optional().describe(\"Height (default: 100)\"),\n parentId: S.parentId,\n fillColor: flexJson(S.colorRgba).optional().describe('Fill color. Hex \"#FF0000\" or {r,g,b,a?} 0-1. Default: no fill (empty fills array).'),\n strokeColor: flexJson(S.colorRgba).optional().describe('Stroke color. Hex \"#FF0000\" or {r,g,b,a?} 0-1. Default: none.'),\n strokeWeight: z.coerce.number().positive().optional().describe(\"Stroke weight (default: 1)\"),\n cornerRadius: z.coerce.number().min(0).optional().describe(\"Corner radius (default: 0)\"),\n layoutMode: z.enum([\"NONE\", \"HORIZONTAL\", \"VERTICAL\"]).optional().describe(\"Auto-layout direction (default: NONE)\"),\n layoutWrap: z.enum([\"NO_WRAP\", \"WRAP\"]).optional().describe(\"Wrap (default: NO_WRAP)\"),\n paddingTop: z.coerce.number().optional().describe(\"Top padding (default: 0)\"),\n paddingRight: z.coerce.number().optional().describe(\"Right padding (default: 0)\"),\n paddingBottom: z.coerce.number().optional().describe(\"Bottom padding (default: 0)\"),\n paddingLeft: z.coerce.number().optional().describe(\"Left padding (default: 0)\"),\n primaryAxisAlignItems: z.enum([\"MIN\", \"MAX\", \"CENTER\", \"SPACE_BETWEEN\"]).optional(),\n counterAxisAlignItems: z.enum([\"MIN\", \"MAX\", \"CENTER\", \"BASELINE\"]).optional(),\n layoutSizingHorizontal: z.enum([\"FIXED\", \"HUG\", \"FILL\"]).optional(),\n layoutSizingVertical: z.enum([\"FIXED\", \"HUG\", \"FILL\"]).optional(),\n itemSpacing: z.coerce.number().optional().describe(\"Spacing between children (default: 0)\"),\n // Style/variable references\n fillStyleName: z.string().optional().describe(\"Apply a fill paint style by name (case-insensitive). Omit to skip.\"),\n strokeStyleName: z.string().optional().describe(\"Apply a stroke paint style by name. Omit to skip.\"),\n fillVariableId: z.string().optional().describe(\"Bind a color variable to the fill. Creates a solid fill and binds the variable to fills/0/color.\"),\n strokeVariableId: z.string().optional().describe(\"Bind a color variable to the stroke. Creates a solid stroke and binds the variable to strokes/0/color.\"),\n});\n\nconst autoLayoutItem = z.object({\n nodeIds: flexJson(z.array(z.string())).describe(\"Array of node IDs to wrap\"),\n name: z.string().optional().describe(\"Frame name (default: 'Auto Layout')\"),\n layoutMode: z.enum([\"HORIZONTAL\", \"VERTICAL\"]).optional().describe(\"Direction (default: VERTICAL)\"),\n itemSpacing: z.coerce.number().optional().describe(\"Spacing between children (default: 0)\"),\n paddingTop: z.coerce.number().optional().describe(\"Top padding (default: 0)\"),\n paddingRight: z.coerce.number().optional().describe(\"Right padding (default: 0)\"),\n paddingBottom: z.coerce.number().optional().describe(\"Bottom padding (default: 0)\"),\n paddingLeft: z.coerce.number().optional().describe(\"Left padding (default: 0)\"),\n primaryAxisAlignItems: z.enum([\"MIN\", \"MAX\", \"CENTER\", \"SPACE_BETWEEN\"]).optional(),\n counterAxisAlignItems: z.enum([\"MIN\", \"MAX\", \"CENTER\", \"BASELINE\"]).optional(),\n layoutSizingHorizontal: z.enum([\"FIXED\", \"HUG\", \"FILL\"]).optional(),\n layoutSizingVertical: z.enum([\"FIXED\", \"HUG\", \"FILL\"]).optional(),\n layoutWrap: z.enum([\"NO_WRAP\", \"WRAP\"]).optional(),\n});\n\n// ─── MCP Registration ────────────────────────────────────────────\n\nexport function registerMcpTools(server: McpServer, sendCommand: SendCommandFn) {\n server.tool(\n \"create_frame\",\n \"Create frames in Figma. Supports batch. Prefer fillStyleName or fillVariableId over hardcoded fillColor for design token consistency.\",\n { items: flexJson(z.array(frameItem)).describe(\"Array of frames to create\"), depth: S.depth },\n async (params: any) => {\n try { return mcpJson(await sendCommand(\"create_frame\", params)); }\n catch (e) { return mcpError(\"Error creating frames\", e); }\n }\n );\n\n server.tool(\n \"create_auto_layout\",\n \"Wrap existing nodes in an auto-layout frame. One call replaces create_frame + update_frame + insert_child × N.\",\n { items: flexJson(z.array(autoLayoutItem)).describe(\"Array of auto-layout wraps to perform\"), depth: S.depth },\n async (params: any) => {\n try { return mcpJson(await sendCommand(\"create_auto_layout\", params)); }\n catch (e) { return mcpError(\"Error creating auto layout\", e); }\n }\n );\n}\n\n// ─── Figma Handlers ──────────────────────────────────────────────\n\nasync function resolvePaintStyle(name: string): Promise<{ id: string | null, available: string[] }> {\n const styles = await figma.getLocalPaintStylesAsync();\n const available = styles.map(s => s.name);\n const exact = styles.find(s => s.name === name);\n if (exact) return { id: exact.id, available };\n const fuzzy = styles.find(s => s.name.toLowerCase().includes(name.toLowerCase()));\n return { id: fuzzy?.id ?? null, available };\n}\n\nasync function createSingleFrame(p: any) {\n const {\n x = 0, y = 0, width = 100, height = 100, name = \"Frame\", parentId,\n fillColor, strokeColor, strokeWeight, cornerRadius,\n layoutMode = \"NONE\", layoutWrap = \"NO_WRAP\",\n paddingTop = 0, paddingRight = 0, paddingBottom = 0, paddingLeft = 0,\n primaryAxisAlignItems = \"MIN\", counterAxisAlignItems = \"MIN\",\n layoutSizingHorizontal = \"FIXED\", layoutSizingVertical = \"FIXED\",\n itemSpacing = 0,\n fillStyleName, strokeStyleName,\n fillVariableId, strokeVariableId,\n } = p;\n\n const frame = figma.createFrame();\n frame.x = x;\n frame.y = y;\n frame.resize(width, height);\n frame.name = name;\n frame.fills = []; // no fill by default\n if (cornerRadius !== undefined) frame.cornerRadius = cornerRadius;\n\n const deferH = parentId && layoutSizingHorizontal === \"FILL\";\n const deferV = parentId && layoutSizingVertical === \"FILL\";\n\n if (layoutMode !== \"NONE\") {\n frame.layoutMode = layoutMode;\n frame.layoutWrap = layoutWrap;\n frame.paddingTop = paddingTop;\n frame.paddingRight = paddingRight;\n frame.paddingBottom = paddingBottom;\n frame.paddingLeft = paddingLeft;\n frame.primaryAxisAlignItems = primaryAxisAlignItems;\n frame.counterAxisAlignItems = counterAxisAlignItems;\n frame.layoutSizingHorizontal = deferH ? \"FIXED\" : layoutSizingHorizontal;\n frame.layoutSizingVertical = deferV ? \"FIXED\" : layoutSizingVertical;\n frame.itemSpacing = itemSpacing;\n }\n\n // Fill: variableId > styleName > direct color\n const hints: string[] = [];\n let fillTokenized = false;\n if (fillVariableId) {\n const v = await findVariableById(fillVariableId);\n if (v) {\n frame.fills = [solidPaint(fillColor || { r: 0, g: 0, b: 0 })];\n const bound = figma.variables.setBoundVariableForPaint(frame.fills[0] as SolidPaint, \"color\", v);\n frame.fills = [bound];\n fillTokenized = true;\n } else {\n hints.push(`fillVariableId '${fillVariableId}' not found.`);\n }\n } else if (fillStyleName) {\n const { id: sid, available } = await resolvePaintStyle(fillStyleName);\n if (sid) {\n try { await (frame as any).setFillStyleIdAsync(sid); fillTokenized = true; }\n catch (e: any) { hints.push(`fillStyleName '${fillStyleName}' matched but failed to apply: ${e.message}`); }\n } else hints.push(styleNotFoundHint(\"fillStyleName\", fillStyleName, available));\n } else if (fillColor) {\n frame.fills = [solidPaint(fillColor)];\n const suggestion = await suggestStyleForColor(fillColor, \"fillStyleName\");\n if (suggestion) hints.push(suggestion);\n }\n\n // Stroke: variableId > styleName > direct color\n let strokeTokenized = false;\n if (strokeVariableId) {\n const v = await findVariableById(strokeVariableId);\n if (v) {\n frame.strokes = [solidPaint(strokeColor || { r: 0, g: 0, b: 0 })];\n const bound = figma.variables.setBoundVariableForPaint(frame.strokes[0] as SolidPaint, \"color\", v);\n frame.strokes = [bound];\n strokeTokenized = true;\n } else {\n hints.push(`strokeVariableId '${strokeVariableId}' not found.`);\n }\n } else if (strokeStyleName) {\n const { id: sid, available } = await resolvePaintStyle(strokeStyleName);\n if (sid) {\n try { await (frame as any).setStrokeStyleIdAsync(sid); strokeTokenized = true; }\n catch (e: any) { hints.push(`strokeStyleName '${strokeStyleName}' matched but failed to apply: ${e.message}`); }\n } else hints.push(styleNotFoundHint(\"strokeStyleName\", strokeStyleName, available));\n } else if (strokeColor) {\n frame.strokes = [solidPaint(strokeColor)];\n const suggestion = await suggestStyleForColor(strokeColor, \"strokeStyleName\");\n if (suggestion) hints.push(suggestion);\n }\n if (strokeWeight !== undefined) frame.strokeWeight = strokeWeight;\n\n // Append to parent or page (with deferred FILL sizing)\n const parent = await appendToParent(frame, parentId);\n if (parent) {\n if (deferH) { try { frame.layoutSizingHorizontal = \"FILL\"; } catch {} }\n if (deferV) { try { frame.layoutSizingVertical = \"FILL\"; } catch {} }\n }\n\n const result: any = { id: frame.id };\n if (hints.length > 0) result.warning = hints.join(\" \");\n return result;\n}\n\nasync function createSingleAutoLayout(p: any) {\n if (!p.nodeIds?.length) throw new Error(\"Missing nodeIds\");\n\n const nodes: SceneNode[] = [];\n for (const id of p.nodeIds) {\n const node = await figma.getNodeByIdAsync(id);\n if (!node) throw new Error(`Node not found: ${id}`);\n nodes.push(node as SceneNode);\n }\n\n const originalParent = nodes[0].parent || figma.currentPage;\n\n // Calculate bounding box\n let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n for (const n of nodes) {\n if (\"x\" in n && \"y\" in n && \"width\" in n && \"height\" in n) {\n const nx = (n as any).x, ny = (n as any).y, nw = (n as any).width, nh = (n as any).height;\n if (nx < minX) minX = nx;\n if (ny < minY) minY = ny;\n if (nx + nw > maxX) maxX = nx + nw;\n if (ny + nh > maxY) maxY = ny + nh;\n }\n }\n\n const frame = figma.createFrame();\n frame.name = p.name || \"Auto Layout\";\n frame.fills = [];\n if (minX !== Infinity) {\n frame.x = minX;\n frame.y = minY;\n frame.resize(maxX - minX, maxY - minY);\n }\n\n if (\"appendChild\" in originalParent) (originalParent as any).appendChild(frame);\n for (const node of nodes) frame.appendChild(node);\n\n frame.layoutMode = p.layoutMode || \"VERTICAL\";\n frame.itemSpacing = p.itemSpacing ?? 0;\n frame.paddingTop = p.paddingTop ?? 0;\n frame.paddingRight = p.paddingRight ?? 0;\n frame.paddingBottom = p.paddingBottom ?? 0;\n frame.paddingLeft = p.paddingLeft ?? 0;\n if (p.primaryAxisAlignItems) frame.primaryAxisAlignItems = p.primaryAxisAlignItems;\n if (p.counterAxisAlignItems) frame.counterAxisAlignItems = p.counterAxisAlignItems;\n frame.layoutSizingHorizontal = p.layoutSizingHorizontal || \"HUG\";\n frame.layoutSizingVertical = p.layoutSizingVertical || \"HUG\";\n if (p.layoutWrap) frame.layoutWrap = p.layoutWrap;\n\n return { id: frame.id };\n}\n\nexport const figmaHandlers: Record<string, (params: any) => Promise<any>> = {\n create_frame: (p) => batchHandler(p, createSingleFrame),\n create_auto_layout: (p) => batchHandler(p, createSingleAutoLayout),\n};\n","import { z } from \"zod\";\nimport { flexJson } from \"../utils/coercion\";\nimport * as S from \"./schemas\";\nimport type { McpServer, SendCommandFn } from \"./types\";\nimport { mcpJson, mcpError } from \"./types\";\nimport { batchHandler, appendToParent, styleNotFoundHint, suggestStyleForColor, suggestTextStyle, findVariableById } from \"./helpers\";\n\n// ─── Schema ──────────────────────────────────────────────────────\n\nconst textItem = z.object({\n text: z.string().describe(\"Text content\"),\n name: z.string().optional().describe(\"Layer name (default: text content)\"),\n x: S.xPos,\n y: S.yPos,\n fontSize: z.coerce.number().optional().describe(\"Font size (default: 14)\"),\n fontWeight: z.coerce.number().optional().describe(\"Font weight: 100-900 (default: 400)\"),\n fontColor: flexJson(S.colorRgba).optional().describe('Font color. Hex \"#000000\" or {r,g,b,a?} 0-1. Default: black.'),\n fontColorVariableId: z.string().optional().describe(\"Bind a color variable to the text fill instead of hardcoded fontColor.\"),\n fontColorStyleName: z.string().optional().describe(\"Apply a paint style to the text fill by name (case-insensitive). Overrides fontColor.\"),\n parentId: S.parentId,\n textStyleId: z.string().optional().describe(\"Text style ID to apply (overrides fontSize/fontWeight). Omit to skip.\"),\n textStyleName: z.string().optional().describe(\"Text style name (case-insensitive match). Omit to skip.\"),\n textAlignHorizontal: z.enum([\"LEFT\", \"CENTER\", \"RIGHT\", \"JUSTIFIED\"]).optional().describe(\"Horizontal text alignment (default: LEFT)\"),\n textAlignVertical: z.enum([\"TOP\", \"CENTER\", \"BOTTOM\"]).optional().describe(\"Vertical text alignment (default: TOP)\"),\n layoutSizingHorizontal: z.enum([\"FIXED\", \"HUG\", \"FILL\"]).optional().describe(\"Horizontal sizing. FILL auto-sets textAutoResize to HEIGHT.\"),\n layoutSizingVertical: z.enum([\"FIXED\", \"HUG\", \"FILL\"]).optional().describe(\"Vertical sizing (default: HUG)\"),\n textAutoResize: z.enum([\"NONE\", \"WIDTH_AND_HEIGHT\", \"HEIGHT\", \"TRUNCATE\"]).optional().describe(\"Text auto-resize behavior (default: WIDTH_AND_HEIGHT when FILL)\"),\n});\n\n// ─── MCP Registration ────────────────────────────────────────────\n\nexport function registerMcpTools(server: McpServer, sendCommand: SendCommandFn) {\n server.tool(\n \"create_text\",\n \"Create text nodes in Figma. Uses Inter font. Max 10 items per batch. Use textStyleName for typography and fontColorStyleName for fill color.\",\n { items: flexJson(z.array(textItem).max(10)).describe(\"Array of text nodes to create (max 10)\"), depth: S.depth },\n async (params: any) => {\n try { return mcpJson(await sendCommand(\"create_text\", params)); }\n catch (e) { return mcpError(\"Error creating text\", e); }\n }\n );\n}\n\n// ─── Figma Handlers ──────────────────────────────────────────────\n\nfunction getFontStyle(weight: number): string {\n const map: Record<number, string> = {\n 100: \"Thin\", 200: \"Extra Light\", 300: \"Light\", 400: \"Regular\",\n 500: \"Medium\", 600: \"Semi Bold\", 700: \"Bold\", 800: \"Extra Bold\", 900: \"Black\",\n };\n return map[weight] || \"Regular\";\n}\n\n/**\n * Batch create_text with font preloading.\n * Collects all unique fonts + text styles upfront, loads them in one Promise.all,\n * then creates nodes without per-item font loading overhead.\n */\nasync function createTextBatch(params: any): Promise<{ results: any[] }> {\n const items = params.items || [params];\n const depth = params.depth;\n\n // 1. Collect unique font keys needed (family::style format)\n const fontKeys = new Set<string>();\n for (const p of items) {\n const style = getFontStyle(p.fontWeight || 400);\n fontKeys.add(`Inter::${style}`);\n }\n\n // 2. Resolve text styles by name once (not per-item)\n const styleNames = new Set<string>();\n for (const p of items) {\n if (p.textStyleName && !p.textStyleId) styleNames.add(p.textStyleName);\n }\n let textStyles: any[] | null = null;\n if (styleNames.size > 0) {\n textStyles = await figma.getLocalTextStylesAsync();\n }\n\n // 2a. Resolve paint styles for fontColorStyleName once\n const hasFontColorStyle = items.some((p: any) => p.fontColorStyleName);\n let paintStyles: any[] | null = null;\n if (hasFontColorStyle) {\n paintStyles = await figma.getLocalPaintStylesAsync();\n }\n\n // 2b. Resolve text style IDs and collect their fonts for preloading\n const resolvedTextStyleMap = new Map<string, any>(); // textStyleId → style object\n for (const p of items) {\n let sid = p.textStyleId;\n if (!sid && p.textStyleName && textStyles) {\n const exact = textStyles.find((s: any) => s.name === p.textStyleName);\n if (exact) sid = exact.id;\n else {\n const fuzzy = textStyles.find((s: any) => s.name.toLowerCase().includes(p.textStyleName.toLowerCase()));\n if (fuzzy) sid = fuzzy.id;\n }\n }\n if (sid && !resolvedTextStyleMap.has(sid)) {\n const s = await figma.getStyleByIdAsync(sid);\n if (s?.type === \"TEXT\") {\n resolvedTextStyleMap.set(sid, s);\n const fn = (s as TextStyle).fontName;\n if (fn) fontKeys.add(`${fn.family}::${fn.style}`);\n }\n }\n }\n\n // 3. Preload all fonts in parallel (Inter styles + text style fonts)\n await Promise.all(\n [...fontKeys].map(key => {\n const [family, style] = key.split(\"::\");\n return figma.loadFontAsync({ family, style });\n })\n );\n\n // 4. Import setCharacters once\n const { setCharacters } = await import(\"../utils/figma-helpers\");\n\n // 5. Create nodes (font already loaded, no await per item for fonts)\n const results = [];\n for (const p of items) {\n try {\n const {\n x = 0, y = 0, text = \"Text\", fontSize = 14, fontWeight = 400,\n fontColor, fontColorVariableId, fontColorStyleName, name = \"\",\n parentId, textStyleId, textStyleName,\n textAlignHorizontal, textAlignVertical,\n layoutSizingHorizontal, layoutSizingVertical, textAutoResize,\n } = p;\n\n const textNode = figma.createText();\n textNode.x = x;\n textNode.y = y;\n textNode.name = name || text;\n\n const style = getFontStyle(fontWeight);\n textNode.fontName = { family: \"Inter\", style };\n textNode.fontSize = parseInt(String(fontSize));\n\n await setCharacters(textNode, text);\n\n // Text alignment\n if (textAlignHorizontal) textNode.textAlignHorizontal = textAlignHorizontal;\n if (textAlignVertical) textNode.textAlignVertical = textAlignVertical;\n\n // Font color: variableId > styleName > direct color > default black\n const hints: string[] = [];\n let colorTokenized = false;\n if (fontColorVariableId) {\n const v = await findVariableById(fontColorVariableId);\n if (v) {\n const fc = fontColor || { r: 0, g: 0, b: 0, a: 1 };\n textNode.fills = [{ type: \"SOLID\", color: { r: fc.r ?? 0, g: fc.g ?? 0, b: fc.b ?? 0 }, opacity: fc.a ?? 1 }];\n const bound = figma.variables.setBoundVariableForPaint(textNode.fills[0] as SolidPaint, \"color\", v);\n textNode.fills = [bound];\n colorTokenized = true;\n } else {\n hints.push(`fontColorVariableId '${fontColorVariableId}' not found.`);\n }\n } else if (fontColorStyleName && paintStyles) {\n const exact = paintStyles.find((s: any) => s.name === fontColorStyleName);\n const match = exact || paintStyles.find((s: any) => s.name.toLowerCase().includes(fontColorStyleName.toLowerCase()));\n if (match) {\n try {\n await (textNode as any).setFillStyleIdAsync(match.id);\n colorTokenized = true;\n } catch (e: any) {\n hints.push(`fontColorStyleName '${fontColorStyleName}' matched '${match.name}' but failed to apply: ${e.message}`);\n }\n } else {\n hints.push(styleNotFoundHint(\"fontColorStyleName\", fontColorStyleName, paintStyles!.map((s: any) => s.name)));\n }\n }\n if (!colorTokenized) {\n const fc = fontColor || { r: 0, g: 0, b: 0, a: 1 };\n textNode.fills = [{ type: \"SOLID\", color: { r: fc.r ?? 0, g: fc.g ?? 0, b: fc.b ?? 0 }, opacity: fc.a ?? 1 }];\n if (fontColor) {\n const suggestion = await suggestStyleForColor(fontColor, \"fontColorStyleName\");\n if (suggestion) hints.push(suggestion);\n }\n }\n\n // Text style: by name > by ID (fonts already preloaded in step 2b/3)\n let resolvedStyleId = textStyleId;\n if (!resolvedStyleId && textStyleName && textStyles) {\n const exact = textStyles.find((s: any) => s.name === textStyleName);\n if (exact) resolvedStyleId = exact.id;\n else {\n const fuzzy = textStyles.find((s: any) => s.name.toLowerCase().includes(textStyleName.toLowerCase()));\n if (fuzzy) resolvedStyleId = fuzzy.id;\n }\n }\n if (resolvedStyleId) {\n const cached = resolvedTextStyleMap.get(resolvedStyleId);\n if (cached) {\n try {\n await (textNode as any).setTextStyleIdAsync(cached.id);\n } catch (e: any) {\n hints.push(`textStyleName '${textStyleName || resolvedStyleId}' matched but failed to apply: ${e.message}`);\n }\n } else {\n // Name resolved to an ID but getStyleByIdAsync couldn't load it\n hints.push(`textStyleName '${textStyleName || resolvedStyleId}' matched style ID '${resolvedStyleId}' but the style could not be loaded. It may be from a remote library or deleted.`);\n }\n } else if (textStyleName) {\n hints.push(styleNotFoundHint(\"textStyleName\", textStyleName, textStyles!.map((s: any) => s.name)));\n } else {\n // No text style used — suggest matching or creating one\n hints.push(await suggestTextStyle(fontSize, fontWeight));\n }\n\n await appendToParent(textNode, parentId);\n\n if (textAutoResize) {\n textNode.textAutoResize = textAutoResize;\n } else if (layoutSizingHorizontal === \"FILL\" || layoutSizingHorizontal === \"FIXED\") {\n textNode.textAutoResize = \"HEIGHT\";\n }\n\n if (layoutSizingHorizontal) {\n try { textNode.layoutSizingHorizontal = layoutSizingHorizontal; } catch {}\n }\n if (layoutSizingVertical) {\n try { textNode.layoutSizingVertical = layoutSizingVertical; } catch {}\n }\n\n let result: any = { id: textNode.id };\n if (depth !== undefined) {\n const { nodeSnapshot } = await import(\"./helpers\");\n const snapshot = await nodeSnapshot(textNode.id, depth);\n if (snapshot) result = { ...result, ...snapshot };\n }\n if (hints.length > 0) result.warning = hints.join(\" \");\n results.push(result);\n } catch (e: any) {\n results.push({ error: e.message });\n }\n }\n return { results };\n}\n\nexport const figmaHandlers: Record<string, (params: any) => Promise<any>> = {\n create_text: createTextBatch,\n};\n","import { z } from \"zod\";\nimport { flexJson } from \"../utils/coercion\";\nimport * as S from \"./schemas\";\nimport type { McpServer, SendCommandFn } from \"./types\";\nimport { mcpJson, mcpError } from \"./types\";\nimport { batchHandler } from \"./helpers\";\n\n// ─── Schemas ─────────────────────────────────────────────────────\n\nconst moveItem = z.object({\n nodeId: S.nodeId,\n x: z.coerce.number().describe(\"New X\"),\n y: z.coerce.number().describe(\"New Y\"),\n});\nconst resizeItem = z.object({\n nodeId: S.nodeId,\n width: z.coerce.number().positive().describe(\"New width\"),\n height: z.coerce.number().positive().describe(\"New height\"),\n});\nconst deleteItem = z.object({\n nodeId: z.string().describe(\"Node ID to delete\"),\n});\nconst cloneItem = z.object({\n nodeId: z.string().describe(\"Node ID to clone\"),\n parentId: z.string().optional().describe(\"Parent for the clone (e.g. a page ID). Defaults to same parent as original.\"),\n x: z.coerce.number().optional().describe(\"New X for clone. Omit to keep original position.\"),\n y: z.coerce.number().optional().describe(\"New Y for clone. Omit to keep original position.\"),\n});\nconst insertItem = z.object({\n parentId: z.string().describe(\"Parent node ID\"),\n childId: z.string().describe(\"Child node ID to move\"),\n index: z.coerce.number().optional().describe(\"Index to insert at (0=first). Omit to append.\"),\n});\n\n// ─── MCP Registration ────────────────────────────────────────────\n\nexport function registerMcpTools(server: McpServer, sendCommand: SendCommandFn) {\n server.tool(\n \"move_node\",\n \"Move nodes to new positions. Batch: pass multiple items.\",\n { items: flexJson(z.array(moveItem)).describe(\"Array of {nodeId, x, y}\"), depth: S.depth },\n async (params: any) => {\n try { return mcpJson(await sendCommand(\"move_node\", params)); }\n catch (e) { return mcpError(\"Error moving nodes\", e); }\n }\n );\n\n server.tool(\n \"resize_node\",\n \"Resize nodes. Batch: pass multiple items.\",\n { items: flexJson(z.array(resizeItem)).describe(\"Array of {nodeId, width, height}\"), depth: S.depth },\n async (params: any) => {\n try { return mcpJson(await sendCommand(\"resize_node\", params)); }\n catch (e) { return mcpError(\"Error resizing nodes\", e); }\n }\n );\n\n server.tool(\n \"delete_node\",\n \"Delete nodes. Batch: pass multiple items.\",\n { items: flexJson(z.array(deleteItem)).describe(\"Array of {nodeId}\") },\n async (params: any) => {\n try { return mcpJson(await sendCommand(\"delete_node\", params)); }\n catch (e) { return mcpError(\"Error deleting nodes\", e); }\n }\n );\n\n server.tool(\n \"clone_node\",\n \"Clone nodes. Batch: pass multiple items.\",\n { items: flexJson(z.array(cloneItem)).describe(\"Array of {nodeId, x?, y?}\"), depth: S.depth },\n async (params: any) => {\n try { return mcpJson(await sendCommand(\"clone_node\", params)); }\n catch (e) { return mcpError(\"Error cloning nodes\", e); }\n }\n );\n\n server.tool(\n \"insert_child\",\n \"Move nodes into a parent at a specific index (reorder/reparent). Batch: pass multiple items.\",\n { items: flexJson(z.array(insertItem)).describe(\"Array of {parentId, childId, index?}\"), depth: S.depth },\n async (params: any) => {\n try { return mcpJson(await sendCommand(\"insert_child\", params)); }\n catch (e) { return mcpError(\"Error inserting children\", e); }\n }\n );\n}\n\n// ─── Figma Handlers ──────────────────────────────────────────────\n\nasync function moveSingle(p: any) {\n const node = await figma.getNodeByIdAsync(p.nodeId);\n if (!node) throw new Error(`Node not found: ${p.nodeId}`);\n if (!(\"x\" in node)) throw new Error(`Node does not support position: ${p.nodeId}`);\n (node as any).x = p.x;\n (node as any).y = p.y;\n return {};\n}\n\nasync function resizeSingle(p: any) {\n const node = await figma.getNodeByIdAsync(p.nodeId);\n if (!node) throw new Error(`Node not found: ${p.nodeId}`);\n if (\"resize\" in node) (node as any).resize(p.width, p.height);\n else if (\"resizeWithoutConstraints\" in node) (node as any).resizeWithoutConstraints(p.width, p.height);\n else throw new Error(`Node does not support resize: ${p.nodeId}`);\n return {};\n}\n\nasync function deleteSingle(p: any) {\n const node = await figma.getNodeByIdAsync(p.nodeId);\n if (!node) throw new Error(`Node not found: ${p.nodeId}`);\n node.remove();\n return {};\n}\n\nasync function cloneSingle(p: any) {\n const node = await figma.getNodeByIdAsync(p.nodeId);\n if (!node) throw new Error(`Node not found: ${p.nodeId}`);\n const clone = (node as any).clone();\n if (p.x !== undefined && \"x\" in clone) { clone.x = p.x; clone.y = p.y; }\n if (p.parentId) {\n const parent = await figma.getNodeByIdAsync(p.parentId);\n if (!parent || !(\"appendChild\" in parent)) throw new Error(`Invalid parent: ${p.parentId}`);\n (parent as any).appendChild(clone);\n } else if (node.parent) {\n (node.parent as any).appendChild(clone);\n } else {\n figma.currentPage.appendChild(clone);\n }\n return { id: clone.id };\n}\n\nasync function insertSingle(p: any) {\n const parent = await figma.getNodeByIdAsync(p.parentId);\n if (!parent) throw new Error(`Parent not found: ${p.parentId}`);\n if (!(\"insertChild\" in parent)) throw new Error(`Parent does not support children: ${p.parentId}. Only FRAME, COMPONENT, GROUP, SECTION, and PAGE nodes can have children.`);\n const child = await figma.getNodeByIdAsync(p.childId);\n if (!child) throw new Error(`Child not found: ${p.childId}`);\n if (p.index !== undefined) (parent as any).insertChild(p.index, child);\n else (parent as any).appendChild(child);\n return {};\n}\n\nexport const figmaHandlers: Record<string, (params: any) => Promise<any>> = {\n move_node: (p) => batchHandler(p, moveSingle),\n resize_node: (p) => batchHandler(p, resizeSingle),\n delete_node: (p) => batchHandler(p, deleteSingle),\n // Legacy alias\n delete_multiple_nodes: async (p) => batchHandler({ items: (p.nodeIds || []).map((id: string) => ({ nodeId: id })) }, deleteSingle),\n clone_node: (p) => batchHandler(p, cloneSingle),\n insert_child: (p) => batchHandler(p, insertSingle),\n};\n","import { z } from \"zod\";\nimport { flexJson, flexBool } from \"../utils/coercion\";\nimport * as S from \"./schemas\";\nimport type { McpServer, SendCommandFn } from \"./types\";\nimport { mcpJson, mcpError } from \"./types\";\nimport { batchHandler, styleNotFoundHint, suggestStyleForColor } from \"./helpers\";\n\n// ─── Schemas ─────────────────────────────────────────────────────\n\nconst fillItem = z.object({\n nodeId: S.nodeId,\n color: flexJson(S.colorRgba).optional().describe('Fill color. Hex \"#FF0000\" or {r,g,b,a?} 0-1. Ignored when styleName is set.'),\n styleName: z.string().optional().describe(\"Apply fill paint style by name instead of color. Omit to use color.\"),\n});\n\nconst strokeItem = z.object({\n nodeId: S.nodeId,\n color: flexJson(S.colorRgba).optional().describe('Stroke color. Hex \"#FF0000\" or {r,g,b,a?} 0-1. Ignored when styleName is set.'),\n strokeWeight: z.coerce.number().positive().optional().describe(\"Stroke weight (default: 1)\"),\n styleName: z.string().optional().describe(\"Apply stroke paint style by name instead of color. Omit to use color.\"),\n});\n\nconst cornerItem = z.object({\n nodeId: S.nodeId,\n radius: z.coerce.number().min(0).describe(\"Corner radius\"),\n corners: flexJson(z.array(flexBool(z.boolean())).length(4)).optional()\n .describe(\"Which corners to round [topLeft, topRight, bottomRight, bottomLeft]. Default: all corners [true,true,true,true].\"),\n});\n\nconst opacityItem = z.object({\n nodeId: S.nodeId,\n opacity: z.coerce.number().min(0).max(1).describe(\"Opacity (0-1)\"),\n});\n\n// ─── MCP Registration ────────────────────────────────────────────\n\nexport function registerMcpTools(server: McpServer, sendCommand: SendCommandFn) {\n server.tool(\n \"set_fill_color\",\n \"Set fill color on nodes. Use styleName to apply a paint style by name, or provide color directly. Batch: pass multiple items.\",\n { items: flexJson(z.array(fillItem)).describe(\"Array of {nodeId, color?, styleName?}\"), depth: S.depth },\n async (params: any) => {\n try { return mcpJson(await sendCommand(\"set_fill_color\", params)); }\n catch (e) { return mcpError(\"Error setting fill\", e); }\n }\n );\n\n server.tool(\n \"set_stroke_color\",\n \"Set stroke color on nodes. Use styleName to apply a paint style by name. Batch: pass multiple items.\",\n { items: flexJson(z.array(strokeItem)).describe(\"Array of {nodeId, color?, strokeWeight?, styleName?}\"), depth: S.depth },\n async (params: any) => {\n try { return mcpJson(await sendCommand(\"set_stroke_color\", params)); }\n catch (e) { return mcpError(\"Error setting stroke\", e); }\n }\n );\n\n server.tool(\n \"set_corner_radius\",\n \"Set corner radius on nodes. Batch: pass multiple items.\",\n { items: flexJson(z.array(cornerItem)).describe(\"Array of {nodeId, radius, corners?}\"), depth: S.depth },\n async (params: any) => {\n try { return mcpJson(await sendCommand(\"set_corner_radius\", params)); }\n catch (e) { return mcpError(\"Error setting corner radius\", e); }\n }\n );\n\n server.tool(\n \"set_opacity\",\n \"Set opacity on nodes. Batch: pass multiple items.\",\n { items: flexJson(z.array(opacityItem)).describe(\"Array of {nodeId, opacity}\"), depth: S.depth },\n async (params: any) => {\n try { return mcpJson(await sendCommand(\"set_opacity\", params)); }\n catch (e) { return mcpError(\"Error setting opacity\", e); }\n }\n );\n}\n\n// ─── Figma Handlers ──────────────────────────────────────────────\n\nasync function resolveStyle(name: string): Promise<{ match: { id: string; name: string } | null, available: string[] }> {\n const styles = await figma.getLocalPaintStylesAsync();\n const available = styles.map(s => s.name);\n const exact = styles.find(s => s.name === name);\n if (exact) return { match: { id: exact.id, name: exact.name }, available };\n const fuzzy = styles.find(s => s.name.toLowerCase().includes(name.toLowerCase()));\n if (fuzzy) return { match: { id: fuzzy.id, name: fuzzy.name }, available };\n return { match: null, available };\n}\n\nasync function setFillSingle(p: any) {\n const node = await figma.getNodeByIdAsync(p.nodeId);\n if (!node) throw new Error(`Node not found: ${p.nodeId}`);\n if (!(\"fills\" in node)) throw new Error(`Node does not support fills: ${p.nodeId}`);\n\n if (p.styleName) {\n const { match, available } = await resolveStyle(p.styleName);\n if (match) {\n await (node as any).setFillStyleIdAsync(match.id);\n const result: any = { matchedStyle: match.name };\n if (p.color) result.warning = \"Both styleName and color provided — used styleName, ignored color. Pass only one.\";\n return result;\n }\n throw new Error(styleNotFoundHint(\"styleName\", p.styleName, available));\n } else if (p.color) {\n const { r = 0, g = 0, b = 0, a = 1 } = p.color;\n (node as any).fills = [{ type: \"SOLID\", color: { r, g, b }, opacity: a }];\n const suggestion = await suggestStyleForColor(p.color, \"styleName\");\n if (suggestion) return { warning: suggestion };\n }\n return {};\n}\n\nasync function setStrokeSingle(p: any) {\n const node = await figma.getNodeByIdAsync(p.nodeId);\n if (!node) throw new Error(`Node not found: ${p.nodeId}`);\n if (!(\"strokes\" in node)) throw new Error(`Node does not support strokes: ${p.nodeId}`);\n\n if (p.styleName) {\n const { match, available } = await resolveStyle(p.styleName);\n if (match) {\n await (node as any).setStrokeStyleIdAsync(match.id);\n const result: any = { matchedStyle: match.name };\n if (p.color) result.warning = \"Both styleName and color provided — used styleName, ignored color. Pass only one.\";\n if (p.strokeWeight !== undefined && \"strokeWeight\" in node) (node as any).strokeWeight = p.strokeWeight;\n return result;\n }\n throw new Error(styleNotFoundHint(\"styleName\", p.styleName, available));\n } else if (p.color) {\n const { r = 0, g = 0, b = 0, a = 1 } = p.color;\n (node as any).strokes = [{ type: \"SOLID\", color: { r, g, b }, opacity: a }];\n }\n if (p.strokeWeight !== undefined && \"strokeWeight\" in node) (node as any).strokeWeight = p.strokeWeight;\n const result: any = {};\n if (p.color) {\n const suggestion = await suggestStyleForColor(p.color, \"styleName\");\n if (suggestion) result.warning = suggestion;\n }\n return result;\n}\n\nasync function setCornerSingle(p: any) {\n const node = await figma.getNodeByIdAsync(p.nodeId);\n if (!node) throw new Error(`Node not found: ${p.nodeId}`);\n if (!(\"cornerRadius\" in node)) throw new Error(`Node does not support corner radius: ${p.nodeId}`);\n\n const corners = p.corners || [true, true, true, true];\n if (\"topLeftRadius\" in node && Array.isArray(corners) && corners.length === 4) {\n if (corners[0]) (node as any).topLeftRadius = p.radius;\n if (corners[1]) (node as any).topRightRadius = p.radius;\n if (corners[2]) (node as any).bottomRightRadius = p.radius;\n if (corners[3]) (node as any).bottomLeftRadius = p.radius;\n } else {\n (node as any).cornerRadius = p.radius;\n }\n return {};\n}\n\nasync function setOpacitySingle(p: any) {\n const node = await figma.getNodeByIdAsync(p.nodeId);\n if (!node) throw new Error(`Node not found: ${p.nodeId}`);\n if (!(\"opacity\" in node)) throw new Error(`Node does not support opacity`);\n (node as any).opacity = p.opacity;\n return {};\n}\n\nexport const figmaHandlers: Record<string, (params: any) => Promise<any>> = {\n set_fill_color: (p) => batchHandler(p, setFillSingle),\n set_stroke_color: (p) => batchHandler(p, setStrokeSingle),\n set_corner_radius: (p) => batchHandler(p, setCornerSingle),\n set_opacity: (p) => batchHandler(p, setOpacitySingle),\n};\n","import { z } from \"zod\";\nimport { flexJson } from \"../utils/coercion\";\nimport * as S from \"./schemas\";\nimport type { McpServer, SendCommandFn } from \"./types\";\nimport { mcpJson, mcpError } from \"./types\";\nimport { batchHandler } from \"./helpers\";\n\n// ─── Schema ──────────────────────────────────────────────────────\n\nconst updateFrameItem = z.object({\n nodeId: S.nodeId,\n layoutMode: z.enum([\"NONE\", \"HORIZONTAL\", \"VERTICAL\"]).optional().describe(\"Auto-layout direction\"),\n layoutWrap: z.enum([\"NO_WRAP\", \"WRAP\"]).optional().describe(\"Wrap (default: NO_WRAP)\"),\n paddingTop: z.coerce.number().optional().describe(\"Top padding\"),\n paddingRight: z.coerce.number().optional().describe(\"Right padding\"),\n paddingBottom: z.coerce.number().optional().describe(\"Bottom padding\"),\n paddingLeft: z.coerce.number().optional().describe(\"Left padding\"),\n primaryAxisAlignItems: z.enum([\"MIN\", \"MAX\", \"CENTER\", \"SPACE_BETWEEN\"]).optional().describe(\"Primary axis alignment\"),\n counterAxisAlignItems: z.enum([\"MIN\", \"MAX\", \"CENTER\", \"BASELINE\"]).optional().describe(\"Counter axis alignment\"),\n layoutSizingHorizontal: z.enum([\"FIXED\", \"HUG\", \"FILL\"]).optional().describe(\"Horizontal sizing (works on any node in auto-layout)\"),\n layoutSizingVertical: z.enum([\"FIXED\", \"HUG\", \"FILL\"]).optional().describe(\"Vertical sizing (works on any node in auto-layout)\"),\n itemSpacing: z.coerce.number().optional().describe(\"Spacing between children\"),\n counterAxisSpacing: z.coerce.number().optional().describe(\"Spacing between wrapped rows/columns (WRAP only)\"),\n});\n\n// ─── MCP Registration ────────────────────────────────────────────\n\nexport function registerMcpTools(server: McpServer, sendCommand: SendCommandFn) {\n server.tool(\n \"update_frame\",\n \"Update layout properties on frames. Combines layout mode, padding, alignment, sizing, and spacing in one call. Batch: pass multiple items.\",\n { items: flexJson(z.array(updateFrameItem)).describe(\"Array of {nodeId, ...layout properties}\"), depth: S.depth },\n async (params: any) => {\n try { return mcpJson(await sendCommand(\"update_frame\", params)); }\n catch (e) { return mcpError(\"Error updating frame\", e); }\n }\n );\n}\n\n// ─── Figma Handlers ──────────────────────────────────────────────\n\nconst LAYOUT_TYPES = [\"FRAME\", \"COMPONENT\", \"COMPONENT_SET\", \"INSTANCE\"];\n\nasync function updateFrameSingle(p: any) {\n const node = await figma.getNodeByIdAsync(p.nodeId);\n if (!node) throw new Error(`Node not found: ${p.nodeId}`);\n\n const isLayoutType = LAYOUT_TYPES.includes(node.type);\n const settingLayoutMode = p.layoutMode !== undefined;\n // Auto-layout is active if already set OR being set in this call\n const hasAutoLayout = settingLayoutMode\n ? p.layoutMode !== \"NONE\"\n : (isLayoutType && (node as any).layoutMode !== \"NONE\");\n\n // 1. Layout mode & wrap\n if (settingLayoutMode) {\n if (!isLayoutType) throw new Error(`Node type ${node.type} does not support layoutMode`);\n (node as any).layoutMode = p.layoutMode;\n if (p.layoutMode !== \"NONE\" && p.layoutWrap) (node as any).layoutWrap = p.layoutWrap;\n } else if (p.layoutWrap !== undefined) {\n if (!isLayoutType) throw new Error(`Node type ${node.type} does not support layoutWrap`);\n if (!hasAutoLayout) throw new Error(\"layoutWrap requires auto-layout (layoutMode !== NONE)\");\n (node as any).layoutWrap = p.layoutWrap;\n }\n\n // 2. Padding\n const hasPadding = p.paddingTop !== undefined || p.paddingRight !== undefined ||\n p.paddingBottom !== undefined || p.paddingLeft !== undefined;\n if (hasPadding) {\n if (!isLayoutType) throw new Error(`Node type ${node.type} does not support padding`);\n if (!hasAutoLayout) throw new Error(\"Padding requires auto-layout (layoutMode !== NONE)\");\n if (p.paddingTop !== undefined) (node as any).paddingTop = p.paddingTop;\n if (p.paddingRight !== undefined) (node as any).paddingRight = p.paddingRight;\n if (p.paddingBottom !== undefined) (node as any).paddingBottom = p.paddingBottom;\n if (p.paddingLeft !== undefined) (node as any).paddingLeft = p.paddingLeft;\n }\n\n // 3. Alignment\n if (p.primaryAxisAlignItems !== undefined || p.counterAxisAlignItems !== undefined) {\n if (!isLayoutType) throw new Error(`Node type ${node.type} does not support axis alignment`);\n if (!hasAutoLayout) throw new Error(\"Axis alignment requires auto-layout (layoutMode !== NONE)\");\n if (p.primaryAxisAlignItems !== undefined) (node as any).primaryAxisAlignItems = p.primaryAxisAlignItems;\n if (p.counterAxisAlignItems !== undefined) (node as any).counterAxisAlignItems = p.counterAxisAlignItems;\n }\n\n // 4. Sizing (no type check — works on any node in auto-layout)\n if (p.layoutSizingHorizontal !== undefined) (node as any).layoutSizingHorizontal = p.layoutSizingHorizontal;\n if (p.layoutSizingVertical !== undefined) (node as any).layoutSizingVertical = p.layoutSizingVertical;\n\n // 5. Spacing\n if (p.itemSpacing !== undefined) {\n if (!isLayoutType) throw new Error(`Node type ${node.type} does not support item spacing`);\n if (!hasAutoLayout) throw new Error(\"Item spacing requires auto-layout (layoutMode !== NONE)\");\n (node as any).itemSpacing = p.itemSpacing;\n }\n if (p.counterAxisSpacing !== undefined) {\n if (!isLayoutType) throw new Error(`Node type ${node.type} does not support counter-axis spacing`);\n if (!hasAutoLayout) throw new Error(\"Counter-axis spacing requires auto-layout\");\n const wrap = p.layoutWrap || (node as any).layoutWrap;\n if (wrap !== \"WRAP\") throw new Error(\"counterAxisSpacing requires layoutWrap=WRAP\");\n (node as any).counterAxisSpacing = p.counterAxisSpacing;\n }\n\n return {};\n}\n\nexport const figmaHandlers: Record<string, (params: any) => Promise<any>> = {\n update_frame: (p) => batchHandler(p, updateFrameSingle),\n};\n","import { z } from \"zod\";\nimport { flexJson, flexBool } from \"../utils/coercion\";\nimport * as S from \"./schemas\";\nimport type { McpServer, SendCommandFn } from \"./types\";\nimport { mcpJson, mcpError } from \"./types\";\nimport { batchHandler } from \"./helpers\";\n\n// ─── Schemas ─────────────────────────────────────────────────────\n\n// Uses S.effectEntry from shared schemas\n\nconst effectItem = z.object({\n nodeId: S.nodeId,\n effects: flexJson(z.array(S.effectEntry)).optional().describe(\"Array of effect objects. Ignored when effectStyleName is set.\"),\n effectStyleName: z.string().optional().describe(\"Apply an effect style by name (case-insensitive). Omit to use raw effects.\"),\n});\n\nconst constraintItem = z.object({\n nodeId: S.nodeId,\n horizontal: z.enum([\"MIN\", \"CENTER\", \"MAX\", \"STRETCH\", \"SCALE\"]),\n vertical: z.enum([\"MIN\", \"CENTER\", \"MAX\", \"STRETCH\", \"SCALE\"]),\n});\n\nconst exportSettingEntry = z.object({\n format: z.enum([\"PNG\", \"JPG\", \"SVG\", \"PDF\"]),\n suffix: z.string().optional(),\n contentsOnly: flexBool(z.boolean()).optional(),\n constraint: flexJson(z.object({\n type: z.enum([\"SCALE\", \"WIDTH\", \"HEIGHT\"]),\n value: z.coerce.number(),\n })).optional(),\n});\n\nconst exportSettingsItem = z.object({\n nodeId: S.nodeId,\n settings: flexJson(z.array(exportSettingEntry)).describe(\"Export settings array\"),\n});\n\nconst nodePropertiesItem = z.object({\n nodeId: S.nodeId,\n properties: flexJson(z.record(z.string(), z.unknown())).describe(\"Key-value properties to set\"),\n});\n\n// ─── MCP Registration ────────────────────────────────────────────\n\nexport function registerMcpTools(server: McpServer, sendCommand: SendCommandFn) {\n server.tool(\n \"set_effects\",\n \"Set effects (shadows, blurs) on nodes. Use effectStyleName to apply by name, or provide raw effects. Batch: pass multiple items.\",\n { items: flexJson(z.array(effectItem)).describe(\"Array of {nodeId, effects}\"), depth: S.depth },\n async (params: any) => {\n try { return mcpJson(await sendCommand(\"set_effects\", params)); }\n catch (e) { return mcpError(\"Error setting effects\", e); }\n }\n );\n\n server.tool(\n \"set_constraints\",\n \"Set constraints on nodes. Batch: pass multiple items.\",\n { items: flexJson(z.array(constraintItem)).describe(\"Array of {nodeId, horizontal, vertical}\"), depth: S.depth },\n async (params: any) => {\n try { return mcpJson(await sendCommand(\"set_constraints\", params)); }\n catch (e) { return mcpError(\"Error setting constraints\", e); }\n }\n );\n\n server.tool(\n \"set_export_settings\",\n \"Set export settings on nodes. Batch: pass multiple items.\",\n { items: flexJson(z.array(exportSettingsItem)).describe(\"Array of {nodeId, settings}\"), depth: S.depth },\n async (params: any) => {\n try { return mcpJson(await sendCommand(\"set_export_settings\", params)); }\n catch (e) { return mcpError(\"Error setting export settings\", e); }\n }\n );\n\n server.tool(\n \"set_node_properties\",\n \"Set arbitrary properties on nodes. Batch: pass multiple items.\",\n { items: flexJson(z.array(nodePropertiesItem)).describe(\"Array of {nodeId, properties}\"), depth: S.depth },\n async (params: any) => {\n try { return mcpJson(await sendCommand(\"set_node_properties\", params)); }\n catch (e) { return mcpError(\"Error setting node properties\", e); }\n }\n );\n}\n\n// ─── Figma Handlers ──────────────────────────────────────────────\n\nasync function setEffectsSingle(p: any) {\n const node = await figma.getNodeByIdAsync(p.nodeId);\n if (!node) throw new Error(`Node not found: ${p.nodeId}`);\n if (!(\"effects\" in node)) throw new Error(`Node does not support effects: ${p.nodeId}`);\n\n const result: any = {};\n if (p.effectStyleName) {\n const styles = await figma.getLocalEffectStylesAsync();\n const exact = styles.find(s => s.name === p.effectStyleName);\n const match = exact || styles.find(s => s.name.toLowerCase().includes(p.effectStyleName.toLowerCase()));\n if (!match) {\n const available = styles.map(s => s.name);\n const names = available.slice(0, 20);\n const suffix = available.length > 20 ? `, … and ${available.length - 20} more` : \"\";\n throw new Error(`effectStyleName '${p.effectStyleName}' not found. Available: [${names.join(\", \")}${suffix}]`);\n }\n await (node as any).setEffectStyleIdAsync(match.id);\n result.matchedStyle = match.name;\n if (p.effects) result.warning = \"Both effectStyleName and effects provided — used effectStyleName, ignored effects. Pass only one.\";\n } else if (p.effects) {\n const mapped = p.effects.map((e: any) => {\n const eff: any = { type: e.type, radius: e.radius, visible: e.visible ?? true };\n if (e.type === \"DROP_SHADOW\" || e.type === \"INNER_SHADOW\") eff.blendMode = e.blendMode || \"NORMAL\";\n if (e.color) eff.color = { r: e.color.r ?? 0, g: e.color.g ?? 0, b: e.color.b ?? 0, a: e.color.a ?? 1 };\n if (e.offset) eff.offset = { x: e.offset.x ?? 0, y: e.offset.y ?? 0 };\n if (e.spread !== undefined) eff.spread = e.spread;\n return eff;\n });\n (node as any).effects = mapped;\n }\n return result;\n}\n\nasync function setConstraintsSingle(p: any) {\n const node = await figma.getNodeByIdAsync(p.nodeId);\n if (!node) throw new Error(`Node not found: ${p.nodeId}`);\n if (!(\"constraints\" in node)) throw new Error(`Node does not support constraints: ${p.nodeId}`);\n (node as any).constraints = { horizontal: p.horizontal, vertical: p.vertical };\n return {};\n}\n\nasync function setExportSettingsSingle(p: any) {\n const node = await figma.getNodeByIdAsync(p.nodeId);\n if (!node) throw new Error(`Node not found: ${p.nodeId}`);\n if (!(\"exportSettings\" in node)) throw new Error(`Node does not support exportSettings: ${p.nodeId}`);\n (node as any).exportSettings = p.settings;\n return {};\n}\n\nasync function setNodePropertiesSingle(p: any) {\n const node = await figma.getNodeByIdAsync(p.nodeId);\n if (!node) throw new Error(`Node not found: ${p.nodeId}`);\n for (const [key, value] of Object.entries(p.properties)) {\n if (key in node) (node as any)[key] = value;\n }\n return {};\n}\n\nexport const figmaHandlers: Record<string, (params: any) => Promise<any>> = {\n set_effects: (p) => batchHandler(p, setEffectsSingle),\n set_constraints: (p) => batchHandler(p, setConstraintsSingle),\n set_export_settings: (p) => batchHandler(p, setExportSettingsSingle),\n set_node_properties: (p) => batchHandler(p, setNodePropertiesSingle),\n};\n","import { z } from \"zod\";\nimport { flexJson, flexBool } from \"../utils/coercion\";\nimport * as S from \"./schemas\";\nimport type { McpServer, SendCommandFn } from \"./types\";\nimport { mcpJson, mcpError } from \"./types\";\nimport { batchHandler, suggestStyleForColor, suggestTextStyle } from \"./helpers\";\n\n// ─── Schemas ─────────────────────────────────────────────────────\n\nconst textContentItem = z.object({\n nodeId: z.string().describe(\"Text node ID\"),\n text: z.string().describe(\"New text content\"),\n});\n\nconst textPropsItem = z.object({\n nodeId: z.string().describe(\"Text node ID\"),\n fontSize: z.coerce.number().optional().describe(\"Font size\"),\n fontWeight: z.coerce.number().optional().describe(\"Font weight: 100-900\"),\n fontColor: flexJson(S.colorRgba).optional().describe('Font color. Hex \"#000\" or {r,g,b,a?} 0-1.'),\n textStyleId: z.string().optional().describe(\"Text style ID to apply (overrides font props)\"),\n textStyleName: z.string().optional().describe(\"Text style name (case-insensitive match)\"),\n textAlignHorizontal: z.enum([\"LEFT\", \"CENTER\", \"RIGHT\", \"JUSTIFIED\"]).optional().describe(\"Horizontal text alignment\"),\n textAlignVertical: z.enum([\"TOP\", \"CENTER\", \"BOTTOM\"]).optional().describe(\"Vertical text alignment\"),\n textAutoResize: z.enum([\"NONE\", \"WIDTH_AND_HEIGHT\", \"HEIGHT\", \"TRUNCATE\"]).optional(),\n layoutSizingHorizontal: z.enum([\"FIXED\", \"HUG\", \"FILL\"]).optional(),\n layoutSizingVertical: z.enum([\"FIXED\", \"HUG\", \"FILL\"]).optional(),\n});\n\nconst scanTextItem = z.object({\n nodeId: S.nodeId,\n limit: z.coerce.number().optional().describe(\"Max text nodes to return (default: 50)\"),\n includePath: flexBool(z.boolean()).optional().describe(\"Include ancestor path strings (default: true). Set false to reduce payload.\"),\n includeGeometry: flexBool(z.boolean()).optional().describe(\"Include absoluteX/absoluteY/width/height (default: true). Set false to reduce payload.\"),\n});\n\n// ─── MCP Registration ────────────────────────────────────────────\n\nexport function registerMcpTools(server: McpServer, sendCommand: SendCommandFn) {\n server.tool(\n \"set_text_content\",\n \"Set text content on text nodes. Batch: pass multiple items to replace text in multiple nodes at once.\",\n { items: flexJson(z.array(textContentItem)).describe(\"Array of {nodeId, text}\"), depth: S.depth },\n async (params: any) => {\n try { return mcpJson(await sendCommand(\"set_text_content\", params)); }\n catch (e) { return mcpError(\"Error setting text content\", e); }\n }\n );\n\n server.tool(\n \"set_text_properties\",\n \"Set font properties on existing text nodes (fontSize, fontWeight, fontColor, textStyle). Batch: pass multiple items.\",\n { items: flexJson(z.array(textPropsItem)).describe(\"Array of {nodeId, fontSize?, fontWeight?, fontColor?, ...}\"), depth: S.depth },\n async (params: any) => {\n try { return mcpJson(await sendCommand(\"set_text_properties\", params)); }\n catch (e) { return mcpError(\"Error setting text properties\", e); }\n }\n );\n\n server.tool(\n \"scan_text_nodes\",\n \"Scan all text nodes within a node tree. Batch: pass multiple items.\",\n { items: flexJson(z.array(scanTextItem)).describe(\"Array of {nodeId}\") },\n async (params: any) => {\n try { return mcpJson(await sendCommand(\"scan_text_nodes\", params)); }\n catch (e) { return mcpError(\"Error scanning text nodes\", e); }\n }\n );\n}\n\n// ─── Figma Handlers ──────────────────────────────────────────────\n\n/**\n * Batch set_text_content with font preloading.\n * Resolves all nodes and preloads their fonts in one pass before writing text.\n */\nasync function setTextContentBatch(params: any): Promise<{ results: any[] }> {\n const items = params.items || [params];\n const depth = params.depth;\n\n // 1. Resolve all nodes first\n const resolved: { node: TextNode; text: string }[] = [];\n const errors: Map<number, string> = new Map();\n for (let i = 0; i < items.length; i++) {\n const p = items[i];\n const node = await figma.getNodeByIdAsync(p.nodeId);\n if (!node) { errors.set(i, `Node not found: ${p.nodeId}`); continue; }\n if (node.type !== \"TEXT\") { errors.set(i, `Node is not a text node: ${p.nodeId}`); continue; }\n resolved.push({ node: node as TextNode, text: p.text });\n }\n\n // 2. Collect unique fonts and preload in parallel\n const fontsToLoad = new Map<string, FontName>();\n const fallback: FontName = { family: \"Inter\", style: \"Regular\" };\n fontsToLoad.set(\"Inter::Regular\", fallback);\n for (const { node } of resolved) {\n const fn = node.fontName;\n if (fn !== figma.mixed && fn) {\n const key = `${(fn as FontName).family}::${(fn as FontName).style}`;\n fontsToLoad.set(key, fn as FontName);\n }\n }\n await Promise.all([...fontsToLoad.values()].map(f => figma.loadFontAsync(f)));\n\n // 3. Import setCharacters once\n const { setCharacters } = await import(\"../utils/figma-helpers\");\n\n // 4. Set text on all nodes\n const results: any[] = [];\n let resolvedIdx = 0;\n for (let i = 0; i < items.length; i++) {\n if (errors.has(i)) {\n results.push({ error: errors.get(i) });\n continue;\n }\n const { node, text } = resolved[resolvedIdx++];\n try {\n await setCharacters(node, text);\n let result: any = \"ok\";\n if (depth !== undefined) {\n const { nodeSnapshot } = await import(\"./helpers\");\n const snapshot = await nodeSnapshot(node.id, depth);\n if (snapshot) result = snapshot;\n }\n results.push(result);\n } catch (e: any) {\n results.push({ error: e.message });\n }\n }\n return { results };\n}\n\n/**\n * Batch set_text_properties with font preloading.\n */\nasync function setTextPropertiesBatch(params: any): Promise<{ results: any[] }> {\n const items = params.items || [params];\n const depth = params.depth;\n\n // 1. Resolve all nodes\n const resolved: { node: TextNode; props: any }[] = [];\n const errors: Map<number, string> = new Map();\n for (let i = 0; i < items.length; i++) {\n const p = items[i];\n const node = await figma.getNodeByIdAsync(p.nodeId);\n if (!node) { errors.set(i, `Node not found: ${p.nodeId}`); continue; }\n if (node.type !== \"TEXT\") { errors.set(i, `Not a text node: ${p.nodeId}`); continue; }\n resolved.push({ node: node as TextNode, props: p });\n }\n\n // 2. Collect fonts to load\n const fontsToLoad = new Map<string, FontName>();\n for (const { node, props } of resolved) {\n // Current font\n const fn = node.fontName;\n if (fn !== figma.mixed && fn) {\n fontsToLoad.set(`${fn.family}::${fn.style}`, fn);\n }\n // Target font if changing weight\n if (props.fontWeight !== undefined) {\n const style = getFontStyle(props.fontWeight);\n const family = (fn !== figma.mixed && fn) ? fn.family : \"Inter\";\n fontsToLoad.set(`${family}::${style}`, { family, style });\n }\n }\n await Promise.all([...fontsToLoad.values()].map(f => figma.loadFontAsync(f)));\n\n // 3. Resolve text styles by name once\n let textStyles: any[] | null = null;\n const styleNames = new Set<string>();\n for (const { props } of resolved) {\n if (props.textStyleName && !props.textStyleId) styleNames.add(props.textStyleName);\n }\n if (styleNames.size > 0) textStyles = await figma.getLocalTextStylesAsync();\n\n // 4. Apply properties\n const results: any[] = [];\n let resolvedIdx = 0;\n for (let i = 0; i < items.length; i++) {\n if (errors.has(i)) { results.push({ error: errors.get(i) }); continue; }\n const { node, props } = resolved[resolvedIdx++];\n try {\n // Text style takes priority\n let resolvedStyleId = props.textStyleId;\n if (!resolvedStyleId && props.textStyleName && textStyles) {\n const exact = textStyles.find((s: any) => s.name === props.textStyleName);\n if (exact) resolvedStyleId = exact.id;\n else {\n const fuzzy = textStyles.find((s: any) => s.name.toLowerCase().includes(props.textStyleName.toLowerCase()));\n if (fuzzy) resolvedStyleId = fuzzy.id;\n }\n }\n if (resolvedStyleId) {\n const s = await figma.getStyleByIdAsync(resolvedStyleId);\n if (s?.type === \"TEXT\") await (node as any).setTextStyleIdAsync(s.id);\n } else {\n if (props.fontWeight !== undefined) {\n const family = (node.fontName !== figma.mixed && node.fontName) ? node.fontName.family : \"Inter\";\n node.fontName = { family, style: getFontStyle(props.fontWeight) };\n }\n if (props.fontSize !== undefined) node.fontSize = props.fontSize;\n }\n\n if (props.fontColor) {\n node.fills = [{\n type: \"SOLID\",\n color: { r: props.fontColor.r ?? 0, g: props.fontColor.g ?? 0, b: props.fontColor.b ?? 0 },\n opacity: props.fontColor.a ?? 1,\n }];\n }\n\n if (props.textAlignHorizontal) node.textAlignHorizontal = props.textAlignHorizontal;\n if (props.textAlignVertical) node.textAlignVertical = props.textAlignVertical;\n if (props.textAutoResize) node.textAutoResize = props.textAutoResize;\n if (props.layoutSizingHorizontal) {\n try { node.layoutSizingHorizontal = props.layoutSizingHorizontal; } catch {}\n }\n if (props.layoutSizingVertical) {\n try { node.layoutSizingVertical = props.layoutSizingVertical; } catch {}\n }\n\n let result: any = \"ok\";\n if (depth !== undefined) {\n const { nodeSnapshot } = await import(\"./helpers\");\n const snapshot = await nodeSnapshot(node.id, depth);\n if (snapshot) result = snapshot;\n }\n\n // Warnings — only on actual conflicts or actionable suggestions\n const warnings: string[] = [];\n if (props.textStyleName && props.textStyleId) {\n warnings.push(\"Both textStyleName and textStyleId provided — used textStyleId. Pass only one.\");\n }\n if (!resolvedStyleId && !props.textStyleName && !props.textStyleId &&\n (props.fontSize !== undefined || props.fontWeight !== undefined)) {\n const fs = props.fontSize ?? (typeof node.fontSize === \"number\" ? node.fontSize : 14);\n const fw = props.fontWeight ?? 400;\n warnings.push(await suggestTextStyle(fs, fw));\n }\n if (props.fontColor) {\n const suggestion = await suggestStyleForColor(props.fontColor, \"fontColorStyleName\");\n if (suggestion) warnings.push(suggestion);\n }\n if (warnings.length > 0) {\n if (typeof result === \"string\") result = { status: result };\n result.warning = warnings.join(\" \");\n }\n results.push(result);\n } catch (e: any) {\n results.push({ error: e.message });\n }\n }\n return { results };\n}\n\nfunction getFontStyle(weight: number): string {\n const map: Record<number, string> = {\n 100: \"Thin\", 200: \"Extra Light\", 300: \"Light\", 400: \"Regular\",\n 500: \"Medium\", 600: \"Semi Bold\", 700: \"Bold\", 800: \"Extra Bold\", 900: \"Black\",\n };\n return map[weight] || \"Regular\";\n}\n\nasync function scanTextSingle(p: any) {\n const node = await figma.getNodeByIdAsync(p.nodeId);\n if (!node) throw new Error(`Node not found: ${p.nodeId}`);\n\n const limit = p.limit ?? 50;\n const opts = { includePath: p.includePath !== false, includeGeometry: p.includeGeometry !== false };\n const textNodes: any[] = [];\n await collectTextNodes(node, [], [], 0, textNodes, limit, opts);\n const truncated = textNodes.length >= limit;\n return { nodeId: p.nodeId, count: textNodes.length, truncated, textNodes };\n}\n\nasync function collectTextNodes(node: any, namePath: string[], idPath: string[], depth: number, out: any[], limit: number, opts: { includePath: boolean; includeGeometry: boolean }) {\n if (out.length >= limit) return;\n if (node.visible === false) return;\n const names = [...namePath, node.name || `Unnamed ${node.type}`];\n const ids = [...idPath, node.id];\n\n if (node.type === \"TEXT\") {\n let fontFamily = \"\", fontStyle = \"\";\n if (node.fontName && typeof node.fontName === \"object\") {\n if (\"family\" in node.fontName) fontFamily = node.fontName.family;\n if (\"style\" in node.fontName) fontStyle = node.fontName.style;\n }\n const entry: any = {\n id: node.id,\n name: node.name || \"Text\",\n characters: node.characters,\n fontSize: typeof node.fontSize === \"number\" ? node.fontSize : 0,\n fontFamily,\n fontStyle,\n };\n if (opts.includeGeometry) {\n const bounds = node.absoluteBoundingBox ?? node.absoluteRenderBounds;\n entry.absoluteX = bounds ? bounds.x : null;\n entry.absoluteY = bounds ? bounds.y : null;\n entry.width = bounds ? bounds.width : (node.width ?? 0);\n entry.height = bounds ? bounds.height : (node.height ?? 0);\n }\n if (opts.includePath) {\n entry.path = names.join(\" > \");\n entry.pathIds = ids.join(\" > \");\n entry.depth = depth;\n }\n out.push(entry);\n }\n\n if (\"children\" in node) {\n for (const child of (node as any).children) {\n if (out.length >= limit) break;\n await collectTextNodes(child, names, ids, depth + 1, out, limit, opts);\n }\n }\n}\n\n// Legacy handler: set_multiple_text_contents maps to set_text_content\nasync function setMultipleTextContentsFigma(params: any) {\n // Legacy format: { nodeId: \"parent\", text: [{ nodeId, text }] }\n const items = params.text || params.items || [];\n return setTextContentBatch({ items, depth: params.depth });\n}\n\nexport const figmaHandlers: Record<string, (params: any) => Promise<any>> = {\n set_text_content: setTextContentBatch,\n set_text_properties: setTextPropertiesBatch,\n scan_text_nodes: (p) => batchHandler(p, scanTextSingle),\n // Legacy alias\n set_multiple_text_contents: setMultipleTextContentsFigma,\n};\n","import { z } from \"zod\";\nimport type { McpServer, SendCommandFn } from \"./types\";\nimport { mcpJson, mcpError } from \"./types\";\n\n// ─── MCP Registration ────────────────────────────────────────────\n\nexport function registerMcpTools(server: McpServer, sendCommand: SendCommandFn) {\n server.tool(\n \"get_available_fonts\",\n \"Get available fonts in Figma. Optionally filter by query string.\",\n { query: z.string().optional().describe(\"Filter fonts by name (case-insensitive). Omit to list all fonts.\") },\n async ({ query }: any) => {\n try { return mcpJson(await sendCommand(\"get_available_fonts\", { query })); }\n catch (e) { return mcpError(\"Error getting fonts\", e); }\n }\n );\n}\n\n// ─── Figma Handlers ──────────────────────────────────────────────\n\nasync function getAvailableFonts(params: any) {\n const fonts = await figma.listAvailableFontsAsync();\n let result = fonts;\n if (params?.query) {\n const q = params.query.toLowerCase();\n result = fonts.filter((f: any) => f.fontName.family.toLowerCase().includes(q));\n }\n // Deduplicate by family name, list styles\n const familyMap: Record<string, string[]> = {};\n for (const f of result) {\n const fam = f.fontName.family;\n if (!familyMap[fam]) familyMap[fam] = [];\n familyMap[fam].push(f.fontName.style);\n }\n return {\n count: Object.keys(familyMap).length,\n fonts: Object.entries(familyMap).map(([family, styles]) => ({ family, styles })),\n };\n}\n\nexport const figmaHandlers: Record<string, (params: any) => Promise<any>> = {\n get_available_fonts: getAvailableFonts,\n};\n","import { z } from \"zod\";\nimport { flexJson, flexBool } from \"../utils/coercion\";\nimport * as S from \"./schemas\";\nimport type { McpServer, SendCommandFn } from \"./types\";\nimport { mcpJson, mcpError } from \"./types\";\nimport { batchHandler, appendToParent, solidPaint, styleNotFoundHint, suggestStyleForColor, findVariableById } from \"./helpers\";\n\n// ─── Schemas ─────────────────────────────────────────────────────\n\nconst componentItem = z.object({\n name: z.string().describe(\"Component name\"),\n x: S.xPos,\n y: S.yPos,\n width: z.coerce.number().optional().describe(\"Width (default: 100)\"),\n height: z.coerce.number().optional().describe(\"Height (default: 100)\"),\n parentId: S.parentId,\n fillColor: flexJson(S.colorRgba).optional().describe('Fill color. Hex \"#FF0000\" or {r,g,b,a?} 0-1. Omit for no fill.'),\n fillStyleName: z.string().optional().describe(\"Apply a fill paint style by name (case-insensitive).\"),\n fillVariableId: z.string().optional().describe(\"Bind a color variable to the fill.\"),\n strokeColor: flexJson(S.colorRgba).optional().describe('Stroke color. Hex \"#FF0000\" or {r,g,b,a?} 0-1. Omit for no stroke.'),\n strokeStyleName: z.string().optional().describe(\"Apply a stroke paint style by name.\"),\n strokeVariableId: z.string().optional().describe(\"Bind a color variable to the stroke.\"),\n strokeWeight: z.coerce.number().positive().optional().describe(\"Stroke weight (default: 1)\"),\n cornerRadius: z.coerce.number().optional().describe(\"Corner radius (default: 0)\"),\n layoutMode: z.enum([\"NONE\", \"HORIZONTAL\", \"VERTICAL\"]).optional().describe(\"Layout direction (default: NONE)\"),\n layoutWrap: z.enum([\"NO_WRAP\", \"WRAP\"]).optional().describe(\"Wrap behavior (default: NO_WRAP)\"),\n paddingTop: z.coerce.number().optional().describe(\"Top padding (default: 0)\"),\n paddingRight: z.coerce.number().optional().describe(\"Right padding (default: 0)\"),\n paddingBottom: z.coerce.number().optional().describe(\"Bottom padding (default: 0)\"),\n paddingLeft: z.coerce.number().optional().describe(\"Left padding (default: 0)\"),\n primaryAxisAlignItems: z.enum([\"MIN\", \"MAX\", \"CENTER\", \"SPACE_BETWEEN\"]).optional().describe(\"Primary axis alignment (default: MIN)\"),\n counterAxisAlignItems: z.enum([\"MIN\", \"MAX\", \"CENTER\", \"BASELINE\"]).optional().describe(\"Counter axis alignment (default: MIN)\"),\n layoutSizingHorizontal: z.enum([\"FIXED\", \"HUG\", \"FILL\"]).optional().describe(\"Horizontal sizing (default: FIXED)\"),\n layoutSizingVertical: z.enum([\"FIXED\", \"HUG\", \"FILL\"]).optional().describe(\"Vertical sizing (default: FIXED)\"),\n itemSpacing: z.coerce.number().optional().describe(\"Spacing between children (default: 0)\"),\n});\n\nconst fromNodeItem = z.object({\n nodeId: S.nodeId,\n});\n\nconst combineItem = z.object({\n componentIds: flexJson(z.array(z.string())).describe(\"Component IDs to combine (min 2)\"),\n name: z.string().optional().describe(\"Name for the component set. Omit to auto-generate.\"),\n});\n\nconst propItem = z.object({\n componentId: z.string().describe(\"Component node ID\"),\n propertyName: z.string().describe(\"Property name\"),\n type: z.enum([\"BOOLEAN\", \"TEXT\", \"INSTANCE_SWAP\", \"VARIANT\"]).describe(\"Property type\"),\n defaultValue: flexBool(z.union([z.string(), z.boolean()])).describe(\"Default value (string for TEXT/VARIANT, boolean for BOOLEAN)\"),\n preferredValues: flexJson(z.array(z.object({\n type: z.enum([\"COMPONENT\", \"COMPONENT_SET\"]),\n key: z.string(),\n })).optional()).describe(\"Preferred values for INSTANCE_SWAP type. Omit for none.\"),\n});\n\nconst instanceItem = z.object({\n componentId: z.string().describe(\"Component or component set ID\"),\n variantProperties: flexJson(z.record(z.string(), z.string())).optional().describe('Pick variant by properties, e.g. {\"Style\":\"Secondary\",\"Size\":\"Large\"}. Ignored for plain COMPONENT IDs.'),\n x: z.coerce.number().optional().describe(\"X position. Omit to keep default.\"),\n y: z.coerce.number().optional().describe(\"Y position. Omit to keep default.\"),\n parentId: S.parentId,\n});\n\n// ─── MCP Registration ────────────────────────────────────────────\n\nexport function registerMcpTools(server: McpServer, sendCommand: SendCommandFn) {\n server.tool(\n \"create_component\",\n \"Create components in Figma. Same layout params as create_frame. Name with 'Property=Value' pattern (e.g. 'Size=Small') if you plan to combine_as_variants later. Batch: pass multiple items.\",\n { items: flexJson(z.array(componentItem)).describe(\"Array of components to create\"), depth: S.depth },\n async (params: any) => {\n try { return mcpJson(await sendCommand(\"create_component\", params)); }\n catch (e) { return mcpError(\"Error creating component\", e); }\n }\n );\n\n server.tool(\n \"create_component_from_node\",\n \"Convert existing nodes into components. Batch: pass multiple items.\",\n { items: flexJson(z.array(fromNodeItem)).describe(\"Array of {nodeId}\"), depth: S.depth },\n async (params: any) => {\n try { return mcpJson(await sendCommand(\"create_component_from_node\", params)); }\n catch (e) { return mcpError(\"Error creating component from node\", e); }\n }\n );\n\n server.tool(\n \"combine_as_variants\",\n \"Combine components into variant sets. Name components with 'Property=Value' pattern (e.g. 'Style=Primary', 'Size=Large') BEFORE combining — Figma derives variant properties from component names. Avoid slashes in names. The resulting set is placed in the components' shared parent (or page root if parents differ). Batch: pass multiple items.\",\n { items: flexJson(z.array(combineItem)).describe(\"Array of {componentIds, name?}\"), depth: S.depth },\n async (params: any) => {\n try { return mcpJson(await sendCommand(\"combine_as_variants\", params)); }\n catch (e) { return mcpError(\"Error combining variants\", e); }\n }\n );\n\n server.tool(\n \"add_component_property\",\n \"Add properties to components. Batch: pass multiple items.\",\n { items: flexJson(z.array(propItem)).describe(\"Array of {componentId, propertyName, type, defaultValue, preferredValues?}\") },\n async (params: any) => {\n try { return mcpJson(await sendCommand(\"add_component_property\", params)); }\n catch (e) { return mcpError(\"Error adding component property\", e); }\n }\n );\n\n server.tool(\n \"create_instance_from_local\",\n \"Create instances of local components. For COMPONENT_SET, use variantProperties to pick a specific variant (e.g. {\\\"Style\\\":\\\"Secondary\\\"}). Batch: pass multiple items.\",\n { items: flexJson(z.array(instanceItem)).describe(\"Array of {componentId, x?, y?, parentId?}\") },\n async (params: any) => {\n try { return mcpJson(await sendCommand(\"create_instance_from_local\", params)); }\n catch (e) { return mcpError(\"Error creating instance\", e); }\n }\n );\n\n server.tool(\n \"search_components\",\n \"Search local components and component sets across all pages. Returns component id, name, and which page it lives on.\",\n {\n query: z.string().optional().describe(\"Filter by name (case-insensitive substring). Omit to list all.\"),\n setsOnly: flexBool(z.boolean()).optional().describe(\"If true, return only COMPONENT_SET nodes\"),\n limit: z.coerce.number().optional().describe(\"Max results (default 100)\"),\n offset: z.coerce.number().optional().describe(\"Skip N results (default 0)\"),\n },\n async (params: any) => {\n try { return mcpJson(await sendCommand(\"search_components\", params)); }\n catch (e) { return mcpError(\"Error searching components\", e); }\n }\n );\n\n server.tool(\n \"get_component_by_id\",\n \"Get detailed component info including property definitions and variants.\",\n {\n componentId: z.string().describe(\"Component node ID\"),\n includeChildren: flexBool(z.boolean()).optional().describe(\"For COMPONENT_SETs: include variant children (default false)\"),\n },\n async (params: any) => {\n try { return mcpJson(await sendCommand(\"get_component_by_id\", params)); }\n catch (e) { return mcpError(\"Error getting component\", e); }\n }\n );\n\n server.tool(\n \"get_instance_overrides\",\n \"Get override properties from a component instance.\",\n { nodeId: z.string().optional().describe(\"Instance node ID (uses selection if omitted)\") },\n async ({ nodeId }: any) => {\n try { return mcpJson(await sendCommand(\"get_instance_overrides\", { instanceNodeId: nodeId || null })); }\n catch (e) { return mcpError(\"Error getting overrides\", e); }\n }\n );\n}\n\n// ─── Figma Handlers ──────────────────────────────────────────────\n\nasync function resolvePaintStyle(name: string): Promise<{ id: string | null, available: string[] }> {\n const styles = await figma.getLocalPaintStylesAsync();\n const available = styles.map(s => s.name);\n const exact = styles.find(s => s.name === name);\n if (exact) return { id: exact.id, available };\n const fuzzy = styles.find(s => s.name.toLowerCase().includes(name.toLowerCase()));\n return { id: fuzzy?.id ?? null, available };\n}\n\nasync function bindFillVariable(node: any, variableId: string, fallbackColor?: any) {\n const v = await findVariableById(variableId);\n if (!v) return false;\n node.fills = [solidPaint(fallbackColor || { r: 0, g: 0, b: 0 })];\n const bound = figma.variables.setBoundVariableForPaint(node.fills[0], \"color\", v);\n node.fills = [bound];\n return true;\n}\n\nasync function bindStrokeVariable(node: any, variableId: string, fallbackColor?: any) {\n const v = await findVariableById(variableId);\n if (!v) return false;\n node.strokes = [solidPaint(fallbackColor || { r: 0, g: 0, b: 0 })];\n const bound = figma.variables.setBoundVariableForPaint(node.strokes[0], \"color\", v);\n node.strokes = [bound];\n return true;\n}\n\nasync function createComponentSingle(p: any) {\n if (!p.name) throw new Error(\"Missing name\");\n const {\n x = 0, y = 0, width = 100, height = 100, name, parentId,\n fillColor, fillStyleName, fillVariableId,\n strokeColor, strokeStyleName, strokeVariableId,\n strokeWeight, cornerRadius,\n layoutMode = \"NONE\", layoutWrap = \"NO_WRAP\",\n paddingTop = 0, paddingRight = 0, paddingBottom = 0, paddingLeft = 0,\n primaryAxisAlignItems = \"MIN\", counterAxisAlignItems = \"MIN\",\n layoutSizingHorizontal = \"FIXED\", layoutSizingVertical = \"FIXED\",\n itemSpacing = 0,\n } = p;\n\n const deferH = parentId && layoutSizingHorizontal === \"FILL\";\n const deferV = parentId && layoutSizingVertical === \"FILL\";\n\n const comp = figma.createComponent();\n comp.name = name;\n comp.x = x; comp.y = y;\n comp.resize(width, height);\n comp.fills = [];\n\n if (layoutMode !== \"NONE\") {\n comp.layoutMode = layoutMode;\n comp.layoutWrap = layoutWrap;\n comp.paddingTop = paddingTop; comp.paddingRight = paddingRight;\n comp.paddingBottom = paddingBottom; comp.paddingLeft = paddingLeft;\n comp.primaryAxisAlignItems = primaryAxisAlignItems;\n comp.counterAxisAlignItems = counterAxisAlignItems;\n comp.layoutSizingHorizontal = deferH ? \"FIXED\" : layoutSizingHorizontal;\n comp.layoutSizingVertical = deferV ? \"FIXED\" : layoutSizingVertical;\n comp.itemSpacing = itemSpacing;\n }\n\n // Fill: variableId > styleName > direct color\n const hints: string[] = [];\n if (fillVariableId) {\n const ok = await bindFillVariable(comp, fillVariableId, fillColor);\n if (!ok) hints.push(`fillVariableId '${fillVariableId}' not found.`);\n } else if (fillStyleName) {\n const { id: sid, available } = await resolvePaintStyle(fillStyleName);\n if (sid) {\n try { await (comp as any).setFillStyleIdAsync(sid); }\n catch (e: any) { hints.push(`fillStyleName '${fillStyleName}' matched but failed to apply: ${e.message}`); }\n } else hints.push(styleNotFoundHint(\"fillStyleName\", fillStyleName, available));\n } else if (fillColor) {\n comp.fills = [solidPaint(fillColor)];\n const suggestion = await suggestStyleForColor(fillColor, \"fillStyleName\");\n if (suggestion) hints.push(suggestion);\n }\n\n // Stroke: variableId > styleName > direct color\n if (strokeVariableId) {\n const ok = await bindStrokeVariable(comp, strokeVariableId, strokeColor);\n if (!ok) hints.push(`strokeVariableId '${strokeVariableId}' not found.`);\n } else if (strokeStyleName) {\n const { id: sid, available } = await resolvePaintStyle(strokeStyleName);\n if (sid) {\n try { await (comp as any).setStrokeStyleIdAsync(sid); }\n catch (e: any) { hints.push(`strokeStyleName '${strokeStyleName}' matched but failed to apply: ${e.message}`); }\n } else hints.push(styleNotFoundHint(\"strokeStyleName\", strokeStyleName, available));\n } else if (strokeColor) {\n comp.strokes = [solidPaint(strokeColor)];\n const suggestion = await suggestStyleForColor(strokeColor, \"strokeStyleName\");\n if (suggestion) hints.push(suggestion);\n }\n if (strokeWeight !== undefined) comp.strokeWeight = strokeWeight;\n if (cornerRadius !== undefined) comp.cornerRadius = cornerRadius;\n\n const parent = await appendToParent(comp, parentId);\n if (parent) {\n if (deferH) { try { comp.layoutSizingHorizontal = \"FILL\"; } catch {} }\n if (deferV) { try { comp.layoutSizingVertical = \"FILL\"; } catch {} }\n }\n\n const result: any = { id: comp.id };\n if (hints.length > 0) result.warning = hints.join(\" \");\n return result;\n}\n\nasync function fromNodeSingle(p: any) {\n const node = await figma.getNodeByIdAsync(p.nodeId);\n if (!node) throw new Error(`Node not found: ${p.nodeId}`);\n if (!(\"parent\" in node) || !node.parent) throw new Error(\"Node has no parent\");\n const parent = node.parent;\n const index = (parent as any).children.indexOf(node);\n const comp = figma.createComponent();\n comp.name = node.name;\n if (\"width\" in node && \"height\" in node) comp.resize((node as any).width, (node as any).height);\n if (\"x\" in node && \"y\" in node) { comp.x = (node as any).x; comp.y = (node as any).y; }\n const clone = (node as any).clone(); clone.x = 0; clone.y = 0;\n comp.appendChild(clone);\n (parent as any).insertChild(index, comp);\n node.remove();\n return { id: comp.id };\n}\n\nasync function combineSingle(p: any) {\n if (!p.componentIds?.length || p.componentIds.length < 2) throw new Error(\"Need at least 2 components\");\n const comps: ComponentNode[] = [];\n for (const id of p.componentIds) {\n const node = await figma.getNodeByIdAsync(id);\n if (!node) throw new Error(`Component not found: ${id}`);\n if (node.type !== \"COMPONENT\") throw new Error(`Node ${id} is not a COMPONENT`);\n comps.push(node as ComponentNode);\n }\n // Use common parent of components (falls back to currentPage)\n const parent = comps[0].parent && comps.every(c => c.parent === comps[0].parent)\n ? comps[0].parent : figma.currentPage;\n const set = figma.combineAsVariants(comps, parent as any);\n if (p.name) set.name = p.name;\n return { id: set.id };\n}\n\nasync function addPropSingle(p: any) {\n const node = await figma.getNodeByIdAsync(p.componentId);\n if (!node) throw new Error(`Node not found: ${p.componentId}`);\n if (node.type !== \"COMPONENT\" && node.type !== \"COMPONENT_SET\") throw new Error(`Node ${p.componentId} is a ${node.type}, not a COMPONENT or COMPONENT_SET. Property definitions can only be added to COMPONENT_SET nodes (or standalone COMPONENT nodes not inside a set).`);\n (node as any).addComponentProperty(p.propertyName, p.type, p.defaultValue);\n return {};\n}\n\nasync function instanceSingle(p: any) {\n let node: any = await figma.getNodeByIdAsync(p.componentId);\n if (!node) {\n // Component may be on another page — load all pages and retry\n await figma.loadAllPagesAsync();\n node = await figma.getNodeByIdAsync(p.componentId);\n }\n if (!node) throw new Error(`Component not found: ${p.componentId}`);\n if (node.type === \"COMPONENT_SET\") {\n if (!node.children?.length) throw new Error(\"Component set has no variants\");\n // Match variant by properties if provided\n if (p.variantProperties && typeof p.variantProperties === \"object\") {\n const match = node.children.find((child: any) => {\n if (child.type !== \"COMPONENT\" || !child.variantProperties) return false;\n return Object.entries(p.variantProperties).every(\n ([k, v]) => child.variantProperties[k] === v\n );\n });\n if (match) node = match;\n else throw new Error(`No variant matching ${JSON.stringify(p.variantProperties)} in ${node.name}`);\n } else {\n node = node.defaultVariant || node.children[0];\n }\n }\n if (node.type !== \"COMPONENT\") throw new Error(`Not a component: ${node.type}`);\n const inst = node.createInstance();\n if (p.x !== undefined) inst.x = p.x;\n if (p.y !== undefined) inst.y = p.y;\n await appendToParent(inst, p.parentId);\n return { id: inst.id };\n}\n\nasync function getLocalComponentsFigma(params: any) {\n await figma.loadAllPagesAsync();\n const setsOnly = params?.setsOnly;\n const types = setsOnly ? [\"COMPONENT_SET\"] : [\"COMPONENT\", \"COMPONENT_SET\"];\n let components = figma.root.findAllWithCriteria({ types: types as any });\n if (params?.query) {\n const f = params.query.toLowerCase();\n components = components.filter((c: any) => c.name.toLowerCase().includes(f));\n }\n const total = components.length;\n const limit = params?.limit || 100;\n const offset = params?.offset || 0;\n components = components.slice(offset, offset + limit);\n return {\n totalCount: total, returned: components.length, offset, limit,\n components: components.map((c: any) => {\n const e: any = { id: c.id, name: c.name, type: c.type };\n if (c.type === \"COMPONENT_SET\" && \"children\" in c) e.variantCount = c.children.length;\n if (c.description) e.description = c.description;\n // Walk up to find containing page\n let p = c.parent;\n while (p && p.type !== \"PAGE\") p = p.parent;\n if (p) { e.pageId = p.id; e.pageName = p.name; }\n return e;\n }),\n };\n}\n\nasync function getComponentByIdFigma(params: any) {\n const node = await figma.getNodeByIdAsync(params.componentId);\n if (!node) throw new Error(`Component not found: ${params.componentId}`);\n if (node.type !== \"COMPONENT\" && node.type !== \"COMPONENT_SET\") throw new Error(`Not a component: ${node.type}`);\n const r: any = { id: node.id, name: node.name, type: node.type };\n if (\"description\" in node) r.description = (node as any).description;\n if (node.parent) { r.parentId = node.parent.id; r.parentName = node.parent.name; }\n if (\"componentPropertyDefinitions\" in node) r.propertyDefinitions = (node as any).componentPropertyDefinitions;\n if (node.type === \"COMPONENT_SET\" && \"variantGroupProperties\" in node) r.variantGroupProperties = (node as any).variantGroupProperties;\n if (node.type === \"COMPONENT\" && \"variantProperties\" in node) r.variantProperties = (node as any).variantProperties;\n if (\"children\" in node && (node as any).children) {\n if (node.type === \"COMPONENT_SET\") {\n r.variantCount = (node as any).children.length;\n if (params.includeChildren) r.children = (node as any).children.map((c: any) => ({ id: c.id, name: c.name, type: c.type }));\n } else {\n r.children = (node as any).children.map((c: any) => ({ id: c.id, name: c.name, type: c.type }));\n }\n }\n return r;\n}\n\nasync function getInstanceOverridesFigma(params: any) {\n let inst: any = null;\n if (params?.instanceNodeId) {\n inst = await figma.getNodeByIdAsync(params.instanceNodeId);\n if (!inst) throw new Error(`Instance not found: ${params.instanceNodeId}`);\n if (inst.type !== \"INSTANCE\") throw new Error(\"Node is not an instance\");\n } else {\n const sel = figma.currentPage.selection.filter((n: any) => n.type === \"INSTANCE\");\n if (!sel.length) throw new Error(\"No instance selected\");\n inst = sel[0];\n }\n const overrides = inst.overrides || [];\n const main = await inst.getMainComponentAsync();\n return {\n mainComponentId: main?.id,\n overrides: overrides.map((o: any) => ({ id: o.id, fields: o.overriddenFields })),\n };\n}\n\nexport const figmaHandlers: Record<string, (params: any) => Promise<any>> = {\n create_component: (p) => batchHandler(p, createComponentSingle),\n create_component_from_node: (p) => batchHandler(p, fromNodeSingle),\n combine_as_variants: (p) => batchHandler(p, combineSingle),\n add_component_property: (p) => batchHandler(p, addPropSingle),\n create_instance_from_local: (p) => batchHandler(p, instanceSingle),\n search_components: getLocalComponentsFigma,\n get_component_by_id: getComponentByIdFigma,\n get_instance_overrides: getInstanceOverridesFigma,\n};\n","import { z } from \"zod\";\nimport { flexJson, flexBool, flexNum } from \"../utils/coercion\";\nimport * as S from \"./schemas\";\nimport type { McpServer, SendCommandFn } from \"./types\";\nimport { mcpJson, mcpError } from \"./types\";\nimport { batchHandler } from \"./helpers\";\n\n// ─── Schemas ─────────────────────────────────────────────────────\n\nconst paintStyleItem = z.object({\n name: z.string().describe(\"Style name\"),\n color: flexJson(S.colorRgba).describe('Color. Hex \"#FF0000\" or {r,g,b,a?} 0-1.'),\n});\n\nconst textStyleItem = z.object({\n name: z.string().describe(\"Style name\"),\n fontFamily: z.string().describe(\"Font family\"),\n fontStyle: z.string().optional().describe(\"Font style (default: Regular)\"),\n fontSize: z.coerce.number().describe(\"Font size\"),\n lineHeight: flexNum(z.union([\n z.number(),\n z.object({ value: z.coerce.number(), unit: z.enum([\"PIXELS\", \"PERCENT\", \"AUTO\"]) }),\n ])).optional().describe(\"Line height — number (px) or {value, unit}. Default: auto.\"),\n letterSpacing: flexNum(z.union([\n z.number(),\n z.object({ value: z.coerce.number(), unit: z.enum([\"PIXELS\", \"PERCENT\"]) }),\n ])).optional().describe(\"Letter spacing — number (px) or {value, unit}. Default: 0.\"),\n textCase: z.enum([\"ORIGINAL\", \"UPPER\", \"LOWER\", \"TITLE\"]).optional(),\n textDecoration: z.enum([\"NONE\", \"UNDERLINE\", \"STRIKETHROUGH\"]).optional(),\n});\n\nconst effectStyleItem = z.object({\n name: z.string().describe(\"Style name\"),\n effects: flexJson(z.array(S.effectEntry)).describe(\"Array of effects\"),\n});\n\nconst applyStyleItem = z.object({\n nodeId: S.nodeId,\n styleId: z.string().optional().describe(\"Style ID. Provide either styleId or styleName.\"),\n styleName: z.string().optional().describe(\"Style name (case-insensitive substring match). Provide either styleId or styleName.\"),\n styleType: z.preprocess((v) => typeof v === \"string\" ? v.toLowerCase() : v, z.enum([\"fill\", \"stroke\", \"text\", \"effect\"])).describe(\"Type of style: fill, stroke, text, or effect (case-insensitive)\"),\n});\n\n// ─── MCP Registration ────────────────────────────────────────────\n\nexport function registerMcpTools(server: McpServer, sendCommand: SendCommandFn) {\n server.tool(\n \"get_styles\",\n \"List local styles (paint, text, effect, grid). Returns IDs and names only.\",\n {},\n async () => {\n try { return mcpJson(await sendCommand(\"get_styles\")); }\n catch (e) { return mcpError(\"Error getting styles\", e); }\n }\n );\n\n server.tool(\n \"get_style_by_id\",\n \"Get detailed style info by ID. Returns full paint/font/effect/grid details.\",\n { styleId: z.string().describe(\"Style ID\") },\n async ({ styleId }: any) => {\n try { return mcpJson(await sendCommand(\"get_style_by_id\", { styleId })); }\n catch (e) { return mcpError(\"Error getting style\", e); }\n }\n );\n\n server.tool(\n \"remove_style\",\n \"Delete a style by ID.\",\n { styleId: z.string().describe(\"Style ID to remove\") },\n async ({ styleId }: any) => {\n try { return mcpJson(await sendCommand(\"remove_style\", { styleId })); }\n catch (e) { return mcpError(\"Error removing style\", e); }\n }\n );\n\n server.tool(\n \"create_paint_style\",\n \"Create color/paint styles. Batch: pass multiple items.\",\n { items: flexJson(z.array(paintStyleItem)).describe(\"Array of {name, color}\") },\n async (params: any) => {\n try { return mcpJson(await sendCommand(\"create_paint_style\", params)); }\n catch (e) { return mcpError(\"Error creating paint style\", e); }\n }\n );\n\n server.tool(\n \"create_text_style\",\n \"Create text styles. Batch: pass multiple items.\",\n { items: flexJson(z.array(textStyleItem)).describe(\"Array of text style definitions\") },\n async (params: any) => {\n try { return mcpJson(await sendCommand(\"create_text_style\", params)); }\n catch (e) { return mcpError(\"Error creating text style\", e); }\n }\n );\n\n server.tool(\n \"create_effect_style\",\n \"Create effect styles (shadows, blurs). Batch: pass multiple items.\",\n { items: flexJson(z.array(effectStyleItem)).describe(\"Array of {name, effects}\") },\n async (params: any) => {\n try { return mcpJson(await sendCommand(\"create_effect_style\", params)); }\n catch (e) { return mcpError(\"Error creating effect style\", e); }\n }\n );\n\n server.tool(\n \"apply_style_to_node\",\n \"Apply a style to nodes by ID or name. Use styleName for convenience (case-insensitive). Batch: pass multiple items.\",\n { items: flexJson(z.array(applyStyleItem)).describe(\"Array of {nodeId, styleId?, styleName?, styleType}\"), depth: S.depth },\n async (params: any) => {\n try { return mcpJson(await sendCommand(\"apply_style_to_node\", params)); }\n catch (e) { return mcpError(\"Error applying style\", e); }\n }\n );\n}\n\n// ─── Figma Handlers ──────────────────────────────────────────────\n\n/** Ensure Figma-internal trailing comma is present for API lookups.\n * Accepts both raw (S:hex,) and stripped (S:hex) formats for backward compat. */\nfunction ensureStyleId(id: string): string {\n return id.startsWith(\"S:\") && !id.endsWith(\",\") ? id + \",\" : id;\n}\n\nasync function getStylesFigma() {\n const [colors, texts, effects, grids] = await Promise.all([\n figma.getLocalPaintStylesAsync(),\n figma.getLocalTextStylesAsync(),\n figma.getLocalEffectStylesAsync(),\n figma.getLocalGridStylesAsync(),\n ]);\n return {\n colors: colors.map(s => ({ id: s.id, name: s.name })),\n texts: texts.map(s => ({ id: s.id, name: s.name })),\n effects: effects.map(s => ({ id: s.id, name: s.name })),\n grids: grids.map(s => ({ id: s.id, name: s.name })),\n };\n}\n\nfunction rgbaToHex(color: any): string {\n const r = Math.round(color.r * 255);\n const g = Math.round(color.g * 255);\n const b = Math.round(color.b * 255);\n const a = color.a !== undefined ? Math.round(color.a * 255) : 255;\n if (a === 255) return `#${[r, g, b].map(x => x.toString(16).padStart(2, \"0\")).join(\"\")}`;\n return `#${[r, g, b, a].map(x => x.toString(16).padStart(2, \"0\")).join(\"\")}`;\n}\n\nasync function getStyleByIdFigma(params: any) {\n const style = await figma.getStyleByIdAsync(ensureStyleId(params.styleId));\n if (!style) throw new Error(`Style not found: ${params.styleId}`);\n const r: any = { id: style.id, name: style.name, type: style.type };\n if (style.type === \"PAINT\") {\n r.paints = (style as PaintStyle).paints.map((p: any) => {\n const paint = { ...p };\n if (paint.color) paint.color = rgbaToHex(paint.color);\n return paint;\n });\n } else if (style.type === \"TEXT\") {\n const ts = style as TextStyle;\n r.fontSize = ts.fontSize; r.fontName = ts.fontName;\n r.letterSpacing = ts.letterSpacing; r.lineHeight = ts.lineHeight;\n r.textCase = ts.textCase; r.textDecoration = ts.textDecoration;\n } else if (style.type === \"EFFECT\") {\n r.effects = (style as EffectStyle).effects;\n }\n return r;\n}\n\nasync function removeStyleFigma(params: any) {\n const style = await figma.getStyleByIdAsync(ensureStyleId(params.styleId));\n if (!style) throw new Error(`Style not found: ${params.styleId}`);\n style.remove();\n return \"ok\";\n}\n\nasync function createPaintStyleSingle(p: any) {\n const style = figma.createPaintStyle();\n style.name = p.name;\n const { r, g, b, a = 1 } = p.color;\n style.paints = [{ type: \"SOLID\", color: { r, g, b }, opacity: a }];\n return { id: style.id };\n}\n\nasync function createTextStyleSingle(p: any) {\n const style = figma.createTextStyle();\n style.name = p.name;\n const fontStyle = p.fontStyle || \"Regular\";\n await figma.loadFontAsync({ family: p.fontFamily, style: fontStyle });\n style.fontName = { family: p.fontFamily, style: fontStyle };\n style.fontSize = p.fontSize;\n if (p.lineHeight !== undefined) {\n if (typeof p.lineHeight === \"number\") style.lineHeight = { value: p.lineHeight, unit: \"PIXELS\" };\n else if (p.lineHeight.unit === \"AUTO\") style.lineHeight = { unit: \"AUTO\" };\n else style.lineHeight = { value: p.lineHeight.value, unit: p.lineHeight.unit };\n }\n if (p.letterSpacing !== undefined) {\n if (typeof p.letterSpacing === \"number\") style.letterSpacing = { value: p.letterSpacing, unit: \"PIXELS\" };\n else style.letterSpacing = { value: p.letterSpacing.value, unit: p.letterSpacing.unit };\n }\n if (p.textCase) style.textCase = p.textCase;\n if (p.textDecoration) style.textDecoration = p.textDecoration;\n return { id: style.id };\n}\n\nasync function createEffectStyleSingle(p: any) {\n const style = figma.createEffectStyle();\n style.name = p.name;\n style.effects = p.effects.map((e: any) => {\n const eff: any = { type: e.type, radius: e.radius, visible: e.visible ?? true };\n if (e.type === \"DROP_SHADOW\" || e.type === \"INNER_SHADOW\") eff.blendMode = e.blendMode || \"NORMAL\";\n if (e.color) eff.color = { r: e.color.r, g: e.color.g, b: e.color.b, a: e.color.a ?? 1 };\n if (e.offset) eff.offset = { x: e.offset.x, y: e.offset.y };\n if (e.spread !== undefined) eff.spread = e.spread;\n return eff;\n });\n return { id: style.id };\n}\n\nasync function applyStyleSingle(p: any) {\n const node = await figma.getNodeByIdAsync(p.nodeId);\n if (!node) throw new Error(`Node not found: ${p.nodeId}`);\n\n let styleId = p.styleId ? ensureStyleId(p.styleId) : null;\n let matchedStyle: string | undefined;\n if (!styleId && p.styleName) {\n const [paints, texts, effects] = await Promise.all([\n figma.getLocalPaintStylesAsync(), figma.getLocalTextStylesAsync(), figma.getLocalEffectStylesAsync(),\n ]);\n // Filter to styles relevant for the requested type\n const typeMap: Record<string, any[]> = { fill: paints, stroke: paints, text: texts, effect: effects };\n const relevant = typeMap[p.styleType] || [...paints, ...texts, ...effects];\n const exact = relevant.find(s => s.name === p.styleName);\n if (exact) { styleId = exact.id; matchedStyle = exact.name; }\n else {\n const fuzzy = relevant.find(s => s.name.toLowerCase().includes(p.styleName.toLowerCase()));\n if (!fuzzy) {\n const available = relevant.map(s => s.name).slice(0, 20);\n const suffix = relevant.length > 20 ? `, … and ${relevant.length - 20} more` : \"\";\n throw new Error(`styleName '${p.styleName}' not found for type '${p.styleType}'. Available: [${available.join(\", \")}${suffix}]`);\n }\n styleId = fuzzy.id;\n matchedStyle = fuzzy.name;\n }\n }\n\n switch (p.styleType) {\n case \"fill\": await (node as any).setFillStyleIdAsync(styleId); break;\n case \"stroke\": await (node as any).setStrokeStyleIdAsync(styleId); break;\n case \"text\": await (node as any).setTextStyleIdAsync(styleId); break;\n case \"effect\": await (node as any).setEffectStyleIdAsync(styleId); break;\n default: throw new Error(`Unknown style type: ${p.styleType}`);\n }\n const result: any = { styleId: styleId };\n if (matchedStyle) result.matchedStyle = matchedStyle;\n // Hint when both styleId and styleName provided\n if (p.styleId && p.styleName) {\n result.warning = \"Both styleId and styleName provided — used styleId. Pass only one: styleName (by name lookup) or styleId (direct ID).\";\n }\n return result;\n}\n\nexport const figmaHandlers: Record<string, (params: any) => Promise<any>> = {\n get_styles: getStylesFigma,\n get_style_by_id: getStyleByIdFigma,\n remove_style: removeStyleFigma,\n create_paint_style: (p) => batchHandler(p, createPaintStyleSingle),\n create_text_style: (p) => batchHandler(p, createTextStyleSingle),\n create_effect_style: (p) => batchHandler(p, createEffectStyleSingle),\n apply_style_to_node: (p) => batchHandler(p, applyStyleSingle),\n};\n","import { z } from \"zod\";\nimport { flexJson, flexBool } from \"../utils/coercion\";\nimport * as S from \"./schemas\";\nimport type { McpServer, SendCommandFn } from \"./types\";\nimport { mcpJson, mcpError } from \"./types\";\nimport { findVariableById } from \"./helpers\";\n\n// ─── Schemas ─────────────────────────────────────────────────────\n\nconst collectionItem = z.object({\n name: z.string().describe(\"Collection name\"),\n});\n\nconst variableItem = z.object({\n collectionId: z.string().describe(\"Variable collection ID\"),\n name: z.string().describe(\"Variable name\"),\n resolvedType: z.enum([\"COLOR\", \"FLOAT\", \"STRING\", \"BOOLEAN\"]).describe(\"Variable type\"),\n});\n\nconst setValueItem = z.object({\n variableId: z.string().describe(\"Variable ID (use full ID from create_variable response, e.g. VariableID:1:6)\"),\n modeId: z.string().describe(\"Mode ID\"),\n value: flexJson(z.union([\n z.number(), z.string(), z.boolean(),\n z.object({ r: z.coerce.number(), g: z.coerce.number(), b: z.coerce.number(), a: z.coerce.number().optional() }),\n ])).describe(\"Value: number, string, boolean, or {r,g,b,a} color\"),\n});\n\nconst bindingItem = z.object({\n nodeId: z.string().describe(\"Node ID\"),\n field: z.string().describe(\"Property field (e.g., 'opacity', 'fills/0/color')\"),\n variableId: z.string().describe(\"Variable ID (use full ID from create_variable response, e.g. VariableID:1:6)\"),\n});\n\nconst addModeItem = z.object({\n collectionId: z.string().describe(\"Collection ID\"),\n name: z.string().describe(\"Mode name\"),\n});\n\nconst renameModeItem = z.object({\n collectionId: z.string().describe(\"Collection ID\"),\n modeId: z.string().describe(\"Mode ID\"),\n name: z.string().describe(\"New name\"),\n});\n\nconst removeModeItem = z.object({\n collectionId: z.string().describe(\"Collection ID\"),\n modeId: z.string().describe(\"Mode ID\"),\n});\n\nconst setExplicitModeItem = z.object({\n nodeId: S.nodeId,\n collectionId: z.string().describe(\"Variable collection ID\"),\n modeId: z.string().describe(\"Mode ID to pin (e.g. Dark mode)\"),\n});\n\n// ─── MCP Registration ────────────────────────────────────────────\n\nexport function registerMcpTools(server: McpServer, sendCommand: SendCommandFn) {\n server.tool(\n \"create_variable_collection\",\n \"Create variable collections. Batch: pass multiple items.\",\n { items: flexJson(z.array(collectionItem)).describe(\"Array of {name}\") },\n async ({ items }: any) => {\n try { return mcpJson(await sendCommand(\"create_variable_collection\", { items })); }\n catch (e) { return mcpError(\"Error creating variable collection\", e); }\n }\n );\n\n server.tool(\n \"create_variable\",\n \"Create variables in a collection. Batch: pass multiple items.\",\n { items: flexJson(z.array(variableItem)).describe(\"Array of {collectionId, name, resolvedType}\") },\n async ({ items }: any) => {\n try { return mcpJson(await sendCommand(\"create_variable\", { items })); }\n catch (e) { return mcpError(\"Error creating variable\", e); }\n }\n );\n\n server.tool(\n \"set_variable_value\",\n \"Set variable values for modes. Batch: pass multiple items.\",\n { items: flexJson(z.array(setValueItem)).describe(\"Array of {variableId, modeId, value}\") },\n async ({ items }: any) => {\n try { return mcpJson(await sendCommand(\"set_variable_value\", { items })); }\n catch (e) { return mcpError(\"Error setting variable value\", e); }\n }\n );\n\n server.tool(\n \"get_local_variables\",\n \"List local variables. Pass includeValues:true to get all mode values in bulk (avoids N separate get_variable_by_id calls).\",\n {\n type: z.enum([\"COLOR\", \"FLOAT\", \"STRING\", \"BOOLEAN\"]).optional().describe(\"Filter by type\"),\n collectionId: z.string().optional().describe(\"Filter by collection. Omit for all collections.\"),\n includeValues: flexBool(z.boolean()).optional().describe(\"Include valuesByMode for each variable (default: false)\"),\n },\n async (params: any) => {\n try { return mcpJson(await sendCommand(\"get_local_variables\", params)); }\n catch (e) { return mcpError(\"Error getting variables\", e); }\n }\n );\n\n server.tool(\n \"get_local_variable_collections\",\n \"List all local variable collections.\",\n {},\n async () => {\n try { return mcpJson(await sendCommand(\"get_local_variable_collections\")); }\n catch (e) { return mcpError(\"Error getting variable collections\", e); }\n }\n );\n\n server.tool(\n \"get_variable_by_id\",\n \"Get detailed variable info including all mode values.\",\n { variableId: z.string().describe(\"Variable ID\") },\n async ({ variableId }: any) => {\n try { return mcpJson(await sendCommand(\"get_variable_by_id\", { variableId })); }\n catch (e) { return mcpError(\"Error getting variable\", e); }\n }\n );\n\n server.tool(\n \"get_variable_collection_by_id\",\n \"Get detailed variable collection info including modes and variable IDs.\",\n { collectionId: z.string().describe(\"Collection ID\") },\n async ({ collectionId }: any) => {\n try { return mcpJson(await sendCommand(\"get_variable_collection_by_id\", { collectionId })); }\n catch (e) { return mcpError(\"Error getting variable collection\", e); }\n }\n );\n\n server.tool(\n \"set_variable_binding\",\n \"Bind variables to node properties. Common fields: 'fills/0/color', 'strokes/0/color', 'opacity', 'topLeftRadius', 'itemSpacing'. Batch: pass multiple items.\",\n { items: flexJson(z.array(bindingItem)).describe(\"Array of {nodeId, field, variableId}\") },\n async ({ items }: any) => {\n try { return mcpJson(await sendCommand(\"set_variable_binding\", { items })); }\n catch (e) { return mcpError(\"Error binding variable\", e); }\n }\n );\n\n server.tool(\n \"add_mode\",\n \"Add modes to variable collections. Batch: pass multiple items.\",\n { items: flexJson(z.array(addModeItem)).describe(\"Array of {collectionId, name}\") },\n async ({ items }: any) => {\n try { return mcpJson(await sendCommand(\"add_mode\", { items })); }\n catch (e) { return mcpError(\"Error adding mode\", e); }\n }\n );\n\n server.tool(\n \"rename_mode\",\n \"Rename modes in variable collections. Batch: pass multiple items.\",\n { items: flexJson(z.array(renameModeItem)).describe(\"Array of {collectionId, modeId, name}\") },\n async ({ items }: any) => {\n try { return mcpJson(await sendCommand(\"rename_mode\", { items })); }\n catch (e) { return mcpError(\"Error renaming mode\", e); }\n }\n );\n\n server.tool(\n \"remove_mode\",\n \"Remove modes from variable collections. Batch: pass multiple items.\",\n { items: flexJson(z.array(removeModeItem)).describe(\"Array of {collectionId, modeId}\") },\n async ({ items }: any) => {\n try { return mcpJson(await sendCommand(\"remove_mode\", { items })); }\n catch (e) { return mcpError(\"Error removing mode\", e); }\n }\n );\n\n server.tool(\n \"set_explicit_variable_mode\",\n \"Pin a variable collection mode on a frame (e.g. show Dark mode). Batch: pass multiple items.\",\n { items: flexJson(z.array(setExplicitModeItem)).describe(\"Array of {nodeId, collectionId, modeId}\") },\n async ({ items }: any) => {\n try { return mcpJson(await sendCommand(\"set_explicit_variable_mode\", { items })); }\n catch (e) { return mcpError(\"Error setting variable mode\", e); }\n }\n );\n\n server.tool(\n \"get_node_variables\",\n \"Get variable bindings on a node. Returns which variables are bound to fills, strokes, opacity, corner radius, etc.\",\n { nodeId: S.nodeId },\n async ({ nodeId }: any) => {\n try { return mcpJson(await sendCommand(\"get_node_variables\", { nodeId })); }\n catch (e) { return mcpError(\"Error getting node variables\", e); }\n }\n );\n}\n\n// ─── Figma Handlers ──────────────────────────────────────────────\n\n/** Resolve a variable collection by ID with scan fallback.\n * Direct lookup can fail for recently-created collections. */\nasync function findCollectionById(id: string): Promise<any> {\n const direct = await figma.variables.getVariableCollectionByIdAsync(id);\n if (direct) return direct;\n const all = await figma.variables.getLocalVariableCollectionsAsync();\n return all.find(c => c.id === id) || null;\n}\n\nasync function createCollectionSingle(p: any) {\n const collection = figma.variables.createVariableCollection(p.name);\n return { id: collection.id, modes: collection.modes, defaultModeId: collection.defaultModeId };\n}\n\nasync function createVariableSingle(p: any) {\n const collection = await findCollectionById(p.collectionId);\n if (!collection) throw new Error(`Collection not found: ${p.collectionId}`);\n const variable = figma.variables.createVariable(p.name, collection, p.resolvedType);\n return { id: variable.id };\n}\n\nasync function setValueSingle(p: any) {\n const variable = await findVariableById(p.variableId);\n if (!variable) throw new Error(`Variable not found: ${p.variableId}`);\n let value = p.value;\n if (typeof value === \"object\" && value !== null && \"r\" in value) {\n value = { r: value.r, g: value.g, b: value.b, a: value.a ?? 1 };\n }\n variable.setValueForMode(p.modeId, value);\n return {};\n}\n\nasync function getLocalVariablesFigma(params: any) {\n let variables = params?.type\n ? await figma.variables.getLocalVariablesAsync(params.type)\n : await figma.variables.getLocalVariablesAsync();\n if (params?.collectionId) variables = variables.filter((v: any) => v.variableCollectionId === params.collectionId);\n const includeValues = params?.includeValues === true;\n return {\n variables: variables.map((v: any) => {\n const entry: any = { id: v.id, name: v.name, resolvedType: v.resolvedType, variableCollectionId: v.variableCollectionId };\n if (includeValues) entry.valuesByMode = v.valuesByMode;\n return entry;\n }),\n };\n}\n\nasync function getLocalCollectionsFigma() {\n const collections = await figma.variables.getLocalVariableCollectionsAsync();\n return {\n collections: collections.map((c: any) => ({ id: c.id, name: c.name, modes: c.modes, defaultModeId: c.defaultModeId, variableIds: c.variableIds })),\n };\n}\n\nasync function getVariableByIdFigma(params: any) {\n const v = await findVariableById(params.variableId);\n if (!v) throw new Error(`Variable not found: ${params.variableId}`);\n return { id: v.id, name: v.name, resolvedType: v.resolvedType, variableCollectionId: v.variableCollectionId, valuesByMode: v.valuesByMode, description: v.description, scopes: v.scopes };\n}\n\nasync function getCollectionByIdFigma(params: any) {\n const c = await findCollectionById(params.collectionId);\n if (!c) throw new Error(`Collection not found: ${params.collectionId}`);\n return { id: c.id, name: c.name, modes: c.modes, defaultModeId: c.defaultModeId, variableIds: c.variableIds };\n}\n\nasync function setBindingSingle(p: any) {\n const node = await figma.getNodeByIdAsync(p.nodeId);\n if (!node) throw new Error(`Node not found: ${p.nodeId}`);\n const variable = await findVariableById(p.variableId);\n if (!variable) throw new Error(`Variable not found: ${p.variableId}`);\n\n const paintMatch = p.field.match(/^(fills|strokes)\\/(\\d+)\\/color$/);\n if (paintMatch) {\n const prop = paintMatch[1];\n const index = parseInt(paintMatch[2], 10);\n if (!(prop in node)) throw new Error(`Node does not have ${prop}`);\n const paints = (node as any)[prop].slice();\n if (index >= paints.length) throw new Error(`${prop} index ${index} out of range`);\n const newPaint = figma.variables.setBoundVariableForPaint(paints[index], \"color\", variable);\n paints[index] = newPaint;\n (node as any)[prop] = paints;\n } else if (\"setBoundVariable\" in node) {\n (node as any).setBoundVariable(p.field, variable);\n } else {\n throw new Error(\"Node does not support variable binding\");\n }\n return {};\n}\n\nasync function addModeSingle(p: any) {\n const c = await findCollectionById(p.collectionId);\n if (!c) throw new Error(`Collection not found: ${p.collectionId}`);\n const modeId = c.addMode(p.name);\n return { modeId, modes: c.modes };\n}\n\nasync function renameModeSingle(p: any) {\n const c = await findCollectionById(p.collectionId);\n if (!c) throw new Error(`Collection not found: ${p.collectionId}`);\n c.renameMode(p.modeId, p.name);\n return { modes: c.modes };\n}\n\nasync function removeModeSingle(p: any) {\n const c = await findCollectionById(p.collectionId);\n if (!c) throw new Error(`Collection not found: ${p.collectionId}`);\n c.removeMode(p.modeId);\n return { modes: c.modes };\n}\n\nasync function setExplicitModeSingle(p: any) {\n const node = await figma.getNodeByIdAsync(p.nodeId);\n if (!node) throw new Error(`Node not found: ${p.nodeId}`);\n if (!(\"setExplicitVariableModeForCollection\" in node)) throw new Error(`Node ${p.nodeId} (${node.type}) does not support explicit variable modes. Use a FRAME, COMPONENT, or COMPONENT_SET.`);\n const collection = await findCollectionById(p.collectionId);\n if (!collection) throw new Error(`Collection not found: ${p.collectionId}`);\n try {\n (node as any).setExplicitVariableModeForCollection(collection, p.modeId);\n } catch (e: any) {\n throw new Error(`Failed to set mode '${p.modeId}' on node ${p.nodeId}: ${e.message}. Ensure the modeId is valid for collection '${collection.name}'.`);\n }\n return {};\n}\n\nasync function getNodeVariablesFigma(params: any) {\n const node = await figma.getNodeByIdAsync(params.nodeId);\n if (!node) throw new Error(`Node not found: ${params.nodeId}`);\n const result: any = { nodeId: params.nodeId };\n if (\"boundVariables\" in node) {\n const bv = (node as any).boundVariables;\n if (bv && typeof bv === \"object\") {\n const bindings: Record<string, any> = {};\n for (const [key, val] of Object.entries(bv)) {\n if (Array.isArray(val)) {\n bindings[key] = val.map((v: any) => v?.id ? { variableId: v.id, field: v.field } : v);\n } else if (val && typeof val === \"object\" && (val as any).id) {\n bindings[key] = { variableId: (val as any).id, field: (val as any).field };\n }\n }\n result.boundVariables = bindings;\n }\n }\n if (\"explicitVariableModes\" in node) {\n result.explicitVariableModes = (node as any).explicitVariableModes;\n }\n return result;\n}\n\nasync function batchHandler(params: any, fn: (item: any) => Promise<any>) {\n const items = params.items || [params];\n const results = [];\n for (const item of items) {\n try {\n const r = await fn(item);\n results.push(r && typeof r === \"object\" && Object.keys(r).length === 0 ? \"ok\" : r);\n }\n catch (e: any) { results.push({ error: e.message }); }\n }\n return { results };\n}\n\nexport const figmaHandlers: Record<string, (params: any) => Promise<any>> = {\n create_variable_collection: (p) => batchHandler(p, createCollectionSingle),\n create_variable: (p) => batchHandler(p, createVariableSingle),\n set_variable_value: (p) => batchHandler(p, setValueSingle),\n get_local_variables: getLocalVariablesFigma,\n get_local_variable_collections: getLocalCollectionsFigma,\n get_variable_by_id: getVariableByIdFigma,\n get_variable_collection_by_id: getCollectionByIdFigma,\n set_variable_binding: (p) => batchHandler(p, setBindingSingle),\n add_mode: (p) => batchHandler(p, addModeSingle),\n rename_mode: (p) => batchHandler(p, renameModeSingle),\n remove_mode: (p) => batchHandler(p, removeModeSingle),\n set_explicit_variable_mode: (p) => batchHandler(p, setExplicitModeSingle),\n get_node_variables: getNodeVariablesFigma,\n};\n","import { z } from \"zod\";\nimport { flexJson, flexBool } from \"../utils/coercion\";\nimport * as S from \"./schemas\";\nimport type { McpServer, SendCommandFn } from \"./types\";\nimport { mcpJson, mcpError } from \"./types\";\nimport { batchHandler } from \"./helpers\";\n\n// ─── Schemas ─────────────────────────────────────────────────────\n\nconst lintRules = z.enum([\n \"no-autolayout\", // Frames with >1 child and no auto-layout\n \"shape-instead-of-frame\", // Shapes used where FRAME should be\n \"hardcoded-color\", // Fills/strokes not using styles\n \"no-text-style\", // Text nodes without text style\n \"fixed-in-autolayout\", // Fixed-size children in auto-layout parents\n \"default-name\", // Nodes with default/unnamed names\n \"empty-container\", // Frames/components with layout but no children\n \"stale-text-name\", // Text nodes where layer name diverges from content\n \"all\", // Run all rules\n]);\n\n// ─── MCP Registration ────────────────────────────────────────────\n\nexport function registerMcpTools(server: McpServer, sendCommand: SendCommandFn) {\n server.tool(\n \"lint_node\",\n \"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.\",\n {\n nodeId: z.string().optional().describe(\"Node ID to lint. Omit to lint current selection.\"),\n rules: flexJson(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, all'),\n maxDepth: z.coerce.number().optional().describe(\"Max depth to recurse (default: 10)\"),\n maxFindings: z.coerce.number().optional().describe(\"Stop after N findings (default: 50)\"),\n },\n async (params: any) => {\n try { return mcpJson(await sendCommand(\"lint_node\", params)); }\n catch (e) { return mcpError(\"Error running lint\", e); }\n }\n );\n\n server.tool(\n \"lint_fix_autolayout\",\n \"Auto-fix: convert frames with multiple children to auto-layout. Takes node IDs from lint_node 'no-autolayout' results.\",\n {\n items: flexJson(z.array(z.object({\n nodeId: S.nodeId,\n layoutMode: z.enum([\"HORIZONTAL\", \"VERTICAL\"]).optional().describe(\"Layout direction (default: auto-detect based on child positions)\"),\n itemSpacing: z.coerce.number().optional().describe(\"Spacing between children (default: 0)\"),\n }))).describe(\"Array of frames to convert to auto-layout\"),\n depth: S.depth,\n },\n async (params: any) => {\n try { return mcpJson(await sendCommand(\"lint_fix_autolayout\", params)); }\n catch (e) { return mcpError(\"Error fixing auto-layout\", e); }\n }\n );\n\n server.tool(\n \"lint_fix_replace_shape_with_frame\",\n \"Auto-fix: replace shapes with frames preserving visual properties. Overlapping siblings are re-parented into the new frame. Use after lint_node 'shape-instead-of-frame' results.\",\n {\n items: flexJson(z.array(z.object({\n nodeId: S.nodeId,\n adoptChildren: flexBool(z.boolean()).optional().describe(\"Re-parent overlapping siblings into the new frame (default: true)\"),\n }))).describe(\"Array of shapes to convert to frames\"),\n depth: S.depth,\n },\n async (params: any) => {\n try { return mcpJson(await sendCommand(\"lint_fix_replace_shape_with_frame\", params)); }\n catch (e) { return mcpError(\"Error converting shapes to frames\", e); }\n }\n );\n}\n\n// ─── Figma Handlers ──────────────────────────────────────────────\n\n/** Collected issue: just rule + nodeId. Grouping and prose happen at the end. */\ninterface Issue {\n rule: string;\n nodeId: string;\n nodeName: string;\n /** Extra context for the prose generator */\n extra?: Record<string, any>;\n}\n\nasync function lintNodeHandler(params: any): Promise<any> {\n const ruleSet = new Set<string>(params?.rules || [\"all\"]);\n const runAll = ruleSet.has(\"all\");\n const maxDepth = params?.maxDepth ?? 10;\n const maxFindings = params?.maxFindings ?? 50;\n\n // Get root node\n let root: BaseNode;\n if (params?.nodeId) {\n const node = await figma.getNodeByIdAsync(params.nodeId);\n if (!node) throw new Error(`Node not found: ${params.nodeId}`);\n root = node;\n } else {\n const sel = figma.currentPage.selection;\n if (sel.length === 0) throw new Error(\"Nothing selected and no nodeId provided\");\n root = sel.length === 1 ? sel[0] : figma.currentPage;\n }\n\n // Collect local styles for checks\n let localPaintStyleIds = new Set<string>();\n let localTextStyleIds = new Set<string>();\n if (runAll || ruleSet.has(\"hardcoded-color\")) {\n const paints = await figma.getLocalPaintStylesAsync();\n localPaintStyleIds = new Set(paints.map(s => s.id));\n }\n if (runAll || ruleSet.has(\"no-text-style\")) {\n const texts = await figma.getLocalTextStylesAsync();\n localTextStyleIds = new Set(texts.map(s => s.id));\n }\n\n const issues: Issue[] = [];\n const ctx: LintCtx = { runAll, ruleSet, maxDepth, maxFindings, localPaintStyleIds, localTextStyleIds, hasPaintStyles: localPaintStyleIds.size > 0, hasTextStyles: localTextStyleIds.size > 0 };\n\n await walkNode(root, 0, issues, ctx);\n\n const truncated = issues.length >= maxFindings;\n\n // Group by rule → prose output\n const grouped: Record<string, Issue[]> = {};\n for (const issue of issues) {\n if (!grouped[issue.rule]) grouped[issue.rule] = [];\n grouped[issue.rule].push(issue);\n }\n\n const categories: any[] = [];\n for (const [rule, ruleIssues] of Object.entries(grouped)) {\n categories.push({\n rule,\n count: ruleIssues.length,\n fix: FIX_INSTRUCTIONS[rule] || \"Review and fix manually.\",\n nodes: ruleIssues.map(i => {\n const entry: any = { id: i.nodeId, name: i.nodeName };\n if (i.extra) Object.assign(entry, i.extra);\n return entry;\n }),\n });\n }\n\n const result: any = { nodeId: root.id, nodeName: root.name, categories };\n if (truncated) {\n const breakdown = categories.map(c => `${c.rule}: ${c.count}`).join(\", \");\n result.warning = `Showing first ${maxFindings} findings (${breakdown}). Increase maxFindings or lint specific rules (e.g. rules: [\"hardcoded-color\"]) to see more.`;\n }\n return result;\n}\n\n/** Per-rule fix instructions — natural language, actionable, referencing MCP tools */\nconst FIX_INSTRUCTIONS: Record<string, string> = {\n \"no-autolayout\": \"Use lint_fix_autolayout or update_frame with layoutMode to add auto-layout to these frames.\",\n \"shape-instead-of-frame\": \"Use lint_fix_replace_shape_with_frame to convert these shapes to frames with children.\",\n \"hardcoded-color\": \"Use set_fill_color with styleName to apply a paint style, or set_variable_binding to bind to a color variable.\",\n \"no-text-style\": \"Use apply_style_to_node with styleType:\\\"text\\\" and styleName, or set_variable_binding to bind text properties to variables.\",\n \"fixed-in-autolayout\": \"Use update_frame with layoutSizingHorizontal/layoutSizingVertical to set FILL or HUG instead of FIXED sizing.\",\n \"default-name\": \"Use set_node_properties to give descriptive names.\",\n \"empty-container\": \"These frames or components have auto-layout but no children. Delete them or add content.\",\n \"stale-text-name\": \"These text nodes have layer names that don't match their content. Use set_node_properties to rename, or leave if intentional.\",\n};\n\ninterface LintCtx {\n runAll: boolean;\n ruleSet: Set<string>;\n maxDepth: number;\n maxFindings: number;\n localPaintStyleIds: Set<string>;\n localTextStyleIds: Set<string>;\n hasPaintStyles: boolean;\n hasTextStyles: boolean;\n}\n\nasync function walkNode(node: BaseNode, depth: number, issues: Issue[], ctx: LintCtx) {\n if (issues.length >= ctx.maxFindings) return;\n if (depth > ctx.maxDepth) return;\n\n // ── Rule: no-autolayout ──\n if (ctx.runAll || ctx.ruleSet.has(\"no-autolayout\")) {\n if (isFrame(node) && node.layoutMode === \"NONE\" && \"children\" in node) {\n const childCount = (node as any).children.length;\n if (childCount > 1) {\n const direction = detectLayoutDirection(node as FrameNode);\n issues.push({ rule: \"no-autolayout\", nodeId: node.id, nodeName: node.name, extra: { suggestedDirection: direction } });\n if (issues.length >= ctx.maxFindings) return;\n }\n }\n }\n\n // ── Rule: shape-instead-of-frame ──\n if (ctx.runAll || ctx.ruleSet.has(\"shape-instead-of-frame\")) {\n if (isShape(node) && node.parent && \"children\" in node.parent) {\n const siblings = (node.parent as any).children as SceneNode[];\n const bounds = getAbsoluteBounds(node as SceneNode);\n if (bounds) {\n const overlapping = siblings.filter(s => {\n if (s.id === node.id) return false;\n const sb = getAbsoluteBounds(s);\n if (!sb) return false;\n return sb.x >= bounds.x && sb.y >= bounds.y\n && sb.x + sb.width <= bounds.x + bounds.width\n && sb.y + sb.height <= bounds.y + bounds.height;\n });\n if (overlapping.length > 0) {\n issues.push({ rule: \"shape-instead-of-frame\", nodeId: node.id, nodeName: node.name, extra: { overlappingIds: overlapping.map(s => s.id) } });\n if (issues.length >= ctx.maxFindings) return;\n }\n }\n }\n }\n\n // ── Rule: hardcoded-color ──\n if ((ctx.runAll || ctx.ruleSet.has(\"hardcoded-color\")) && ctx.hasPaintStyles) {\n if (\"fills\" in node && \"fillStyleId\" in node) {\n const fills = (node as any).fills;\n const fillStyleId = (node as any).fillStyleId;\n const hasFillVar = (node as any).boundVariables?.fills?.length > 0;\n if (fills && Array.isArray(fills) && fills.length > 0 && fills[0].type === \"SOLID\") {\n if (!hasFillVar && (!fillStyleId || fillStyleId === \"\" || fillStyleId === figma.mixed)) {\n issues.push({ rule: \"hardcoded-color\", nodeId: node.id, nodeName: node.name });\n if (issues.length >= ctx.maxFindings) return;\n }\n }\n }\n }\n\n // ── Rule: no-text-style ──\n if ((ctx.runAll || ctx.ruleSet.has(\"no-text-style\")) && ctx.hasTextStyles) {\n if (node.type === \"TEXT\") {\n const textStyleId = (node as any).textStyleId;\n const hasTextVar = (node as any).boundVariables && Object.keys((node as any).boundVariables).length > 0;\n if (!hasTextVar && (!textStyleId || textStyleId === \"\" || textStyleId === figma.mixed)) {\n issues.push({ rule: \"no-text-style\", nodeId: node.id, nodeName: node.name });\n if (issues.length >= ctx.maxFindings) return;\n }\n }\n }\n\n // ── Rule: fixed-in-autolayout ──\n if (ctx.runAll || ctx.ruleSet.has(\"fixed-in-autolayout\")) {\n if (isFrame(node) && node.layoutMode !== \"NONE\" && \"children\" in node) {\n for (const child of (node as any).children) {\n if (issues.length >= ctx.maxFindings) break;\n if (!(\"layoutSizingHorizontal\" in child)) continue;\n if (child.layoutSizingHorizontal === \"FIXED\" && child.layoutSizingVertical === \"FIXED\") {\n issues.push({ rule: \"fixed-in-autolayout\", nodeId: child.id, nodeName: child.name, extra: { parentId: node.id, axis: node.layoutMode === \"HORIZONTAL\" ? \"horizontal\" : \"vertical\" } });\n }\n }\n if (issues.length >= ctx.maxFindings) return;\n }\n }\n\n // ── Rule: default-name ──\n if (ctx.runAll || ctx.ruleSet.has(\"default-name\")) {\n const defaultNames = [\"Frame\", \"Rectangle\", \"Ellipse\", \"Line\", \"Text\", \"Group\", \"Component\", \"Instance\", \"Section\", \"Vector\"];\n const isDefault = defaultNames.some(d => node.name === d || /^.+ \\d+$/.test(node.name) && node.name.startsWith(d));\n if (isDefault && node.type !== \"PAGE\") {\n issues.push({ rule: \"default-name\", nodeId: node.id, nodeName: node.name });\n if (issues.length >= ctx.maxFindings) return;\n }\n }\n\n // ── Rule: empty-container ──\n if (ctx.runAll || ctx.ruleSet.has(\"empty-container\")) {\n if (isFrame(node) && \"children\" in node && (node as any).children.length === 0) {\n issues.push({ rule: \"empty-container\", nodeId: node.id, nodeName: node.name });\n if (issues.length >= ctx.maxFindings) return;\n }\n }\n\n // ── Rule: stale-text-name ──\n if (ctx.runAll || ctx.ruleSet.has(\"stale-text-name\")) {\n if (node.type === \"TEXT\") {\n const chars = (node as any).characters as string;\n // Only flag if both name and characters are non-empty and they differ\n if (chars && node.name && node.name !== chars && node.name !== chars.slice(0, node.name.length)) {\n issues.push({ rule: \"stale-text-name\", nodeId: node.id, nodeName: node.name, extra: { characters: chars.slice(0, 60) } });\n if (issues.length >= ctx.maxFindings) return;\n }\n }\n }\n\n // Recurse into children\n if (\"children\" in node) {\n for (const child of (node as any).children) {\n if (issues.length >= ctx.maxFindings) break;\n await walkNode(child, depth + 1, issues, ctx);\n }\n }\n}\n\nfunction isFrame(node: BaseNode): node is FrameNode {\n return node.type === \"FRAME\" || node.type === \"COMPONENT\" || node.type === \"COMPONENT_SET\";\n}\n\nconst SHAPE_TYPES = new Set([\"RECTANGLE\", \"ELLIPSE\", \"POLYGON\", \"STAR\", \"VECTOR\", \"LINE\"]);\nfunction isShape(node: BaseNode): boolean {\n return SHAPE_TYPES.has(node.type);\n}\n\nfunction getAbsoluteBounds(node: SceneNode): { x: number; y: number; width: number; height: number } | null {\n if (\"absoluteBoundingBox\" in node && (node as any).absoluteBoundingBox) {\n return (node as any).absoluteBoundingBox;\n }\n if (\"x\" in node && \"width\" in node) {\n return { x: (node as any).x, y: (node as any).y, width: (node as any).width, height: (node as any).height };\n }\n return null;\n}\n\nfunction detectLayoutDirection(frame: FrameNode): \"VERTICAL\" | \"HORIZONTAL\" {\n const children = frame.children;\n if (children.length < 2) return \"VERTICAL\";\n let xVariance = 0;\n let yVariance = 0;\n for (let i = 1; i < children.length; i++) {\n xVariance += Math.abs(children[i].x - children[i - 1].x);\n yVariance += Math.abs(children[i].y - children[i - 1].y);\n }\n return yVariance >= xVariance ? \"VERTICAL\" : \"HORIZONTAL\";\n}\n\n// ── Auto-fix handlers ──\n\nasync function fixAutolayoutSingle(p: any) {\n const node = await figma.getNodeByIdAsync(p.nodeId);\n if (!node) throw new Error(`Node not found: ${p.nodeId}`);\n if (!isFrame(node)) throw new Error(`Node ${p.nodeId} is ${node.type}, not a FRAME`);\n if (node.layoutMode !== \"NONE\") return { skipped: true, reason: \"Already has auto-layout\" };\n\n const direction = p.layoutMode || detectLayoutDirection(node);\n node.layoutMode = direction;\n if (p.itemSpacing !== undefined) {\n node.itemSpacing = p.itemSpacing;\n }\n return { layoutMode: direction };\n}\n\nasync function fixShapeToFrameSingle(p: any) {\n const shape = await figma.getNodeByIdAsync(p.nodeId);\n if (!shape) throw new Error(`Node not found: ${p.nodeId}`);\n if (!isShape(shape)) throw new Error(`Node ${p.nodeId} is ${shape.type}, not a shape (RECTANGLE, ELLIPSE, etc.)`);\n\n const parent = shape.parent;\n if (!parent || !(\"children\" in parent)) throw new Error(`Shape has no valid parent`);\n\n const s = shape as any;\n const frame = figma.createFrame();\n frame.name = s.name || \"Container\";\n frame.x = s.x;\n frame.y = s.y;\n frame.resize(s.width, s.height);\n\n // Copy visual properties\n if (s.fills) frame.fills = s.fills;\n if (s.strokes) frame.strokes = s.strokes;\n if (s.strokeWeight !== undefined) frame.strokeWeight = s.strokeWeight;\n if (s.strokeAlign) frame.strokeAlign = s.strokeAlign;\n if (s.opacity !== undefined) frame.opacity = s.opacity;\n if (s.cornerRadius !== undefined && s.cornerRadius !== figma.mixed) {\n frame.cornerRadius = s.cornerRadius;\n } else if (\"topLeftRadius\" in s) {\n frame.topLeftRadius = s.topLeftRadius;\n frame.topRightRadius = s.topRightRadius;\n frame.bottomRightRadius = s.bottomRightRadius;\n frame.bottomLeftRadius = s.bottomLeftRadius;\n }\n if (s.effects) frame.effects = s.effects;\n if (s.blendMode) frame.blendMode = s.blendMode;\n frame.clipsContent = true;\n\n // Insert frame at the shape's position in parent\n const shapeIndex = (parent as any).children.indexOf(shape);\n (parent as any).insertChild(shapeIndex, frame);\n\n // Adopt overlapping siblings if requested (default: true)\n const adoptChildren = p.adoptChildren !== false;\n const adopted: string[] = [];\n if (adoptChildren) {\n const shapeBounds = { x: s.x, y: s.y, width: s.width, height: s.height };\n const siblings = (parent as any).children as SceneNode[];\n const toAdopt: SceneNode[] = [];\n for (const sib of siblings) {\n if (sib.id === shape.id || sib.id === frame.id) continue;\n if (!(\"x\" in sib) || !(\"width\" in sib)) continue;\n const sx = (sib as any).x, sy = (sib as any).y;\n const sw = (sib as any).width, sh = (sib as any).height;\n if (sx >= shapeBounds.x && sy >= shapeBounds.y\n && sx + sw <= shapeBounds.x + shapeBounds.width\n && sy + sh <= shapeBounds.y + shapeBounds.height) {\n toAdopt.push(sib);\n }\n }\n for (const child of toAdopt) {\n (child as any).x -= frame.x;\n (child as any).y -= frame.y;\n frame.appendChild(child);\n adopted.push(child.id);\n }\n }\n\n shape.remove();\n return { id: frame.id, adoptedChildren: adopted };\n}\n\nexport const figmaHandlers: Record<string, (params: any) => Promise<any>> = {\n lint_node: lintNodeHandler,\n lint_fix_autolayout: (p) => batchHandler(p, fixAutolayoutSingle),\n lint_fix_replace_shape_with_frame: (p) => batchHandler(p, fixShapeToFrameSingle),\n};\n","import type { McpServer, SendCommandFn } from \"./types\";\nimport { mcpJson, mcpError } from \"./types\";\n\n// ─── MCP Registration ────────────────────────────────────────────\n\nexport function registerMcpTools(server: McpServer, sendCommand: SendCommandFn) {\n server.tool(\n \"ping\",\n \"Verify end-to-end connection to Figma. Call this right after join_channel. Returns { status: 'pong', documentName, currentPage } if the full chain (MCP → relay → plugin → Figma) is working. If this times out, the Figma plugin is not connected — ask the user to check the plugin window for the correct port and channel name.\",\n {},\n async () => {\n try {\n return mcpJson(await sendCommand(\"ping\", {}, 5000));\n } catch (e) {\n return mcpError(\"Connection verification failed\", e);\n }\n }\n );\n}\n\n// ─── Figma Handlers ──────────────────────────────────────────────\n\nasync function ping() {\n return {\n status: \"pong\",\n documentName: figma.root.name,\n currentPage: figma.currentPage.name,\n timestamp: Date.now(),\n };\n}\n\nexport const figmaHandlers: Record<string, (params: any) => Promise<any>> = {\n ping,\n};\n","import type { McpServer } from \"./types\";\n\nexport function registerPrompts(server: McpServer) {\n server.prompt(\n \"design_strategy\",\n \"Best practices for working with Figma designs\",\n () => ({\n messages: [{\n role: \"assistant\" as const,\n content: {\n type: \"text\" as const,\n text: `When working with Figma designs, follow these best practices:\n\n1. Understand Before Creating:\n - Use get_document_info() to see pages and current page\n - Use get_styles() and get_local_variables() to discover existing design tokens\n - Plan layout hierarchy before creating elements\n\n2. Use Design Tokens — Never Hardcode:\n - Colors: use fillStyleName/strokeStyleName (paint styles) or fillVariableId/strokeVariableId (variables)\n - Text: use textStyleName to apply text styles that control font size, weight, and line height together\n - Effects: use effectStyleName to apply shadow/blur styles\n - Only use raw fillColor/fontColor for one-off values not in the design system\n\n3. Auto-Layout First:\n - Use create_frame() with layoutMode: \"VERTICAL\" or \"HORIZONTAL\" for every container\n - Set itemSpacing, padding, and alignment at creation time\n - Use layoutSizingHorizontal/Vertical: \"FILL\" for responsive children\n - Avoid absolute positioning — let auto-layout handle spacing\n\n4. Naming Conventions:\n - Use descriptive, semantic names for all elements\n - Name components with Property=Value pattern (e.g. \"Size=Small\") before combine_as_variants\n\n5. Variable Modes:\n - Use set_explicit_variable_mode() to pin a frame to a specific mode (e.g. Dark)\n - Use get_node_variables() to verify which variables are bound to a node\n\n6. Quality Check — Run Lint:\n - After building a section, run lint_node() to catch common issues:\n * hardcoded-color: fills/strokes not using styles or variables\n * no-text-style: text without a text style applied\n * no-autolayout: frames with children but no auto-layout\n * default-name: nodes still named \"Frame\", \"Rectangle\", etc.\n - Use lint_fix_autolayout() and lint_fix_replace_shape_with_frame() to auto-fix\n - Lint early and often — it is cheaper to fix issues during creation than after`,\n },\n }],\n description: \"Best practices for working with Figma designs\",\n })\n );\n\n server.prompt(\n \"read_design_strategy\",\n \"Best practices for reading Figma designs\",\n () => ({\n messages: [{\n role: \"assistant\" as const,\n content: {\n type: \"text\" as const,\n text: `When reading Figma designs, follow these best practices:\n\n1. Start with selection:\n - First use read_my_design() to understand the current selection\n - If no selection ask user to select single or multiple nodes\n`,\n },\n }],\n description: \"Best practices for reading Figma designs\",\n })\n );\n\n server.prompt(\n \"text_replacement_strategy\",\n \"Systematic approach for replacing text in Figma designs\",\n () => ({\n messages: [{\n role: \"assistant\" as const,\n content: {\n type: \"text\" as const,\n 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\\`\\`\\`\nscan_text_nodes(nodeId: \"node-id\")\nget_node_info(nodeId: \"node-id\") // 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\nclone_node(nodeId: \"selected-node-id\", x: [new-x], y: [new-y])\n\n// Replace text chunk by chunk\nset_text_content(\n items: [\n { nodeId: \"node-id-1\", text: \"New text 1\" },\n // More nodes in this chunk...\n ]\n)\n\n// Verify chunk with small, targeted image exports\nexport_node_as_image(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—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.`,\n },\n }],\n description: \"Systematic approach for replacing text in Figma designs\",\n })\n );\n\n server.prompt(\n \"swap_overrides_instances\",\n \"Guide to swap instance overrides between instances\",\n () => ({\n messages: [{\n role: \"assistant\" as const,\n content: {\n type: \"text\" as const,\n 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 \\`get_selection()\\` to identify selected instances\n- Use \\`search_nodes(types: [\"INSTANCE\"])\\` to find instances on the page\n\n### 2. Extract Source Overrides\n- \\`get_instance_overrides(nodeId: \"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 \\`set_text_content\\` on matching child node IDs\n- For style overrides: use \\`set_fill_color\\`, \\`apply_style_to_node\\`, etc.\n- Match children by name path — source and target instances share the same internal structure\n\n### 4. Verify\n- \\`get_node_info(nodeId, depth: 1)\\` on target instances\n- \\`export_node_as_image\\` for visual verification`,\n },\n }],\n description: \"Strategy for transferring overrides between component instances in Figma\",\n })\n );\n}\n","import type { McpServer, SendCommandFn } from \"./types\";\n\n// Import all tool modules\nimport { registerMcpTools as registerDocument } from \"./document\";\nimport { registerMcpTools as registerSelection } from \"./selection\";\nimport { registerMcpTools as registerNodeInfo } from \"./node-info\";\nimport { registerMcpTools as registerCreateShape } from \"./create-shape\";\nimport { registerMcpTools as registerCreateFrame } from \"./create-frame\";\nimport { registerMcpTools as registerCreateText } from \"./create-text\";\nimport { registerMcpTools as registerModifyNode } from \"./modify-node\";\nimport { registerMcpTools as registerFillStroke } from \"./fill-stroke\";\nimport { registerMcpTools as registerUpdateFrame } from \"./update-frame\";\nimport { registerMcpTools as registerEffects } from \"./effects\";\nimport { registerMcpTools as registerText } from \"./text\";\nimport { registerMcpTools as registerFonts } from \"./fonts\";\nimport { registerMcpTools as registerComponents } from \"./components\";\nimport { registerMcpTools as registerStyles } from \"./styles\";\nimport { registerMcpTools as registerVariables } from \"./variables\";\nimport { registerMcpTools as registerLint } from \"./lint\";\nimport { registerMcpTools as registerConnection } from \"./connection\";\nimport { registerPrompts } from \"./prompts\";\n\n/** Register all MCP tools and prompts on the server */\nexport function registerAllTools(server: McpServer, sendCommand: SendCommandFn) {\n registerDocument(server, sendCommand);\n registerSelection(server, sendCommand);\n registerNodeInfo(server, sendCommand);\n registerCreateShape(server, sendCommand);\n registerCreateFrame(server, sendCommand);\n registerCreateText(server, sendCommand);\n registerModifyNode(server, sendCommand);\n registerFillStroke(server, sendCommand);\n registerUpdateFrame(server, sendCommand);\n registerEffects(server, sendCommand);\n registerText(server, sendCommand);\n registerFonts(server, sendCommand);\n registerComponents(server, sendCommand);\n registerStyles(server, sendCommand);\n registerVariables(server, sendCommand);\n registerLint(server, sendCommand);\n registerConnection(server, sendCommand);\n registerPrompts(server);\n}\n"],"mappings":"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","names":["z","server","z","z","registerMcpTools","server","z","depth","nodeIds","z","registerMcpTools","server","z","nodeId","z","z","z","z","registerMcpTools","server","z","z","registerMcpTools","server","z","z","registerMcpTools","server","z","z","registerMcpTools","server","z","z","registerMcpTools","server","z","z","registerMcpTools","server","z","z","registerMcpTools","server","z","z","registerMcpTools","server","z","registerMcpTools","server","z","z","z","registerMcpTools","server","nodeId","z","z","registerMcpTools","server","z","z","registerMcpTools","server","nodeId","z","z","registerMcpTools","server","registerMcpTools","server","server","server","registerMcpTools","z"]}