convert-figma-to-code
Converts a Figma node URL to a code block by fetching the node and its rendered image from the Figma API.
Instructions
Fetches a Figma node and its rendered image from the Figma API and converts it to a code block. Requires FIGMA_ACCESS_TOKEN environment variable to be set.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| figmaNodeUrl | Yes | The URL of the Figma node (e.g., https://www.figma.com/design/fileKey/fileName?node-id=123-456) |
Implementation Reference
- src/index.ts:675-1049 (registration)The 'convert-figma-to-code' tool is registered using server.tool() on line 675. This is where the tool name, description, Zod schema (figmaNodeUrl), and handler are all defined together.
server.tool( 'convert-figma-to-code', 'Fetches a Figma node and its rendered image from the Figma API and converts it to a code block. Requires FIGMA_ACCESS_TOKEN environment variable to be set.', { figmaNodeUrl: z.string().describe('The URL of the Figma node (e.g., https://www.figma.com/design/fileKey/fileName?node-id=123-456)'), }, async ({ figmaNodeUrl }): Promise<CallToolResult> => { try { // Get Figma access token from environment variables const figmaAccessToken = process.env.FIGMA_ACCESS_TOKEN; if (!figmaAccessToken) { return { content: [ { type: 'text', text: `Error: FIGMA_ACCESS_TOKEN environment variable is not set. To use this tool, you need to: 1. Generate a Personal Access Token from Figma: - Go to Figma > Settings > Account > Personal access tokens - Generate a new token 2. Set the FIGMA_ACCESS_TOKEN environment variable with your token Example for your MCP config: { "env": { "FIGMA_ACCESS_TOKEN": "your-personal-access-token" } }`, }, ], }; } // Parse Figma URL to extract fileKey and nodeId // URL formats: // https://www.figma.com/file/{fileKey}/{fileName}?node-id={nodeId} // https://www.figma.com/design/{fileKey}/{fileName}?node-id={nodeId} const urlPattern = /figma\.com\/(file|design)\/([a-zA-Z0-9]+)(?:\/[^?]*)?(?:\?.*node-id=([^&]+))?/; const match = figmaNodeUrl.match(urlPattern); if (!match) { return { content: [ { type: 'text', text: `Error: Invalid Figma URL format. Expected formats: - https://www.figma.com/file/{fileKey}/{fileName}?node-id={nodeId} - https://www.figma.com/design/{fileKey}/{fileName}?node-id={nodeId} Provided URL: ${figmaNodeUrl}`, }, ], }; } const fileKey = match[2]; const nodeId = match[3] ? decodeURIComponent(match[3]) : null; if (!nodeId) { return { content: [ { type: 'text', text: `Error: No node-id found in the Figma URL. Please make sure your URL includes a node-id parameter. Example: https://www.figma.com/design/${fileKey}/FileName?node-id=123-456 Provided URL: ${figmaNodeUrl}`, }, ], }; } // API headers const headers = { 'X-Figma-Token': figmaAccessToken, }; // Fetch node data const nodeApiUrl = `https://api.figma.com/v1/files/${fileKey}/nodes?ids=${encodeURIComponent(nodeId)}`; const nodeResponse = await fetch(nodeApiUrl, { headers }); if (!nodeResponse.ok) { const errorText = await nodeResponse.text(); return { content: [ { type: 'text', text: `Error fetching Figma node data: Status: ${nodeResponse.status} ${nodeResponse.statusText} Response: ${errorText} API URL: ${nodeApiUrl}`, }, ], }; } const nodeData = await nodeResponse.json(); // Fetch node image const imageApiUrl = `https://api.figma.com/v1/images/${fileKey}?ids=${encodeURIComponent(nodeId)}&scale=2`; const imageResponse = await fetch(imageApiUrl, { headers }); if (!imageResponse.ok) { const errorText = await imageResponse.text(); return { content: [ { type: 'text', text: `Error fetching Figma node image: Status: ${imageResponse.status} ${imageResponse.statusText} Response: ${errorText} API URL: ${imageApiUrl} Node data was retrieved successfully.`, }, ], }; } const imageData = await imageResponse.json(); // Extract the image URL from the response const imageUrl = imageData.images?.[nodeId] || imageData.images?.[Object.keys(imageData.images)[0]] || null; // Helper function to simplify Figma node data - extracts only essential info for code conversion const simplifyNode = (node: any): any => { if (!node) return null; const simplified: any = { type: node.type, name: node.name, }; // Add dimensions if available if (node.absoluteBoundingBox) { simplified.size = { width: Math.round(node.absoluteBoundingBox.width), height: Math.round(node.absoluteBoundingBox.height), }; } // Add layout info for frames if (node.layoutMode) { simplified.layout = { mode: node.layoutMode, // HORIZONTAL, VERTICAL, NONE padding: node.paddingLeft || node.paddingTop ? { top: node.paddingTop, right: node.paddingRight, bottom: node.paddingBottom, left: node.paddingLeft, } : undefined, gap: node.itemSpacing, align: node.primaryAxisAlignItems, justify: node.counterAxisAlignItems, }; } // Add corner radius if (node.cornerRadius) { simplified.borderRadius = node.cornerRadius; } else if (node.rectangleCornerRadii) { simplified.borderRadius = node.rectangleCornerRadii; } // Add fills (background colors) if (node.fills && node.fills.length > 0) { simplified.fills = node.fills .filter((fill: any) => fill.visible !== false) .map((fill: any) => ({ type: fill.type, color: fill.color ? { r: Math.round(fill.color.r * 255), g: Math.round(fill.color.g * 255), b: Math.round(fill.color.b * 255), a: fill.color.a !== undefined ? Math.round(fill.color.a * 100) / 100 : 1, } : undefined, opacity: fill.opacity, })); } // Add strokes (borders) if (node.strokes && node.strokes.length > 0) { simplified.strokes = node.strokes .filter((stroke: any) => stroke.visible !== false) .map((stroke: any) => ({ type: stroke.type, color: stroke.color ? { r: Math.round(stroke.color.r * 255), g: Math.round(stroke.color.g * 255), b: Math.round(stroke.color.b * 255), } : undefined, })); if (node.strokeWeight) { simplified.strokeWeight = node.strokeWeight; } } // Add effects (shadows, blur) if (node.effects && node.effects.length > 0) { simplified.effects = node.effects .filter((effect: any) => effect.visible !== false) .map((effect: any) => ({ type: effect.type, radius: effect.radius, offset: effect.offset, color: effect.color ? { r: Math.round(effect.color.r * 255), g: Math.round(effect.color.g * 255), b: Math.round(effect.color.b * 255), a: Math.round(effect.color.a * 100) / 100, } : undefined, })); } // Add text-specific properties if (node.type === 'TEXT') { simplified.text = node.characters; if (node.style) { simplified.textStyle = { fontFamily: node.style.fontFamily, fontWeight: node.style.fontWeight, fontSize: node.style.fontSize, lineHeight: node.style.lineHeightPx, letterSpacing: node.style.letterSpacing, textAlign: node.style.textAlignHorizontal, }; } } // Recursively process children if (node.children && node.children.length > 0) { simplified.children = node.children.map(simplifyNode).filter(Boolean); } return simplified; }; // Simplify the node data const simplifiedNodeData = nodeData.nodes ? Object.keys(nodeData.nodes).reduce((acc: any, key: string) => { const node = nodeData.nodes[key]; acc[key] = { document: simplifyNode(node.document), components: node.components ? Object.keys(node.components).length + ' components' : undefined, styles: node.styles ? Object.keys(node.styles).length + ' styles' : undefined, }; return acc; }, {}) : simplifyNode(nodeData); // Return combined result with AI instructions return { content: [ { type: 'text', text: `# Figma to Code Conversion Task ## Instructions for AI You have received a Figma design that needs to be converted to code. Follow these steps: ### Step 1: Analyze the Design Look at the rendered image below and the node structure data to understand: - The layout and component hierarchy - Colors, typography, and spacing used - Interactive elements (buttons, inputs, links, etc.) - Component patterns that match Flowbite components ### Step 2: Use Flowbite MCP Resources Before writing code, fetch the relevant Flowbite component documentation using the MCP resources. ### Step 3: Write the Code Generate clean, semantic HTML with Tailwind CSS classes following these guidelines: 1. **Use Flowbite Components**: Match the Figma design to Flowbite components whenever possible 2. **Flowbite variables** - Use Flowbite variable utility classes when possible (e.g. text-brand, rounded-base, etc.) 3. **Tailwind CSS Classes**: Use Tailwind utility classes for styling 4. **Responsive Design**: Include responsive breakpoints (sm:, md:, lg:, xl:) 5. **Semantic HTML**: Use proper HTML5 semantic elements 6. **Accessibility**: Include ARIA attributes and proper alt texts 7. **Match Colors**: Use Flowbite variables and secondly Tailwind color classes that best match the Figma design colors 8. **Match Spacing**: Use Tailwind spacing utilities (p-*, m-*, gap-*) to match the design ### Step 4: Output Format - IMPORTANT **⚠️ ONLY output the component code block - DO NOT include:** - \`<!DOCTYPE html>\` - \`<html>\`, \`</html>\` tags - \`<head>\`, \`</head>\` tags - \`<body>\`, \`</body>\` tags - \`<link>\` tags (CSS imports) - \`<script>\` tags (JS imports) - Any meta tags or document structure - ':dark' variant classes unless explicitly requested **✅ DO output:** - Only the component HTML markup itself - The actual UI component code that would go inside a \`<body>\` tag - Clean, well-formatted code block with proper indentation - Just the reusable component/section code - When possible use the "brand" variables from Flowbite instead of hardcoded colors from Tailwind CSS **Example of what to output:** \`\`\`html <div class="bg-neutral-primary-soft block max-w-sm p-6 border border-default rounded-base shadow-xs"> <h5 class="mb-3 text-2xl font-semibold tracking-tight text-heading leading-8">Noteworthy technology acquisitions 2021</h5> <p class="text-body mb-6">Here are the biggest technology acquisitions of 2025 so far, in reverse chronological order.</p> <a href="#" class="inline-flex items-center text-white bg-brand box-border border border-transparent hover:bg-brand-strong focus:ring-4 focus:ring-brand-medium shadow-xs font-medium leading-5 rounded-base text-sm px-4 py-2.5 focus:outline-none"> Read more <svg class="w-4 h-4 ms-1.5 rtl:rotate-180 -me-0.5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 12H5m14 0-4 4m4-4-4-4"/></svg> </a> </div> \`\`\` --- ## Figma Design Data ### File Information - **File Key**: ${fileKey} - **Node ID**: ${nodeId} - **Source URL**: ${figmaNodeUrl} ### Rendered Design Image ${imageUrl ? ` <img src="${imageUrl}"> **Direct Image URL**: ${imageUrl}` : '⚠️ No image URL available - analyze the node data structure below'} ### Node Structure Data (Simplified) The following JSON contains the essential Figma node structure for code conversion: \`\`\`json ${JSON.stringify(simplifiedNodeData, null, 2)} \`\`\` --- ## Now Convert This Design Based on the image and node data above: 1. Identify the UI components visible in the design 2. Fetch the relevant Flowbite component documentation from the MCP resources 3. Generate ONLY the component HTML/Tailwind CSS code (no document wrapper, no \`<html>\`, \`<head>\`, \`<body>\`, \`<link>\`, or \`<script>\` tags) 4. Ensure the code is production-ready with proper responsiveness and accessibility`, }, ], }; } catch (error) { console.error(`Error fetching Figma node: ${error}`); return { content: [ { type: 'text', text: `Error fetching Figma node: ${error instanceof Error ? error.message : String(error)} Please check: 1. Your FIGMA_ACCESS_TOKEN is valid 2. The Figma URL is correct 3. You have access to the Figma file`, }, ], }; } } ); - src/index.ts:681-1048 (handler)The handler function starting at line 681 executes the tool logic: parses the Figma URL, fetches node data and image from Figma API, simplifies the node structure, and returns a comprehensive prompt instructing an AI to convert the design to HTML/Tailwind CSS code using Flowbite components.
async ({ figmaNodeUrl }): Promise<CallToolResult> => { try { // Get Figma access token from environment variables const figmaAccessToken = process.env.FIGMA_ACCESS_TOKEN; if (!figmaAccessToken) { return { content: [ { type: 'text', text: `Error: FIGMA_ACCESS_TOKEN environment variable is not set. To use this tool, you need to: 1. Generate a Personal Access Token from Figma: - Go to Figma > Settings > Account > Personal access tokens - Generate a new token 2. Set the FIGMA_ACCESS_TOKEN environment variable with your token Example for your MCP config: { "env": { "FIGMA_ACCESS_TOKEN": "your-personal-access-token" } }`, }, ], }; } // Parse Figma URL to extract fileKey and nodeId // URL formats: // https://www.figma.com/file/{fileKey}/{fileName}?node-id={nodeId} // https://www.figma.com/design/{fileKey}/{fileName}?node-id={nodeId} const urlPattern = /figma\.com\/(file|design)\/([a-zA-Z0-9]+)(?:\/[^?]*)?(?:\?.*node-id=([^&]+))?/; const match = figmaNodeUrl.match(urlPattern); if (!match) { return { content: [ { type: 'text', text: `Error: Invalid Figma URL format. Expected formats: - https://www.figma.com/file/{fileKey}/{fileName}?node-id={nodeId} - https://www.figma.com/design/{fileKey}/{fileName}?node-id={nodeId} Provided URL: ${figmaNodeUrl}`, }, ], }; } const fileKey = match[2]; const nodeId = match[3] ? decodeURIComponent(match[3]) : null; if (!nodeId) { return { content: [ { type: 'text', text: `Error: No node-id found in the Figma URL. Please make sure your URL includes a node-id parameter. Example: https://www.figma.com/design/${fileKey}/FileName?node-id=123-456 Provided URL: ${figmaNodeUrl}`, }, ], }; } // API headers const headers = { 'X-Figma-Token': figmaAccessToken, }; // Fetch node data const nodeApiUrl = `https://api.figma.com/v1/files/${fileKey}/nodes?ids=${encodeURIComponent(nodeId)}`; const nodeResponse = await fetch(nodeApiUrl, { headers }); if (!nodeResponse.ok) { const errorText = await nodeResponse.text(); return { content: [ { type: 'text', text: `Error fetching Figma node data: Status: ${nodeResponse.status} ${nodeResponse.statusText} Response: ${errorText} API URL: ${nodeApiUrl}`, }, ], }; } const nodeData = await nodeResponse.json(); // Fetch node image const imageApiUrl = `https://api.figma.com/v1/images/${fileKey}?ids=${encodeURIComponent(nodeId)}&scale=2`; const imageResponse = await fetch(imageApiUrl, { headers }); if (!imageResponse.ok) { const errorText = await imageResponse.text(); return { content: [ { type: 'text', text: `Error fetching Figma node image: Status: ${imageResponse.status} ${imageResponse.statusText} Response: ${errorText} API URL: ${imageApiUrl} Node data was retrieved successfully.`, }, ], }; } const imageData = await imageResponse.json(); // Extract the image URL from the response const imageUrl = imageData.images?.[nodeId] || imageData.images?.[Object.keys(imageData.images)[0]] || null; // Helper function to simplify Figma node data - extracts only essential info for code conversion const simplifyNode = (node: any): any => { if (!node) return null; const simplified: any = { type: node.type, name: node.name, }; // Add dimensions if available if (node.absoluteBoundingBox) { simplified.size = { width: Math.round(node.absoluteBoundingBox.width), height: Math.round(node.absoluteBoundingBox.height), }; } // Add layout info for frames if (node.layoutMode) { simplified.layout = { mode: node.layoutMode, // HORIZONTAL, VERTICAL, NONE padding: node.paddingLeft || node.paddingTop ? { top: node.paddingTop, right: node.paddingRight, bottom: node.paddingBottom, left: node.paddingLeft, } : undefined, gap: node.itemSpacing, align: node.primaryAxisAlignItems, justify: node.counterAxisAlignItems, }; } // Add corner radius if (node.cornerRadius) { simplified.borderRadius = node.cornerRadius; } else if (node.rectangleCornerRadii) { simplified.borderRadius = node.rectangleCornerRadii; } // Add fills (background colors) if (node.fills && node.fills.length > 0) { simplified.fills = node.fills .filter((fill: any) => fill.visible !== false) .map((fill: any) => ({ type: fill.type, color: fill.color ? { r: Math.round(fill.color.r * 255), g: Math.round(fill.color.g * 255), b: Math.round(fill.color.b * 255), a: fill.color.a !== undefined ? Math.round(fill.color.a * 100) / 100 : 1, } : undefined, opacity: fill.opacity, })); } // Add strokes (borders) if (node.strokes && node.strokes.length > 0) { simplified.strokes = node.strokes .filter((stroke: any) => stroke.visible !== false) .map((stroke: any) => ({ type: stroke.type, color: stroke.color ? { r: Math.round(stroke.color.r * 255), g: Math.round(stroke.color.g * 255), b: Math.round(stroke.color.b * 255), } : undefined, })); if (node.strokeWeight) { simplified.strokeWeight = node.strokeWeight; } } // Add effects (shadows, blur) if (node.effects && node.effects.length > 0) { simplified.effects = node.effects .filter((effect: any) => effect.visible !== false) .map((effect: any) => ({ type: effect.type, radius: effect.radius, offset: effect.offset, color: effect.color ? { r: Math.round(effect.color.r * 255), g: Math.round(effect.color.g * 255), b: Math.round(effect.color.b * 255), a: Math.round(effect.color.a * 100) / 100, } : undefined, })); } // Add text-specific properties if (node.type === 'TEXT') { simplified.text = node.characters; if (node.style) { simplified.textStyle = { fontFamily: node.style.fontFamily, fontWeight: node.style.fontWeight, fontSize: node.style.fontSize, lineHeight: node.style.lineHeightPx, letterSpacing: node.style.letterSpacing, textAlign: node.style.textAlignHorizontal, }; } } // Recursively process children if (node.children && node.children.length > 0) { simplified.children = node.children.map(simplifyNode).filter(Boolean); } return simplified; }; // Simplify the node data const simplifiedNodeData = nodeData.nodes ? Object.keys(nodeData.nodes).reduce((acc: any, key: string) => { const node = nodeData.nodes[key]; acc[key] = { document: simplifyNode(node.document), components: node.components ? Object.keys(node.components).length + ' components' : undefined, styles: node.styles ? Object.keys(node.styles).length + ' styles' : undefined, }; return acc; }, {}) : simplifyNode(nodeData); // Return combined result with AI instructions return { content: [ { type: 'text', text: `# Figma to Code Conversion Task ## Instructions for AI You have received a Figma design that needs to be converted to code. Follow these steps: ### Step 1: Analyze the Design Look at the rendered image below and the node structure data to understand: - The layout and component hierarchy - Colors, typography, and spacing used - Interactive elements (buttons, inputs, links, etc.) - Component patterns that match Flowbite components ### Step 2: Use Flowbite MCP Resources Before writing code, fetch the relevant Flowbite component documentation using the MCP resources. ### Step 3: Write the Code Generate clean, semantic HTML with Tailwind CSS classes following these guidelines: 1. **Use Flowbite Components**: Match the Figma design to Flowbite components whenever possible 2. **Flowbite variables** - Use Flowbite variable utility classes when possible (e.g. text-brand, rounded-base, etc.) 3. **Tailwind CSS Classes**: Use Tailwind utility classes for styling 4. **Responsive Design**: Include responsive breakpoints (sm:, md:, lg:, xl:) 5. **Semantic HTML**: Use proper HTML5 semantic elements 6. **Accessibility**: Include ARIA attributes and proper alt texts 7. **Match Colors**: Use Flowbite variables and secondly Tailwind color classes that best match the Figma design colors 8. **Match Spacing**: Use Tailwind spacing utilities (p-*, m-*, gap-*) to match the design ### Step 4: Output Format - IMPORTANT **⚠️ ONLY output the component code block - DO NOT include:** - \`<!DOCTYPE html>\` - \`<html>\`, \`</html>\` tags - \`<head>\`, \`</head>\` tags - \`<body>\`, \`</body>\` tags - \`<link>\` tags (CSS imports) - \`<script>\` tags (JS imports) - Any meta tags or document structure - ':dark' variant classes unless explicitly requested **✅ DO output:** - Only the component HTML markup itself - The actual UI component code that would go inside a \`<body>\` tag - Clean, well-formatted code block with proper indentation - Just the reusable component/section code - When possible use the "brand" variables from Flowbite instead of hardcoded colors from Tailwind CSS **Example of what to output:** \`\`\`html <div class="bg-neutral-primary-soft block max-w-sm p-6 border border-default rounded-base shadow-xs"> <h5 class="mb-3 text-2xl font-semibold tracking-tight text-heading leading-8">Noteworthy technology acquisitions 2021</h5> <p class="text-body mb-6">Here are the biggest technology acquisitions of 2025 so far, in reverse chronological order.</p> <a href="#" class="inline-flex items-center text-white bg-brand box-border border border-transparent hover:bg-brand-strong focus:ring-4 focus:ring-brand-medium shadow-xs font-medium leading-5 rounded-base text-sm px-4 py-2.5 focus:outline-none"> Read more <svg class="w-4 h-4 ms-1.5 rtl:rotate-180 -me-0.5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 12H5m14 0-4 4m4-4-4-4"/></svg> </a> </div> \`\`\` --- ## Figma Design Data ### File Information - **File Key**: ${fileKey} - **Node ID**: ${nodeId} - **Source URL**: ${figmaNodeUrl} ### Rendered Design Image ${imageUrl ? ` <img src="${imageUrl}"> **Direct Image URL**: ${imageUrl}` : '⚠️ No image URL available - analyze the node data structure below'} ### Node Structure Data (Simplified) The following JSON contains the essential Figma node structure for code conversion: \`\`\`json ${JSON.stringify(simplifiedNodeData, null, 2)} \`\`\` --- ## Now Convert This Design Based on the image and node data above: 1. Identify the UI components visible in the design 2. Fetch the relevant Flowbite component documentation from the MCP resources 3. Generate ONLY the component HTML/Tailwind CSS code (no document wrapper, no \`<html>\`, \`<head>\`, \`<body>\`, \`<link>\`, or \`<script>\` tags) 4. Ensure the code is production-ready with proper responsiveness and accessibility`, }, ], }; } catch (error) { console.error(`Error fetching Figma node: ${error}`); return { content: [ { type: 'text', text: `Error fetching Figma node: ${error instanceof Error ? error.message : String(error)} Please check: 1. Your FIGMA_ACCESS_TOKEN is valid 2. The Figma URL is correct 3. You have access to the Figma file`, }, ], }; } } - src/index.ts:678-680 (schema)The input schema defines a single required parameter 'figmaNodeUrl' (a string describing the Figma node URL). It uses Zod for validation.
{ figmaNodeUrl: z.string().describe('The URL of the Figma node (e.g., https://www.figma.com/design/fileKey/fileName?node-id=123-456)'), },