Figma MCP + Codex:从设计稿到结构化JSON数据的自动化提取实战
2026/8/25 19:09:54 网站建设 项目流程

这类工具最值得先看的不是功能列表,而是能不能在普通开发环境里稳定跑起来,以及它到底解决了设计稿到代码转换中的哪个具体痛点。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 的钥匙。

  1. 登录你的 Figma 账号。
  2. 点击右上角头像,进入 “Settings”。
  3. 在左侧找到 “Account”, 向下滚动找到 “Personal access tokens” 部分。
  4. 点击 “Create new token”, 给它起个名字,比如 “Local Dev MCP”。
  5. 创建后,立即复制生成的 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 环境为例:

  1. 确保安装了 Node.js(建议 LTS 版本)和 npm/yarn/pnpm。
  2. 创建一个新的项目目录。
  3. 初始化项目并安装必要的依赖。这里的关键是选择一个靠谱的 Figma API 客户端库,它本质上就是你的“MCP”。社区流行的有figma-apifigma-js。这里以figma-js为例:
mkdir figma-json-extractor && cd figma-json-extractor npm init -y npm install figma-js dotenv

dotenv用来管理环境变量,安全地加载你的 Figma Token。

3. 从单文件读取到结构化 JSON 输出的完整流程

我们从一个最简单的脚本开始,目标是读取一个 Figma 文件,并将所有画板(Frame)的基本信息和其中的矩形元素提取出来。

3.1 基础脚本:连接并获取原始文件数据

首先,在项目根目录创建.env文件,放入你的 Token:

FIGMA_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。如果一切正常,你会看到文件名称和文档类型。这说明你的 MCP(figma-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); } }

这个脚本做了几件事:

  1. 初始化与请求:通过 MCP (figma-js) 获取数据。
  2. 遍历与筛选:递归遍历整个节点树,只挑选我们关心的节点类型(CANVAS,RECTANGLE,TEXT等)。
  3. 属性映射:从 Figma 复杂的节点对象中,提取出前端开发更关心的属性(如尺寸、颜色、字体)。
  4. 结构重组:将数据重新组织成{ file_name, canvases: [ { id, name, children: [ ... ] } ] }这样的层级结构。
  5. 输出 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 输出为 JSON

4.3 错误处理与重试机制

网络请求可能失败,API 也有速率限制。生产级脚本必须考虑这些。

  • 速率限制:Figma API 有请求频率限制。在循环或批量请求时,需要在请求间加入延迟(例如使用setTimeoutasync/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 注意事项与排查清单

当你发现脚本不工作或数据不对时,按这个顺序排查:

  1. Token 与权限
    • Figma Personal Access Token 是否有效且未过期?
    • 该 Token 是否有权限访问目标文件?(文件是否在团队项目中,Token 所属账号是否是该团队成员?)
  2. 文件与节点 ID
    • 文件 Key 是否正确?复制时是否带了多余字符?
    • 如果要获取特定节点,节点 ID 是否正确?是否已经通过client.file确认了节点 ID 的层次结构?
  3. 网络与 API 限制
    • 是否触发了 API 速率限制?检查错误响应中的x-ratelimit-*头信息。
    • 本地网络是否能正常访问api.figma.com
  4. 数据解析逻辑
    • Figma API 的响应结构是否和你代码中访问的属性路径一致?API 版本更新可能导致字段变化。最可靠的方法是,在脚本中先console.log(JSON.stringify(data.document, null, 2))打印出完整的原始响应,对照着写解析逻辑。
    • 你的遍历逻辑是否覆盖了所有需要的节点类型?FRAMEGROUPINSTANCE(组件实例)等都需要考虑。
    • 颜色值fills可能是一个数组,且可能是图片填充 (type: 'IMAGE') 或渐变填充 (type: 'GRADIENT_...'),你的代码是否处理了这些情况?
  5. 输出与集成
    • 输出的 JSON 文件路径是否正确?是否有写入权限?
    • 生成的 CSS/JS 文件格式是否符合你项目的编码规范?

我个人的经验是,第一次跑通后,把核心的“连接-获取-遍历-输出”流程封装成一个可靠的函数或模块。后续不同的提取需求(如只提颜色、只提文本样式、提取组件结构),就只是编写不同的“遍历器”(Traversal) 和“转换器”(Transformer) 的问题。这样,你的 Figma MCP + Codex 方案就从一个一次性脚本,变成了一个可维护、可扩展的前端设计资产同步管道。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询