Figma MCP + Codex:从设计稿到结构化JSON数据的自动化提取实战

📅 发布时间:2026/8/25 19:10:21
Figma MCP + Codex:从设计稿到结构化JSON数据的自动化提取实战
这类工具最值得先看的不是功能列表而是能不能在普通开发环境里稳定跑起来以及它到底解决了设计稿到代码转换中的哪个具体痛点。Figma MCP 配合 Codex 这个组合核心价值在于它能让你用代码的方式直接、批量地读取 Figma 文件里的设计节点并输出结构化的 JSON 数据。这比手动在 Figma 界面上一个个复制属性或者依赖一些不稳定的截图识别工具要可靠得多。它适合两类人一是前端开发尤其是需要频繁从设计稿中提取颜色、尺寸、间距、文本样式等 Token 的开发二是希望将设计系统资产如组件库自动化同步到代码库的团队。最关键的能力是“一次拿全”这意味着你可以通过一个脚本把整个画板、页面甚至文件的设计数据按你需要的层级和格式完整地抓取下来为后续的代码生成、样式同步或设计走查提供数据基础。下面我会按实际落地顺序拆一遍从环境准备、权限获取、脚本编写到数据解析和常见坑点。1. 先搞清楚 MCP 和 Codex 在这里分别扮演什么角色很多人看到“Figma MCP”和“Codex”这两个词容易混淆以为是一个东西。其实它们是协作关系分工明确。1.1 MCP负责与 Figma 官方 API “握手”MCP 在这里通常指的是一个基于 Figma 官方 REST API 封装的客户端或 SDK。它的核心工作是认证帮你处理 Personal Access Token 或 OAuth 流程获得访问 Figma 文件的权限。请求构造将你想要获取节点、文件信息等操作转换成 Figma API 能理解的 HTTP 请求。响应处理接收 Figma API 返回的原始 JSON 数据可能做一些初步的解析或错误处理。简单说MCP 是你的脚本和 Figma 服务器之间的“翻译官”和“信使”。没有它你就得自己从头写 HTTP 客户端、处理认证、管理请求频率限制非常繁琐。1.2 Codex负责执行你的“读取”逻辑并输出 JSON这里的 Codex 不是指 OpenAI 的 Codex而是在这个上下文中指代你编写的、用于控制整个读取流程的脚本或程序可能是 Node.js、Python 等。它负责流程控制告诉 MCP 要去读取哪个 Figma 文件的哪个节点通过文件 Key 和节点 ID。数据遍历与提取Figma 的节点树可能很复杂Codex 脚本需要决定是读取整个画板还是只读取特定类型的图层如矩形、文本。结构重塑将 MCP 返回的原始 API 数据过滤、转换、组装成你最终想要的 JSON 结构。比如你可能只关心矩形的width,height,fills填充色而忽略其他属性。输出将处理好的数据写入到一个.json文件中或者直接输出到控制台供其他程序使用。所以整个流程是你的 Codex 脚本 - 调用 MCP 客户端 - MCP 向 Figma API 发起请求 - 获取原始数据 - MCP 返回给 Codex - Codex 处理并输出最终 JSON。2. 动手前的环境与权限准备在写任何代码之前先把这两件事搞定能避免 80% 的“为什么跑不起来”的问题。2.1 获取 Figma Personal Access Token这是访问 Figma API 的钥匙。登录你的 Figma 账号。点击右上角头像进入 “Settings”。在左侧找到 “Account” 向下滚动找到 “Personal access tokens” 部分。点击 “Create new token” 给它起个名字比如 “Local Dev MCP”。创建后立即复制生成的 Token 字符串。这个页面关闭后就再也看不到了只能重新生成。注意这个 Token 拥有你账户的权限务必像保管密码一样保管它不要提交到公开的代码仓库。通常我们会把它放在环境变量里。2.2 获取目标 Figma 文件的 Key 和节点 ID你需要知道要读取哪个文件以及文件里的哪个部分。文件 Key打开你的 Figma 文件浏览器地址栏的 URL 看起来像https://www.figma.com/file/FILE_KEY/...。其中FILE_KEY就是你要的。节点 ID如果你想读取特定画板或组件需要它的 ID。在 Figma 界面中选中一个画板或图层在右侧 “Properties” 面板最下方可以看到 “ID” 字段。如果你想读取整个文件可以使用根节点的 ID通常是0:0或类似格式但更常见的做法是在 API 请求中不指定节点 ID以获取整个文件树。2.3 本地开发环境准备以最常用的 Node.js 环境为例确保安装了 Node.js建议 LTS 版本和 npm/yarn/pnpm。创建一个新的项目目录。初始化项目并安装必要的依赖。这里的关键是选择一个靠谱的 Figma API 客户端库它本质上就是你的“MCP”。社区流行的有figma-api和figma-js。这里以figma-js为例mkdir figma-json-extractor cd figma-json-extractor npm init -y npm install figma-js dotenvdotenv用来管理环境变量安全地加载你的 Figma Token。3. 从单文件读取到结构化 JSON 输出的完整流程我们从一个最简单的脚本开始目标是读取一个 Figma 文件并将所有画板Frame的基本信息和其中的矩形元素提取出来。3.1 基础脚本连接并获取原始文件数据首先在项目根目录创建.env文件放入你的 TokenFIGMA_PERSONAL_ACCESS_TOKEN你的Token然后创建一个index.js文件require(dotenv).config(); const { Figma } require(figma-js); // 1. 初始化客户端 (MCP的核心作用) const client Figma.Client({ personalAccessToken: process.env.FIGMA_PERSONAL_ACCESS_TOKEN, }); // 2. 你的 Figma 文件 Key const FILE_KEY 你的文件Key; async function getFigmaFile() { try { // 3. 调用 API 获取文件数据 (MCP发起请求) const { data } await client.file(FILE_KEY); console.log(文件名称:, data.name); console.log(文档节点类型:, data.document.type); // 此时 data.document 包含了整个文件的节点树 return data.document; } catch (error) { console.error(获取 Figma 文件失败:, error.message); if (error.response) { console.error(API 响应状态:, error.response.status); console.error(API 响应数据:, error.response.data); } } } getFigmaFile();运行node index.js。如果一切正常你会看到文件名称和文档类型。这说明你的 MCPfigma-js库已经成功连接并拿到了数据。如果报错如 403、404请回头检查 Token 是否正确、是否有文件访问权限、文件 Key 是否正确。3.2 编写 Codex 逻辑遍历节点并提取所需属性现在我们给脚本加上数据处理逻辑这就是 Codex 的工作。我们修改getFigmaFile函数增加一个递归遍历节点的函数async function getFigmaFile() { try { const { data } await client.file(FILE_KEY); const documentNode data.document; // 最终要输出的结构化数据 const extractedData { file_name: data.name, last_modified: data.lastModified, canvases: [], // 存放所有画板 }; // 递归遍历函数 function traverseNode(node, parentCanvas null) { // 定义我们关心的节点类型和要提取的属性 switch (node.type) { case CANVAS: // Figma API 中画板是 CANVAS 类型 const canvas { id: node.id, name: node.name, children: [], }; extractedData.canvases.push(canvas); // 继续遍历画板下的子节点并传入当前画板作为父级 if (node.children) { node.children.forEach(child traverseNode(child, canvas)); } break; case FRAME: // 帧/画板有时也在 CANVAS 下 case GROUP: // 如果是组或帧继续向下遍历 if (node.children) { const container { id: node.id, name: node.name, type: node.type, children: [], }; if (parentCanvas) { parentCanvas.children.push(container); } node.children.forEach(child traverseNode(child, container)); } break; case RECTANGLE: case ELLIPSE: case VECTOR: // 提取形状元素 const shape { id: node.id, name: node.name, type: node.type, bounding_box: { x: node.absoluteBoundingBox?.x, y: node.absoluteBoundingBox?.y, width: node.absoluteBoundingBox?.width, height: node.absoluteBoundingBox?.height, }, // 提取填充色取第一个填充可能是纯色 fills: node.fills?.[0]?.color ? { r: node.fills[0].color.r, g: node.fills[0].color.g, b: node.fills[0].color.b, a: node.fills[0].color.a, } : null, // 提取描边 strokes: node.strokes, cornerRadius: node.cornerRadius, }; if (parentCanvas) { // 找到最直接的父容器可能是FRAME, GROUP, 或CANVAS let targetParent parentCanvas; while (targetParent !targetParent.children) { // 向上查找直到找到有children属性的容器 // 这里简化处理实际可能需要更精确的层级追踪 } if (targetParent targetParent.children) { targetParent.children.push(shape); } } break; case TEXT: // 提取文本元素 const text { id: node.id, name: node.name, type: TEXT, characters: node.characters, style: { font_family: node.style?.fontFamily, font_weight: node.style?.fontWeight, font_size: node.style?.fontSize, line_height: node.style?.lineHeightPx, text_align: node.style?.textAlignHorizontal, }, bounding_box: { x: node.absoluteBoundingBox?.x, y: node.absoluteBoundingBox?.y, width: node.absoluteBoundingBox?.width, height: node.absoluteBoundingBox?.height, }, color: node.fills?.[0]?.color, }; if (parentCanvas parentCanvas.children) { parentCanvas.children.push(text); } break; default: // 对于其他类型节点可以选择忽略或继续遍历其子节点 if (node.children) { node.children.forEach(child traverseNode(child, parentCanvas)); } break; } } // 开始遍历文档根节点的子节点通常是 CANVAS if (documentNode.children) { documentNode.children.forEach(child traverseNode(child)); } // 4. 输出结构化 JSON const fs require(fs); fs.writeFileSync( output_${FILE_KEY}.json, JSON.stringify(extractedData, null, 2) // 美化输出缩进2空格 ); console.log(✅ 数据已成功提取并保存到 output_${FILE_KEY}.json); console.log( 共提取 ${extractedData.canvases.length} 个画板。); // 也可以简单打印到控制台看看 // console.log(JSON.stringify(extractedData, null, 2)); } catch (error) { console.error(处理失败:, error); } }这个脚本做了几件事初始化与请求通过 MCP (figma-js) 获取数据。遍历与筛选递归遍历整个节点树只挑选我们关心的节点类型CANVAS,RECTANGLE,TEXT等。属性映射从 Figma 复杂的节点对象中提取出前端开发更关心的属性如尺寸、颜色、字体。结构重组将数据重新组织成{ file_name, canvases: [ { id, name, children: [ ... ] } ] }这样的层级结构。输出 JSON将最终结构写入文件。运行后你会得到一个output_FILE_KEY.json文件里面就是“一次拿全”的结构化设计数据。4. 处理复杂场景与提升可靠性单文件读取只是开始。实际项目中你会遇到更复杂的需求。4.1 处理大型文件与分页Figma API 对单个请求返回的数据量有限制。如果文件非常大返回的节点树可能会被截断。此时你需要使用nodes参数进行分页查询。思路不要一次性请求整个文件。先通过client.fileNodes(FILE_KEY, [nodeId1, nodeId2, ...])获取你关心的特定节点比如首页画板的 ID。做法可以先获取文件的“版本”或“组件列表”拿到关键节点的 ID再分批请求其详情。figma-js库的client.file方法其实已经处理了部分分页但对于巨型文件主动管理节点 ID 列表更可靠。4.2 提取设计 Token颜色、字体、间距这是前端提效的核心。上面的示例只提取了元素的直接属性。要提取 Token需要更智能的遍历颜色 Token遍历所有节点的fills,strokes,effects属性收集所有唯一的颜色值RGBA并尝试根据图层命名如Primary/500,Text/Secondary进行归类。字体样式 Token遍历所有TEXT节点提取fontFamily,fontWeight,fontSize,lineHeightPx等组合成唯一的字体样式对象并关联到文本图层的样式名如果设计师使用了样式。间距 Token这更复杂通常需要通过计算兄弟节点或父子节点的相对位置x,y差值来推断常用的间距值如 4, 8, 16, 24, 32...。可以结合图层命名如Spacing-16来辅助识别。一个提取颜色 Token 的简化示例const colorTokens new Map(); // 用 Map 去重 function extractColors(node) { // 处理填充色 if (node.fills Array.isArray(node.fills)) { node.fills.forEach(fill { if (fill.color) { const key ${fill.color.r},${fill.color.g},${fill.color.b},${fill.color.a}; if (!colorTokens.has(key)) { colorTokens.set(key, { value: fill.color, // 尝试从节点名或父节点名推断 Token 名 suggestedName: node.name.includes(/) ? node.name.split(/).pop() : null, sourceNodeId: node.id, }); } } }); } // 递归处理子节点 if (node.children) { node.children.forEach(child extractColors(child)); } } // 在获取文件数据后调用 extractColors(documentNode); console.log(提取到的唯一颜色数量:, colorTokens.size); // 可以将 colorTokens 输出为 JSON4.3 错误处理与重试机制网络请求可能失败API 也有速率限制。生产级脚本必须考虑这些。速率限制Figma API 有请求频率限制。在循环或批量请求时需要在请求间加入延迟例如使用setTimeout或async/await配合sleep函数。错误重试对于网络超时或 5xx 服务器错误可以实现简单的重试逻辑。增量更新如果你的目标是同步设计系统可以记录上次同步的文件版本号只获取版本变化的节点而不是每次都全量拉取。一个简单的带延迟和重试的请求示例async function fetchWithRetry(fileKey, nodeIds, retries 3, delay 1000) { for (let i 0; i retries; i) { try { const { data } await client.fileNodes(fileKey, nodeIds); return data; } catch (error) { if (error.response error.response.status 500 i retries - 1) { console.warn(请求失败${delay}ms后重试 (${i 1}/${retries})...); await new Promise(resolve setTimeout(resolve, delay)); delay * 2; // 指数退避 } else { throw error; // 重试次数用完或非5xx错误直接抛出 } } } }5. 将提取的 JSON 集成到前端工作流拿到 JSON 不是终点让它产生价值才是。5.1 生成 CSS/SCSS 变量或 JS 常量你可以写一个后处理脚本读取上一步输出的colorTokens.json然后生成一个design-tokens.scss文件const colorTokens require(./output_color_tokens.json); const fs require(fs); let scssContent // Auto-generated design tokens from Figma\n\n; colorTokens.forEach((token, key) { const { r, g, b, a } token.value; const name token.suggestedName || color-${key.replace(/,/g, -)}; scssContent $${name}: rgba(${Math.round(r * 255)}, ${Math.round(g * 255)}, ${Math.round(b * 255)}, ${a});\n; }); fs.writeFileSync(src/styles/_design-tokens.scss, scssContent); console.log(✅ SCSS 变量文件已生成。);5.2 与样式检查或代码生成工具结合样式检查在 CI/CD 流程中运行你的提取脚本将得到的 JSON 与代码库中定义的样式常量进行对比如果发现不一致比如设计稿颜色更新了但代码没更新则发出警告。代码生成对于简单的 UI 组件可以根据矩形、文本的位置和样式信息尝试生成基础的 HTML 和 CSS 骨架代码。虽然无法 100% 准确但对于标准化高的组件库可以大幅减少重复劳动。5.3 注意事项与排查清单当你发现脚本不工作或数据不对时按这个顺序排查Token 与权限Figma Personal Access Token 是否有效且未过期该 Token 是否有权限访问目标文件文件是否在团队项目中Token 所属账号是否是该团队成员文件与节点 ID文件 Key 是否正确复制时是否带了多余字符如果要获取特定节点节点 ID 是否正确是否已经通过client.file确认了节点 ID 的层次结构网络与 API 限制是否触发了 API 速率限制检查错误响应中的x-ratelimit-*头信息。本地网络是否能正常访问api.figma.com数据解析逻辑Figma API 的响应结构是否和你代码中访问的属性路径一致API 版本更新可能导致字段变化。最可靠的方法是在脚本中先console.log(JSON.stringify(data.document, null, 2))打印出完整的原始响应对照着写解析逻辑。你的遍历逻辑是否覆盖了所有需要的节点类型FRAME、GROUP、INSTANCE组件实例等都需要考虑。颜色值fills可能是一个数组且可能是图片填充 (type: IMAGE) 或渐变填充 (type: GRADIENT_...)你的代码是否处理了这些情况输出与集成输出的 JSON 文件路径是否正确是否有写入权限生成的 CSS/JS 文件格式是否符合你项目的编码规范我个人的经验是第一次跑通后把核心的“连接-获取-遍历-输出”流程封装成一个可靠的函数或模块。后续不同的提取需求如只提颜色、只提文本样式、提取组件结构就只是编写不同的“遍历器”(Traversal) 和“转换器”(Transformer) 的问题。这样你的 Figma MCP Codex 方案就从一个一次性脚本变成了一个可维护、可扩展的前端设计资产同步管道。