如果你和我一样,日常工作里既要对接设计稿,又要写业务代码,还要调 AI 应用,那你大概率遇到过这样的尴尬:AI 助手能写代码、能读文档,但就是“看不懂” Figma 里的设计稿。想让它根据 UI 标注生成前端代码,得先把设计稿手动转成 JSON、截图、甚至复制粘贴节点坐标。这个过程又慢又容易出错,尤其是在设计稿频繁改动的时候。
这篇文章记录的是我自己从零搭建一个 Figma MCP 服务器的完整复盘。所谓“边飞边造引擎”,就是一边在真实项目中用这个工具解决设计稿数据读取问题,一边把它的能力边界、架构设计和踩坑经验沉淀下来。我会从 MCP 的基础概念讲起,逐步拆解 Figma 的开放 API 能力,然后完整给出一个可运行的 MCP Server 代码,最后整理高频报错和工程化建议。
对 AI Engineer、前端开发者和对 MCP 协议感兴趣的同学来说,这篇内容可以作为一套可直接落地的参考方案。读完你不仅能理解 MCP 服务器的工作方式,还能自己动手实现一个连接 Figma 的 AI 工具链。
1. MCP 与 Figma:为什么需要一座桥
1.1 MCP 到底是什么
MCP(Model Context Protocol)是 Anthropic 在 2024 年底提出的一种开放协议,全称是 Model Context Protocol。它解决的核心问题是:让 AI 模型能够以标准化的方式访问外部数据源和工具。
你可以把 MCP 理解成 AI 领域的 USB 接口。USB 定义了设备之间通用的连接标准,鼠标、键盘、U 盘只要符合这个标准,插上就能用。MCP 也一样,它定义了 AI 应用(比如 Claude Desktop、Cline、Cursor)与外部工具(比如数据库、文件系统、设计软件)之间的通信协议。作为开发者,你只需要实现一个 MCP Server,所有支持 MCP 的客户端就能直接复用你的工具能力。
一个 MCP 架构通常包含三层:
- MCP Host:运行 AI 模型的应用程序,比如 Claude Desktop、VS Code 插件等。
- MCP Server:轻量级服务,对外暴露 tools、resources、prompts 三种能力。
- 数据源:MCP Server 背后连接的具体系统,比如 Figma、GitHub、PostgreSQL。
AI 工程师关注 MCP 的主要原因很简单:大模型本身不掌握实时数据,也不具备操作外部系统的能力。通过 MCP,AI 可以调用工具读取 Figma 设计稿的节点信息、颜色变量、文本内容,然后基于这些真实数据生成代码、整理标注、执行设计走查。
1.2 Figma 为什么需要 MCP
Figma 本身已经提供了非常完整的 REST API 和插件机制,那为什么还需要 MCP?
关键在于“谁来调用”这个问题。
- Figma REST API 是给工程师写的代码调用的。
- Figma 插件是给设计师在画布上手动操作的。
- 而 MCP Server 是给 AI 模型调用的。
当 AI 需要“理解”一份设计稿时,它不能自己带着 token 去请求 Figma API,也不方便让设计师手动点插件。MCP Server 作为中间层,把“获取文件结构”“读取选中节点”“提取样式变量”这些动作封装成 AI 可以理解的工具,再通过自然语言触发。
再结合团队协作流程来看:设计师在 Figma 里完成设计后,开发拿到的往往只是静态图片或链接。开发需要自己估算间距、确认字号、核对颜色,这个过程叫“设计标注还原”。而如果 AI 能通过 MCP 直接读取设计稿的真实数据,很多机械劳动就能自动化。
1.3 典型应用场景
根据我自己的实践,Figma MCP Server 在下面几个场景中价值最明显:
- 设计稿转代码:AI 直接读取节点层级、布局属性、文本内容和样式值,生成更精准的 Tailwind CSS 或小程序代码。
- UI 走查与标注核对:自动对比设计稿与线上页面的尺寸、颜色、字体是否一致。
- 批量提取设计资产:从多页面文件中提取图标、颜色变量、字体样式,生成设计令牌(Design Token)。
- 设计稿变更感知:文件更新后,AI 可以读取 diff 信息,辅助团队快速评估影响范围。
如果你平时使用 Trae、CodeBuddy 或 Codex 这类 AI 编程工具,并且团队设计稿存放在 Figma,那么一个标准的 Figma MCP 服务器能把设计数据直接接入 AI 工作流,减少大量“人肉搬运”环节。
2. 环境准备与版本说明
在动手写代码之前,先把环境准备好。这里的版本信息基于我搭建时的环境,建议你根据自身的系统环境做匹配调整。
2.1 基础运行环境
- 操作系统:Windows 11 / macOS 均可,本文示例以 macOS 为主。
- Node.js:建议使用 18 或 20 的 LTS 版本,MCP SDK 依赖较新的 JavaScript 特性。
- 包管理器:npm 或 pnpm 均可。
- TypeScript:MCP SDK 官方示例以 TypeScript 为主,建议全局安装 TypeScript。
- Figma 账号:需要可以创建 Personal Access Token 的 Figma 账号,免费版也可以使用。
如果你使用的是 Windows,注意在代码块中把路径分隔符换成反斜杠,并且确保 Node.js 已经加入系统 PATH。
2.2 Figma API 前置条件
Figma 的 REST API 分为两个层次:
- 文件 API:读取 .fig 文件数据,比如文件结构、节点属性、样式信息。
- 实时协作 API:读取设计稿的实时协作状态。
我们这里的 Figma MCP 服务器主要使用文件 API。你需要先获取一个 Personal Access Token,获取路径为:Figma 首页头像菜单 → Settings → Security → Personal access tokens。
创建 token 时,注意选择权限范围。读取设计稿只需要files:read权限,不需要file_comments:write或file_dev_resources:write。实践上建议遵循最小权限原则,只勾选当前 Server 真正会用到的权限。
2.3 MCP SDK 版本选择
目前 MCP 官方 SDK 还在快速迭代中。原生 TypeScript SDK 包名为@modelcontextprotocol/sdk,安装时以官方 npm 最新版本为准。
需要特别提醒的是,MCP SDK 的 API 形态在不同 minor 版本之间可能发生变化,尤其是McpServer类的注册方式和 transport 的初始化方式。本文给出的代码是基于当前主流版本的写法,如果你的版本有差异,参考官方示例调整即可。
2.4 项目结构规划
一个规范的项目结构能省掉后面很多麻烦。我的推荐结构如下:
figma-mcp-server/ ├── src/ │ ├── index.ts # 入口文件 │ ├── figma-client.ts # Figma API 封装 │ ├── tools/ │ │ ├── get-file-info.ts # 获取文件信息工具 │ │ ├── get-node-detail.ts # 获取节点详情工具 │ │ └── search-nodes.ts # 搜索节点工具 │ └── types.ts # 类型定义 ├── package.json ├── tsconfig.json └── .env.example这个结构的核心思想是:入口文件只负责 MCP 服务的装配,具体的 Figma API 请求逻辑全部封装在figma-client.ts中,每个工具一个文件,便于扩展和维护。
3. 核心原理解析:MCP Server 如何工作
3.1 MCP Server 的三类能力
MCP Server 对外暴露三种能力,理解这三种能力是设计工具集的前提。
- Tools:可被 AI 调用的函数,适合执行具体操作,比如“获取文件节点树”。
- Resources:可被 AI 读取的数据资源,适合暴露只读信息,比如“当前文件的样式变量”。
- Prompts:预设的提示模板,可以引导 AI 以特定方式完成任务,比如“根据设计稿生成 Vue 代码”。
在开发 Figma MCP Server 时,Tools 是最常用的类型。每个 Tool 需要明确声明三件事:
- 工具名称(name):唯一标识,AI 根据名称决定何时调用。
- 参数结构(inputSchema):描述工具需要哪些参数,使用 JSON Schema 格式。
- 处理函数(handler):真正执行任务并返回结果。
3.2 Figma REST API 的关键接口
Figma 的 REST API 中,有两个接口是开发 MCP 服务器时最常用的。
第一个是获取文件数据:
GET https://api.figma.com/v1/files/{fileKey}返回整个文件的所有节点信息,包括每个 node 的 id、name、type、boundingBox、styles、characters 等。注意:如果文件较大,这个接口的响应体可能非常巨大,甚至达到几十 MB。
第二个是获取节点数据:
GET https://api.figma.com/v1/files/{fileKey}/nodes?ids={nodeId}返回指定节点的详细信息,适合按需读取场景。请求时可以在ids参数中传入多个节点 ID,用逗号分隔。
这两个接口都需要在请求头中带上:
X-Figma-Token: YOUR_PERSONAL_ACCESS_TOKEN3.3 AI 调用 MCP 的流程
当你在支持 MCP 的客户端中发送一段自然语言指令,比如“读取 23:88 节点的样式”,完整的链路是这样的:
用户输入 ↓ AI 模型(Host)识别意图 ↓ 根据工具描述选择一个 MCP Tool ↓ 将参数序列化为 JSON,发送给 MCP Server ↓ MCP Server 调用 Figma API ↓ 返回结构化数据给 AI ↓ AI 根据数据生成回复或代码这个流程里最容易出问题的三个环节是:AI 错误地选择了工具、参数传递不匹配、Figma API 返回的数据结构不符合预期。因此,在设计 MCP Server 的工具时,描述信息要写得足够清晰,参数校验要严格,返回结果最好做一层简化处理,避免把整个原始 JSON 抛给模型。
3.4 为什么不直接让 AI 调 Figma API
有人可能会问:既然 Figma 有 REST API,为什么不直接把 API 文档喂给模型,让 AI 自己构造 HTTP 请求?
这涉及几个现实问题:
- 认证安全:Personal Access Token 放进提示词容易被泄漏到日志或对话历史。
- 请求可靠性:AI 自己构造请求容易漏 header、拼错 URL、对错误处理不完善。
- 响应处理:Figma API 原始响应包含大量与任务无关的字段,直接返回会给模型造成噪音。
- 工具语义化:MCP Server 可以把底层 API 封装成业务动作,比如“提取页面所有文本内容”,AI 不需要关心具体调用链。
所以,MCP Server 的价值不只是转发请求,更是把复杂的外部系统简化成一组语义清晰的工具。
4. 实战:从零实现一个 Figma MCP Server
下面我们开始完整搭建。这个项目我建议你跟着敲一遍,不要直接复制整个仓库,因为过程中你会理解每一行代码的作用。
4.1 初始化项目
首先创建项目目录并初始化:
mkdir figma-mcp-server && cd figma-mcp-server npm init -y然后安装必要依赖:
npm install @modelcontextprotocol/sdk zod dotenv npm install -D typescript @types/node tsx@modelcontextprotocol/sdk:MCP 官方 TypeScript SDK。zod:参数校验库,MCP SDK 推荐使用。dotenv:读取.env环境变量文件。tsx:简化 TypeScript 运行流程。
接着创建tsconfig.json:
{ "compilerOptions": { "target": "ES2022", "module": "NodeNext", "moduleResolution": "NodeNext", "outDir": "./dist", "rootDir": "./src", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true }, "include": ["src/**/*"], "exclude": ["node_modules", "dist"] }这里的关键配置是module和moduleResolution都要使用NodeNext,否则在 Node 环境运行 ES Module 时会报错。
4.2 环境变量配置
创建.env.example文件:
FIGMA_API_TOKEN=你的Figma个人访问令牌创建.env文件并填入真实 token:
FIGMA_API_TOKEN=figd_xxxxxxxxxxxxxxxxxxxxx在package.json的 scripts 中增加启动命令:
{ "scripts": { "dev": "tsx src/index.ts", "build": "tsc", "start": "node dist/index.js" } }开发阶段直接使用npm run dev即可,tsx 会帮我们处理 TypeScript 编译。
4.3 封装 Figma API Client
创建src/figma-client.ts,用于统一处理对 Figma REST API 的请求。
// src/figma-client.ts import dotenv from 'dotenv'; dotenv.config(); const FIGMA_API_BASE = 'https://api.figma.com/v1'; const FIGMA_TOKEN = process.env.FIGMA_API_TOKEN || ''; export class FigmaClient { private token: string; constructor(token?: string) { this.token = token || FIGMA_TOKEN; if (!this.token) { throw new Error('未找到 FIGMA_API_TOKEN,请检查 .env 文件'); } } private async request<T>(path: string): Promise<T> { const response = await fetch(`${FIGMA_API_BASE}${path}`, { headers: { 'X-Figma-Token': this.token, }, }); if (!response.ok) { const errorText = await response.text(); throw new Error(`Figma API 请求失败 (${response.status}): ${errorText}`); } return response.json() as Promise<T>; } getFile(fileKey: string) { return this.request<FigmaFileResponse>(`/files/${fileKey}`); } getNodes(fileKey: string, nodeIds: string[]) { const params = new URLSearchParams(); params.set('ids', nodeIds.join(',')); return this.request<FigmaNodesResponse>( `/files/${fileKey}/nodes?${params.toString()}` ); } } // 最小类型定义 interface FigmaFileResponse { name: string; document: { id: string; name: string; type: string; children?: any[]; }; } interface FigmaNodesResponse { nodes: Record<string, any>; }这段代码做了三件事:
- 定义了一个
FigmaClient类,封装request方法统一处理请求头和错误。 getFile获取整个文件结构。getNodes按 ID 获取具体节点。
这里没有把 Figma API 的完整类型定义全部写出来,因为完整类型有几百行。实际开发中,你可以根据自己用到的字段逐步补充。
4.4 创建 MCP Server 入口
创建src/index.ts,注册两个核心工具:一个是获取文件基本信息,另一个是读取指定节点的样式和位置信息。
// src/index.ts import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { z } from 'zod'; import { FigmaClient } from './figma-client.js'; const figmaClient = new FigmaClient(); const server = new McpServer({ name: 'figma-mcp-server', version: '1.0.0', }); // 工具1:获取 Figma 文件基本信息 server.tool( 'get_figma_file_info', '获取 Figma 文件的基本信息,包括文件名、页面列表和顶层节点结构', { fileKey: z.string().describe('Figma 文件的 fileKey,可以从分享链接中获取'), }, async ({ fileKey }) => { try { const file = await figmaClient.getFile(fileKey); const pages = file.document.children?.map((child: any) => ({ id: child.id, name: child.name, type: child.type, })) ?? []; return { content: [ { type: 'text' as const, text: JSON.stringify( { name: file.name, pages: pages, }, null, 2 ), }, ], }; } catch (error: any) { return { content: [ { type: 'text' as const, text: `获取文件信息失败: ${error.message}`, }, ], }; } } ); // 工具2:获取指定节点的样式和布局信息 server.tool( 'get_figma_node_styles', '获取 Figma 中指定节点的布局位置、尺寸、填充颜色、字体等样式信息,适用于 UI 还原和代码生成', { fileKey: z.string().describe('Figma 文件的 fileKey'), nodeId: z.string().describe('节点 ID,例如 23:88'), }, async ({ fileKey, nodeId }) => { try { const response = await figmaClient.getNodes(fileKey, [nodeId]); const nodeData = response.nodes[nodeId]; if (!nodeData) { return { content: [ { type: 'text' as const, text: `未找到节点 ${nodeId},请确认 IDs 中的 ID 是否正确`, }, ], }; } const node = nodeData.document; const result = extractNodeStyle(node); return { content: [ { type: 'text' as const, text: JSON.stringify(result, null, 2), }, ], }; } catch (error: any) { return { content: [ { type: 'text' as const, text: `获取节点信息失败: ${error.message}`, }, ], }; } } ); function extractNodeStyle(node: any) { const base = { id: node.id, name: node.name, type: node.type, visible: node.visible !== false, }; const result: any = { ...base }; if (node.absoluteBoundingBox) { const box = node.absoluteBoundingBox; result.position = { x: box.x, y: box.y, width: box.width, height: box.height, }; } if (node.fillStyleId || node.strokeStyleId || node.effectStyleId) { result.styleIds = { fill: node.fillStyleId ?? null, stroke: node.strokeStyleId ?? null, effect: node.effectStyleId ?? null, }; } if (node.fills && node.fills.length > 0) { const solidFills = node.fills.filter( (fill: any) => fill.type === 'SOLID' && fill.visible !== false ); if (solidFills.length > 0) { const fill = solidFills[0]; result.fills = { color: fill.color ? `rgba(${Math.round(fill.color.r * 255)}, ${Math.round( fill.color.g * 255 )}, ${Math.round(fill.color.b * 255)}, ${fill.color.a})` : null, }; } } if (node.characters) { result.text = { content: node.characters, fontSize: node.style?.fontSize ?? null, fontName: node.style?.fontName ?? null, textAlignHorizontal: node.style?.textAlignHorizontal ?? null, textAlignVertical: node.style?.textAlignVertical ?? null, lineHeightPx: node.style?.lineHeightPx ?? null, }; } return result; } // 启动服务 const transport = new StdioServerTransport(); await server.connect(transport); console.error('Figma MCP Server 已启动');这段代码是核心,解释几个重点。
McpServer是 SDK 提供的高层封装,server.tool方法用于注册工具。第一个参数是工具名,第二个是对工具的描述,第三个是参数 schema,第四个是实际处理函数。
extractNodeStyle函数过滤掉了原始响应里大量无关字段,把 AI 真正关心的位置、颜色、字体信息整理成结构化 JSON。AI 拿到这种格式的数据后,生成代码的准确率会明显高于给它原始 Figma API 响应。
注意,await server.connect(transport)这一行的await要放在顶层。需要确认你的 tsconfig 是否支持顶级 await,如果编译报错,可以改成:
async function main() { const transport = new StdioServerTransport(); await server.connect(transport); console.error('Figma MCP Server 已启动'); } main().catch((error) => { console.error('启动失败:', error); process.exit(1); });4.5 用 MCP Inspector 验证服务
MCP 官方提供了一个调试工具叫 MCP Inspector,可以模拟 AI 客户端向我们的 Server 发送请求,是排查问题的利器。
安装并运行:
npx @modelcontextprotocol/inspector node dist/index.js打开浏览器进入 Inspector 页面后,在 Tools 列表里应该能看到刚注册的两个工具。点击get_figma_file_info,在参数输入框中填入一个真实的 fileKey,点击运行,就能看到返回的页面列表。
这里需要解释一个关键概念:MCP 默认使用 stdio 传输层,也就是说 MCP Server 通过标准输入输出和宿主进程通信。所以你在终端启动 Server 后,它会一直等待输入,不会打印普通日志。如果要在调试时观察内部状态,请使用console.error而不是console.log,因为stdout是协议通道,不能混入业务输出。
4.6 集成到 AI 客户端
调试通过后,就可以把服务器接入支持 MCP 的客户端。以 Claude Desktop 为例,配置文件为claude_desktop_config.json:
{ "mcpServers": { "figma": { "command": "node", "args": ["/absolute/path/to/figma-mcp-server/dist/index.js"], "env": { "FIGMA_API_TOKEN": "你的Figma个人访问令牌" } } } }如果你使用 Cursor 或 Trae,通常在设置页面找到 MCP 配置入口,填入启动命令和环境变量即可。注意启动命令要使用绝对路径,path 环境变量不一定包含你的 Node.js 安装位置。
5. 常见问题与排查思路
在实际搭建和使用的过程中,我遇到了不少问题。这里整理成表格,方便快速对照排查。
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| 启动时报 “Cannot find module” | 编译产物目录不对或未安装依赖 | 确认执行npm run build,并检查 dist 目录是否存在 |
| Server 启动后没有任何输出 | stdio 模式下 stdout 被协议占用 | 使用console.error输出日志,而不是console.log |
| 调用工具返回 401 Unauthorized | Figma Token 无效或已过期 | 重新生成 token,检查是否在环境变量中正确注入 |
| 调用工具返回 403 Forbidden | Token 权限不足 | 确认 token 包含files:read权限 |
| 调用工具返回 404 Not Found | fileKey 或 nodeId 错误 | 复制设计稿 URL 中/file/后面的字段作为 fileKey |
| AI 选择了错误的工具 | 工具描述不清晰 | 完善 tool 的 description 字段,说明推荐使用场景 |
| 返回数据太大导致 AI 误解 | 原始节点包含多层子节点 | 在 handler 中做数据裁剪,只返回必要字段 |
| Figma 文件包含中文字体,AI 识别为空白 | 系统未安装对应字体 | 在系统中安装字体,或在返回数据中同时返回characters文本内容 |
这里单独说明一下 fileKey 的获取方法。Figma 分享链接通常长这样:
https://www.figma.com/file/AbCdEfGhIjKlMnOpQrStUv/项目名称?node-id=0%3A1其中AbCdEfGhIjKlMnOpQrStUv就是 fileKey,node-id参数中的数字就是节点 ID。注意 URL 里的%3A是冒号的编码,实际使用时应转换为0:1这种格式。
另外,网络上不少人提到 Figma 汉化、安装字体、客户端设置的问题。如果你在读取设计稿时发现字体信息异常,优先检查 Figma 客户端所在系统是否已经安装对应字体,以及是否在 Figma 账号中正确同步了字体库。这类问题多属于环境问题,不是 MCP 配置问题。
6. 工程实战注意事项与性能优化
6.1 工具设计要符合 AI 的使用习惯
MCP 工具不是给人类用的,而是给 AI 模型用的。所以工具的设计思路和普通 API 设计差别很大。
一个常见误区是:把 Figma 的原始 API 直接映射成 MCP 工具,一个接口对应一个工具。这样做的结果往往是 AI 面对大量底层函数不知所措,不知道该调用哪个。
更推荐的做法是面向场景设计工具。比如,与其暴露“获取节点数据”和“获取文件数据”两个底层工具,不如设计成:
get_page_list:获取页面列表。get_node_style:获取节点样式。get_text_content:提取文本内容。extract_design_tokens:提取颜色和字体变量。
这样可以显著降低 AI 的决策成本。
6.2 Token 安全与配置管理
Figma Token 属于敏感凭证,需要注意以下几点:
- Token 不要硬编码在代码仓库中,建议通过
.env文件或宿主环境的环境变量注入。 - 如果项目使用 Git,
.env必须加入.gitignore。 - 使用环境变量时,在启动 MCP Server 的配置文件中直接配置
env字段,避免写入 shell 历史。 - 遵循最小权限原则,创建 Token 时只授予当前场景必需的权限。
6.3 数据裁剪与响应控制
Figma 文件 API 返回的原始数据结构非常深,一个节点下面可能嵌套几十层子节点。如果原样返回给 AI,既浪费 token,又干扰模型判断。
我的经验是:在 handler 里做好两件事。
- 垂直裁剪:只返回当前节点自己的一组关键属性,不递归展开所有子节点。
- 水平裁剪:只保留前端代码生成真正需要的字段,比如
name、type、absoluteBoundingBox、fills、style、characters。
如果需要获取子节点信息,可以单独设计一个“获取子节点列表”的工具,让 AI 根据自己的判断决定是否深入。
6.4 错误处理与日志记录
MCP Server 运行在用户本地或 CI 环境中,不像 Web 服务那样容易监控。所以建议:
- 所有工具 handler 内部捕获异常,并把错误信息作为文本内容返回,避免服务崩溃。
- 使用
console.error打印关键路径日志,方便排查问题。 - 记录每次 Figma API 请求的 fileKey 和 nodeId,出现问题时可以快速定位是哪个文件、哪个节点导致的错误。
一个简单的日志工具示例:
function log(level: 'info' | 'error', message: string, meta?: any) { console.error(`[${level}] ${new Date().toISOString()} ${message}`, meta ?? ''); }6.5 支持多文件与多项目
如果你的团队有多个 Figma 项目,建议把 fileKey 设计成工具参数之一,而不是写死在配置里。这样 AI 可以根据对话上下文,动态决定读取哪个文件。
更进一步,可以在 MCP Server 中维护一个项目映射表,把项目代号映射到 fileKey。例如:
const PROJECT_MAP: Record<string, string> = { web: 'AbCdEfGhIjKl', admin: 'MnOpQrStUvWx', mobile: 'YzAbCdEfGhI', };这样用户可以直接说“读取 web 项目的首页设计稿”,AI 先查询映射表,再调用对应的工具。
6.6 性能优化与缓存
Figma API 有访问频率限制,如果 AI 在短时间内多次请求大型文件,可能触发限流。建议在 MCP Server 层加一层简单缓存:
const cache = new Map<string, { data: any; expireAt: number }>(); async function getFileWithCache(fileKey: string, ttlMs = 60000) { const cacheKey = `file:${fileKey}`; const cached = cache.get(cacheKey); if (cached && cached.expireAt > Date.now()) { return cached.data; } const data = await figmaClient.getFile(fileKey); cache.set(cacheKey, { data, expireAt: Date.now() + ttlMs }); return data; }设置 60 秒的 TTL,既能避免短时间内重复拉取,又能保证设计稿更新后能在合理时间内同步。
7. 从 MVP 到生产级:边飞边造的迭代路线
最后梳理一下这个项目从想法到落地的迭代思路,这部分也算是我自己的复盘记录。
7.1 第一版:只做最核心一件事
第一版我只做了一个工具:get_node_styles。目标很简单:输入 fileKey 和 nodeId,返回位置、尺寸、颜色、字体。这个版本解决了“AI 能否拿到设计稿关键数据”的问题,是整条链路的验证模型。
这一步最重要的是快速跑通,不要过度设计。MCP 协议的通信链路、Figma API 的认证流程、数据裁剪的逻辑,都需要在这一步验证清楚。
7.2 第二版:补全语义能力
验证跑通后,再按需增加工具。我在第二版加入了:
get_page_list:帮助 AI 定位内容所在的页面。get_children_nodes:让 AI 可以逐层浏览节点树。search_nodes_by_name:根据名称快速检索节点。
这些工具看起来简单,但极大地提升了 AI 在真实文件中的导航能力。AI 不再需要用户精确提供 nodeId,而可以通过名称和页面逐步找到目标。
7.3 第三版:异常场景覆盖
到第三版,主要精力花在异常处理上。核心要覆盖这些场景:
- 文件不存在或没有访问权限时,返回友好提示。
- 节点被删除或移动到其他位置时,返回可理解的错误。
- Figma API 限流时,给出重试建议。
- 字体缺失导致样式读取不完整时,至少返回源文本内容。
这一步是“边飞”时最真实的体验:项目已经有人在用了,你不能因为一个极端输入把服务搞挂。
7.4 第四版:面向多客户端兼容
MCP 生态还在快速演进,不同的 Host 对工具参数类型、资源协议的支持程度并不一致。如果希望 Server 能被 Claude Desktop、Cursor、Trae 等客户端同时使用,应该在代码中减少对特定客户端特性的依赖,多使用基础协议能力。
例如,在工具返回内容时,尽量只用type: 'text'的 content,不要依赖图片或其他扩展类型,因为并不是所有客户端都支持。
8. 总结与下一步学习方向
写到这里,整套 Figma MCP 服务器的核心内容基本都覆盖了。从 MCP 协议的基本概念,到 Figma REST API 的调用方式,再到完整的 TypeScript 实现,以及最后的工程化注意事项,这是一条可以直接复用的技术链路。
对想继续深入的朋友,我建议按下面几个方向推进:
- 阅读 MCP 官方 SDK 的完整文档,了解
Resources和Prompts的更多用法。 - 研究 Figma Plugin API 和 REST API 的差异,判断哪些场景适合用插件实现,哪些适合用 MCP。
- 结合实际项目,把设计稿转代码的流程做成一个完整的 AI 工程,而不仅仅是读取数据的工具。
- 多关注 AI 编程工具的 MCP 配置机制,不同工具之间协议兼容性差异很大,需要实际测试。
Figma MCP 服务器的开发过程,本质上是在给 AI 装上一双“能看懂设计稿的眼睛”。这个方向目前还有很大的探索空间,希望这篇文章能帮你少走一些弯路。如果你在实际部署中遇到其他问题,欢迎在评论区交流。