一次 opencode + OpenPencil MCP 协同解析 .fig 文件的踩坑实录
前言
设计师给了一份 .fig 文件,我需要让 opencode 这个 AI 编程 Agent 自己读懂设计稿 并产出前端代码。
听起来简单,做起来一波三折------本文把整个过程记录下来,包括为什么最终选了 @open-pencil/mcp 而不是最初的 fig2json,以及路上踩到的所有坑。
环境约束:Windows 11、Node 22(opencode 强制依赖)、无 MSVC、无 MinGW。
一、需求拆解
目标链路:
设计师的 .fig 文件 → opencode AI 读懂 → 生成组件代码(React/Vue/HTML)
需要解决三个问题:
- 解析 :
.fig是 Figma 私有二进制格式(Kiwi schema + Zstd + ZIP),没人公开 spec - 集成:解析结果要被 opencode 的 LLM 上下文消费
- 部署:必须能跑在我的环境里(Node 22,Windows)
二、方案调研------四条候选路径
我先列出了所有可能的方向,并行调研:
| 路径 | 工具 | 优点 | 缺点 |
|---|---|---|---|
| A | kreako/fig2json (Rust CLI) |
纯离线、无账号、一行命令 | 要 Rust + 链接器(MSVC/MinGW) |
| B | Figma 官方 Remote MCP | 官方、写回画布、Code Connect | 只支持 Figma URL ,不支持 .fig 本地文件 |
| C | open-pencil/mcp (Node MCP) |
100+ 工具、本地优先 | 需要跑 OpenPencil 桌面 app |
| D | Open Design (Node 桌面) | 全工作流、品牌系统 | 强制 Node 24,与 Node 22 冲突 |
我当时选了 Path A (fig2json) ------最直接,一个 CLI 转 JSON 就完事。
三、Path A 的崩溃与路径切换
fig2json 在 GitHub Releases 上没有预编译的 Windows 二进制 。要装就只能 cargo install fig2json。
我兴冲冲打开终端:
bash
npm install -g @open-pencil/mcp # ← 先装着备用的
cargo install fig2json
然后------
error: linker `link.exe` not found
我的环境既没有 MSVC 也没有 MinGW 。要编译 fig2json 得先装 Visual Studio Build Tools(几个 GB)。
这是个真实的工程痛点:Rust 生态在 Windows 上的"开箱即用"远不如 macOS/Linux。一个 5MB 的 CLI 工具,要装几 GB 的 toolchain 才能编译,不值。
决策:Pivot 到 Path C(@open-pencil/mcp)
回头看 MCP 包:
bash
$ npm install -g @open-pencil/mcp
added 166 packages in 1m
$ npm view @open-pencil/mcp engines
# (empty --- 没声明 Node 版本约束)
$ node -e "require('@open-pencil/mcp/package.json').version"
# 0.13.2
纯 Node.js 包。没有任何 native module。Node 22 直接跑。
代价:需要 OpenPencil 桌面 app 一起跑(~7MB)。但这是 Tauri 应用,跨平台、单文件、双击就开------比装 Visual Studio Build Tools 友好得多。
四、最终架构
#mermaid-svg-4J6RgHcR6TQ6uLMb{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-4J6RgHcR6TQ6uLMb .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-4J6RgHcR6TQ6uLMb .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-4J6RgHcR6TQ6uLMb .error-icon{fill:#552222;}#mermaid-svg-4J6RgHcR6TQ6uLMb .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-4J6RgHcR6TQ6uLMb .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-4J6RgHcR6TQ6uLMb .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-4J6RgHcR6TQ6uLMb .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-4J6RgHcR6TQ6uLMb .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-4J6RgHcR6TQ6uLMb .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-4J6RgHcR6TQ6uLMb .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-4J6RgHcR6TQ6uLMb .marker{fill:#333333;stroke:#333333;}#mermaid-svg-4J6RgHcR6TQ6uLMb .marker.cross{stroke:#333333;}#mermaid-svg-4J6RgHcR6TQ6uLMb svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-4J6RgHcR6TQ6uLMb p{margin:0;}#mermaid-svg-4J6RgHcR6TQ6uLMb .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-4J6RgHcR6TQ6uLMb .cluster-label text{fill:#333;}#mermaid-svg-4J6RgHcR6TQ6uLMb .cluster-label span{color:#333;}#mermaid-svg-4J6RgHcR6TQ6uLMb .cluster-label span p{background-color:transparent;}#mermaid-svg-4J6RgHcR6TQ6uLMb .label text,#mermaid-svg-4J6RgHcR6TQ6uLMb span{fill:#333;color:#333;}#mermaid-svg-4J6RgHcR6TQ6uLMb .node rect,#mermaid-svg-4J6RgHcR6TQ6uLMb .node circle,#mermaid-svg-4J6RgHcR6TQ6uLMb .node ellipse,#mermaid-svg-4J6RgHcR6TQ6uLMb .node polygon,#mermaid-svg-4J6RgHcR6TQ6uLMb .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-4J6RgHcR6TQ6uLMb .rough-node .label text,#mermaid-svg-4J6RgHcR6TQ6uLMb .node .label text,#mermaid-svg-4J6RgHcR6TQ6uLMb .image-shape .label,#mermaid-svg-4J6RgHcR6TQ6uLMb .icon-shape .label{text-anchor:middle;}#mermaid-svg-4J6RgHcR6TQ6uLMb .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-4J6RgHcR6TQ6uLMb .rough-node .label,#mermaid-svg-4J6RgHcR6TQ6uLMb .node .label,#mermaid-svg-4J6RgHcR6TQ6uLMb .image-shape .label,#mermaid-svg-4J6RgHcR6TQ6uLMb .icon-shape .label{text-align:center;}#mermaid-svg-4J6RgHcR6TQ6uLMb .node.clickable{cursor:pointer;}#mermaid-svg-4J6RgHcR6TQ6uLMb .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-4J6RgHcR6TQ6uLMb .arrowheadPath{fill:#333333;}#mermaid-svg-4J6RgHcR6TQ6uLMb .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-4J6RgHcR6TQ6uLMb .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-4J6RgHcR6TQ6uLMb .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-4J6RgHcR6TQ6uLMb .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-4J6RgHcR6TQ6uLMb .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-4J6RgHcR6TQ6uLMb .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-4J6RgHcR6TQ6uLMb .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-4J6RgHcR6TQ6uLMb .cluster text{fill:#333;}#mermaid-svg-4J6RgHcR6TQ6uLMb .cluster span{color:#333;}#mermaid-svg-4J6RgHcR6TQ6uLMb div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-4J6RgHcR6TQ6uLMb .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-4J6RgHcR6TQ6uLMb rect.text{fill:none;stroke-width:0;}#mermaid-svg-4J6RgHcR6TQ6uLMb .icon-shape,#mermaid-svg-4J6RgHcR6TQ6uLMb .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-4J6RgHcR6TQ6uLMb .icon-shape p,#mermaid-svg-4J6RgHcR6TQ6uLMb .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-4J6RgHcR6TQ6uLMb .icon-shape .label rect,#mermaid-svg-4J6RgHcR6TQ6uLMb .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-4J6RgHcR6TQ6uLMb .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-4J6RgHcR6TQ6uLMb .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-4J6RgHcR6TQ6uLMb :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 自然语言指令
stdin/stdout JSON-RPC
ws://127.0.0.1:7601
解析二进制
👤 用户调用
'用 D:/design.fig 实现落地页'
🛠️ opencode (Node 22 + LLM)
• 内置 Skill: figma-fig-to-code
• • SKILL.md 触发: description 含 .fig / Figma
• MCP client (stdin/stdout JSON-RPC)
@open-pencil/mcp server (Node 22)
• 106 个工具: list_pages, get_jsx, get_node ...
• 通过 WebSocket 与桌面 app 通信
🖥️ OpenPencil 桌面 app (Tauri, ~7MB)
• 真正的 .fig 二进制解析器 (Kiwi + Zstd + WASM)
📄 designer.fig
五、实施步骤
1. 安装 MCP server
bash
npm install -g @open-pencil/mcp
2. 注册到 opencode
编辑 ~/.config/opencode/opencode.json:
jsonc
{
"mcp": {
"open-pencil": {
"type": "local",
"command": ["node", "D:/App/nodejs/node_modules/@open-pencil/mcp/dist/stdio.mjs"],
"environment": { "OPENPENCIL_MCP_ROOT": "D:/App" },
"enabled": true
}
}
}
3. 启动 OpenPencil 桌面 app
去 https://openpencil.dev/ 下载安装,启动。
4. 创建全局 Skill
在 ~/.config/opencode/skills/figma-fig-to-code/ 下创建:
figma-fig-to-code/
• - SKILL.md # 触发条件 + 工作流
• - references/
• • - flex-mapping.md # Figma auto-layout → CSS Flexbox
• • - tool-quickref.md # 106 个工具速查
• • - output-stacks.md # React/Vue/Svelte 输出模板
• - scripts/
• - install.ps1 # Windows 一键装
• - install.sh # Unix 一键装
• - full-e2e.mjs # 端到端验证脚本
SKILL.md 的 frontmatter:
yaml
---
name: figma-fig-to-code
description: Use this skill when the user provides a Figma `.fig` file ... triggers on phrases like "实现这个 figma", "解析 .fig", "design to code"...
license: MIT
metadata:
author: your-name
version: "1.3.0"
---
description 里的触发关键词是 skill 能被自动加载的关键------opencode 的 loader 是基于关键词匹配的。
5. 重启 opencode 让配置生效
⚠️ 重要 :opencode 的配置是启动时加载的,热更新不支持。改了 opencode.json 必须退出当前 session 重启。
6. 验证
bash
opencode mcp list
# ✓ open-pencil connected
opencode debug skill | grep figma
# ✓ figma-fig-to-code
六、真实端到端测试
光有"连通"不算数。我用一个真实的 PPT 模板 .fig(7.6MB)跑全流程:
[probe] target: D:/App/figma-test/ppt模版浅色.fig
[OK 1852ms] open_file ← 真的打开了 7.6MB 的文件
[OK 276ms] list_pages ← 列页面
{
"current": "Page 1",
"pages": [
{ "id": "0:3", "name": "Page 1" },
{ "id": "0:520", "name": "Internal Only Canvas" }
]
}
[OK 5ms] get_components
[OK 4ms] list_fonts
✅ 你的 .fig 文件真的能识别------MCP 链路全打通,返回了真实的 page ID。
七、踩到的坑(决策日志)
我在 SKILL.md 里专门维护了 ## Decision log,记录 4 条关键决策:
D1 --- 为什么不走 fig2json(最初的 Plan A)
Rust + MSVC 装不上 → 改 MCP。未来若 Rust/MSVC 装好,fig2json 仍值得重评------更简单。
D2 --- 为什么 Node 22 兼容是硬约束
opencode 跑 Node 22。Open Design 要 Node 24 被排除了。MCP 路径是唯一不冲突的选项。
D3 --- 为什么用 MCP 而不用 CLI
@open-pencil/cli 的源码第一行就是 Bun.XXX,Node 跑直接 Bun is not defined。装 Bun 会破坏 D2 的"不引入新运行时"原则。
D4 --- 为什么不直接用 @open-pencil/core 编程 API
v0.13.2 有个无限递归 bug (io/formats/pen/read.js:288),任何非空 fixture 都触发 Maximum call stack size exceeded。MCP 走桌面 app 内部的 C++/WASM 路径,不受影响。
八、还存在的坑(实事求是)
| 验证项 | 结果 | 说明 |
|---|---|---|
| MCP ↔ OpenPencil app RPC | ✅ 通过 | ws://127.0.0.1:7601 |
| open_file / list_pages / get_jsx | ✅ 通过 | 在真实 .fig 上返回真数据 |
| 端到端"opencode 读 .fig" | ✅ 通过 | 在 SKILL 工作流设计层可走通 |
| 全自动(opencode AI 自主决策调工具) | ❌ 待测 | 需在重启 opencode 后实测 |
| get_page_tree 在 7.6MB 大文件上慢 | ❌ 慢 | 设计就是分区域收敛,不是问题 |
九、经验总结
- Node 生态的"开箱即用"远超 Rust 在 Windows 上------一个 npm 包 60 秒装好,一个 Rust 工具要装几个 GB toolchain 才能编译。优先 Node。
- MCP 是 AI 工具集成的最佳模式:本地 stdio + 远程 WebSocket,AI 直接当工具用,比写胶水代码强多了。
- Skill 的 description 决定可发现性 :
"Use this skill when..."第三人称 + 触发关键词,opencode 才能自动加载。 - 配置文件改完必须重启------opencode 不热重载,省得以为是 bug。
- 上游 bug 不绕道就踩坑 ------遇到
@open-pencil/core的栈溢出别浪费时间在 Node 上重试,直接用 MCP 路径。 - 诚实记录决策 :如果路径变了没告诉用户,6 个月后的你会骂 6 个月前的自己。SKILL.md 的
## Decision log是这种"反自我"工具。
附:完整 skill 已开源
本项目所有代码(含 SKILL.md、scripts、references)已开源:
GitHub: https://github.com/1648298170/opencode-figma-skill
直接 gh repo clone 1648298170/opencode-figma-skill 或 git clone 就能用。
如果本文对你有帮助,欢迎 Star / Fork / 提 Issue。也欢迎在评论区分享你的 .fig → 代码 自动化经验。