Skip to main content
Glama

DSH Figma Bridge

把 Figma 画布的读写能力开放给你自己电脑上的程序:不再靠截图和手工操作,而是让程序直接读图层树、 写真实属性。批量改间距、重命名图层、像 linter 一样审查硬编码色值——写一次循环,剩下的交给它。

驱动它的程序由你决定:一个脚本、一次 CI 任务,或你自己配置的 agent(本项目的参考实现是 DeepSeek Harness(DSH),仓库里的 packages/bridge 既是通用本地服务,也是它的 MCP server)。

分两块:

  • packages/plugin —— 装进 Figma 的插件,跑在插件 main 沙箱里,有完整的 figma API。

  • packages/bridge —— 本地 Node 进程。对内是插件长轮询的 HTTP 端点,对外提供 MCP(stdio)接口,也可以脱离 MCP 单独以 HTTP 模式运行。

你的程序 ──MCP/stdio 或直接 HTTP──> bridge ──HTTP 长轮询──> Figma 插件 ──figma API──> 画布
                                  127.0.0.1:8790

为什么是长轮询而不是 WebSocket:Figma 插件沙箱的全局对象里只有 figmafetchconsolesetTimeout 系列、__html____uiFiles__ —— 没有 WebSocket,插件也不能监听端口。 细节和取证见 docs/plan.md

公开文档(给用户和 Figma 评审看的那个网址)https://izt-pixel.github.io/dsh-figma/ —— 讲怎么装、怎么用、坏了怎么查; 隐私政策在 https://izt-pixel.github.io/dsh-figma/PRIVACY.html


快速开始

只想用插件、不想读源码? 直接看 docs/使用说明.md—— 那篇讲的是怎么开机、面板每一行什么意思、坏了先看哪三处。本文件讲的是怎么把它搭起来。

0. 前置

  • Node ≥ 22

  • pnpm

  • Figma 桌面版或网页版

1. 构建

pnpm install
pnpm build

构建产物:

路径

用途

packages/bridge/dist/mcp.js

DSH 要启动的 MCP server

packages/plugin/dist/code.js

Figma 插件主线程代码(单文件经典脚本)

packages/plugin/ui.html

插件状态面板

项目不使用打包器tsc 直出。插件必须是"无 import/export 的经典脚本", 因为 Figma 没有模块加载器;packages/plugin/scripts/verify-bundle.mjs 每次构建都会强制校验这一点。

2. 把插件装进 Figma

  1. Figma 里打开任意设计文件

  2. 菜单 Plugins → Development → Import plugin from manifest…

  3. 选择 packages/plugin/manifest.json

  4. 运行 Plugins → Development → DSH Figma Bridge

面板出现后应显示 connected。如果显示 offline,说明桥还没起来(先做第 3 步)。

改代码之后怎么重载插件

改了什么

要做什么

packages/plugin/src/*(即 dist/code.js

关闭插件再重新运行即可,不需要重新导入;用面板右上角的 build id 确认

manifest.json(权限、networkAccessmain/ui 路径)

需要 Import plugin from manifest… 重新导入

只改了桥(packages/bridge/*,含 tools.ts 的工具增删)

结束桥进程即可,不用重启 DSH —— 见下

profile 补丁(cordis.patch.yml

必须重启 DSH

Figma 的开发插件每次运行都会从磁盘重读 main/ui——这正是开发流程的设计。

改了桥为什么只要结束进程? DSH 的 MCP client 只在连接时取一次工具列表,之后再改 tools.ts 不会生效(新工具调不到,报 unknown tool)。但它对"本地服务器进程崩溃"有自动重连并刷新工具集 的行为,而重连就是重新执行 node …/dist/mcp.js——也就是磁盘上最新那份。所以:

Get-Process node | Stop-Process -Force    # DSH 会在退避内重新拉起新版桥

比重启 DSH 快得多。桥是无状态的(客户端注册表会由插件重新 poll 自动重建,退避 ≤10s)。

怎么确认 Figma 里跑的确实是你刚构建的那份? 看面板右上角的 build id

v0.1.0 · p1 · 49408afc
                  ^^^^^^^^ 每次构建都会变(源码的哈希)

(上面这个 49408afc 是当前 dist/code.js 里真实的 id,不要把它当固定值读—— 它跟着源码走。哪天它和面板显示的不一致,就说明两边不是同一份产物。)

之前只有版本号和命令列表可看,而这两者在"只改内部逻辑、不加工具"的构建之间完全一样—— 等于没有任何办法判断插件是否已更新。build id 补上了这个缺口,它也出现在 /figma/healthstatus 的返回里。相同源码重建会得到相同的 id(无改动就是无改动), 重复 stamp 会被拒绝。

networkAccess 只能用 localhost,不能写 127.0.0.1 Figma 的 manifest 校验器会把 IPv4 字面量判为非法 URL,导入或发布时报 Invalid value for allowedDomains. 'http://127.0.0.1:8790' must be a valid URL. 因此插件唯一被允许的 origin 是 http://localhost:8790,桥也相应地同时绑定 127.0.0.1::1 两个回环地址(因为 localhost 在不同机器上解析结果不同)。

连带约束:端口写死在 allowedDomains 里。改端口必须同步改 manifest, 否则 Figma 会按 CSP 拦掉请求——CSP 报错在插件开发者控制台里才能看到。

3. 接入 DSH

编辑你的 DSH profile 补丁层 —— 本机是 C:\Users\izhao\.dsh\profiles\desktop\cordis.patch.yml

# 新增插件必须包在 `insert:` 里。裸写 `- id: xxx` 会被当成"覆盖一个已存在的
# 条目",而它并不存在,于是被静默跳过(日志:patch: entry "xxx" not found)。
- insert:
    - id: mcp-figma
      name: '@deepseek-ai/dsh-mcp-client'
      config:
        serverName: figma
        transport: stdio
        command: node
        args: ['D:\个人文档\figma\packages\bridge\dist\mcp.js']
        env:
          FIGMA_BRIDGE_PORT: '8790'
        # 截图和批量节点操作会超过 60s 默认值
        toolCallTimeoutMs: 120000

改完先离线自检,不用重启——dsh CLI 与应用启动共用同一份补丁语义:

node "D:\DeepSeekharness\dsh\DSH Desktop\resources\app.asar.unpacked\node_modules\@deepseek-ai\dsh\lib\bin.js" \
  --profile desktop --dump-config

它会把合成后的配置树打出来。看到 - id: mcp-figma没有 patch: entry ... not found 警告, 才算补丁生效。这一步能省掉大量"改一次重启一次"的往返。

然后重启 DSH Desktop(这个补丁在应用启动时读取,不热加载)。重启后模型会多出六个工具 (清单见文末「工具」一节),先确认最基础的两个连得上:

  • mcp__figma__status —— 链路状态、文件、页面、当前选区

  • mcp__figma__ping —— 往返测延迟

想断开就把这个 - id: mcp-figma 条目删掉。

node 不在 DSH 的 PATH 上,把 command 换成绝对路径,例如 C:\Program Files\nodejs\node.exe

3b. 不重启 DSH 也想验证 Figma 那一侧?

桥可以脱离 DSH 单独跑 HTTP 模式:

pnpm serve:standalone        # 等价于 node packages/bridge/dist/mcp.js --standalone

这个模式不接 MCP、不碰 stdin,只提供插件要长轮询的 HTTP 端点。 适合先确认"插件 ↔ 桥"这一段通了(面板应显示 connected),再重启 DSH 让桥由 DSH 托管。 注意它和 DSH 自己启动的桥抢同一个端口:要重启 DSH 时先把 standalone 的桥停掉。


Related MCP server: FreeMCP for Figma

排查

现象

原因

处理

面板 Offline/figma/health 也连不上

桥没在跑。DSH 的 profile 补丁只在应用启动时读取,改完必须重启 DSH

重启 DSH Desktop;或临时用 pnpm serve:standalone

面板 Offline,但 /figma/health 正常

端口不一致,或 manifest 的 allowedDomains 与端口不匹配(被 CSP 拦掉,只在插件开发者控制台可见)

对齐插件面板端口、FIGMA_BRIDGE_PORTmanifest.json 三处

面板错误显示 [object Object]

旧版插件产物。Figma 沙箱的 fetch 抛的是普通对象而非 Error

重新构建并重新运行插件(pnpm build 后重跑插件即可加载新 code.js

重启 DSH 后模型仍没有 mcp__figma__* 工具

补丁被跳过。常见原因:新增插件没包在 insert: 里,DSH 会把它当"覆盖不存在的条目"并静默跳过

跑一次 --dump-config 自检(见第 3 步);日志里搜 patch: entry —— 出现即说明补丁没应用

日志出现 patch: entry "mcp-figma" not found

同上:裸 - id: 形式是覆盖语义,不是新增

- insert: 包裹后重跑 --dump-config 确认警告消失


验证

# 全部自动检查:60 项协议冒烟 + 45 项序列化单测 + 125 项写入单测
pnpm test

# 分开跑
pnpm smoke              # 桥的协议层(用假插件替代 Figma 驱动完整协议)
pnpm test:serializer    # 插件的序列化器(vm + 桩 figma,直接调用内部函数)
pnpm test:apply         # 插件的写执行器(记录型桩,断言真正写进节点的值)
pnpm verify:plugin      # 插件产物必须是纯经典脚本,且 ui.html 存在

# 手动看诊断(需要桥在跑)
curl http://localhost:8790/figma/health

/figma/health 会列出已连接的插件实例、绑定的地址,以及它报回的文件名/页面/选区—— 一眼就能分清"桥没起来"、"桥起来了但插件没连"、"都连上了"三种状态。

为什么序列化器能测

插件的 dist/code.js 必须是无导出的经典脚本(Figma 没有模块加载器),所以里面的函数无法 import。 但经典脚本的顶层 function 声明会成为全局属性——test-serializer.mjs 因此用 vm 在带桩 figma 的环境里求值整个 bundle,然后直接调用 serializeNode / describeCommand

这段代码是最容易对模型撒谎的地方:describe 说某个属性不存在、而它其实存在时,模型会跳过它, 下一次写入就是静默的数据丢失。所以它值得有独立测试,而不是只能靠人在 Figma 里点。

pnpm build 会依次跑 tsc → 产物校验 → 序列化测试,任一失败即构建失败。


环境变量

变量

默认

说明

FIGMA_BRIDGE_PORT

8790

HTTP 端口。改动后插件面板里的端口和 manifest.jsonallowedDomains 都要一起改

FIGMA_BRIDGE_HOST

127.0.0.1

主绑定地址;桥会自动附带绑定另一族回环(::1),两族都只在本机。不要改成 0.0.0.0 —— 那等于把画布控制权交给整个局域网

FIGMA_BRIDGE_TIMEOUT_MS

120000

单条命令预算

FIGMA_BRIDGE_LOG

info

debug / info / warn / error。日志一律走 stderr(stdout 是 MCP 通道)


目录

packages/protocol/     线协议:类型、校验函数、常量(唯一真源)
packages/bridge/       本地桥:MCP server + HTTP 端点 + 命令队列
  src/mcp.ts           入口:MCP 接线、绑端口重试、关闭钩子
  src/bridge.ts        HTTP 服务、客户端注册表、超时与断连收敛
  src/tools.ts         面向模型的工具描述(提示词的一部分,不是文档)
  src/render.ts        工具结果 → MCP content(含图像路径,单独可测)
  scripts/smoke.mjs    端到端冒烟检查
packages/plugin/       Figma 插件
  src/code.ts          网络循环 + 命令 handler 注册表 + 节点序列化/base64
  src/protocol-types.d.ts  把协议类型提升为全局类型,让 code.ts 保持零 import
  ui.html              状态面板(纯展示,不参与网络)
  scripts/stamp-build.mjs     把源码哈希写进产物的 build id
  scripts/verify-bundle.mjs   保证产物是无 import/export 的经典脚本
  scripts/test-serializer.mjs vm + 桩 figma,直接测内部序列化函数
docs/plan.md           架构取证的规划文档
docs/使用说明.md        使用者视角:装机、读面板、重载、排障(面向用插件的人)
PRIVACY.md             隐私政策:插件只访问 localhost,作者不接收任何数据

工具

模型看到的名字带 mcp__figma__ 前缀(DSH 的 MCP 命名空间)。

工具

作用

status

链路状态、文件、页面、当前选区、本插件支持的命令清单。任何 Figma 操作失败时先调它

ping

往返测延迟,不碰文档;用来区分"链路慢"和"Figma 慢"

describe

结构化读画布:深度受限的节点树,含坐标尺寸、auto-layout、fills、文本、变量绑定。带 depth/limit/fields 三重封顶,截断时明说

screenshot

导出 PNG 作为图片进入模型上下文——这是模型唯一能"看见"自己改动结果的途径

apply

批量写画布:create / update / move / rename / delete。一次调用 = 一个 undo 步;create 可内联 children 一次搭出整棵树,并可用 ref 让后续操作引用它

tokens

设计变量:list 读集合与变量、audit 找出手写色值并指出「哪个变量已经恰好等于这个值」、bind 绑定、create 新建、delete 删除(仍被绑定的变量会拒绝删除并告知用在哪)

describe 说的是文档声称的样子,screenshot 展示的是用户实际看到的样子。 两者之间的落差就是设计 bug 的藏身处,所以改完一定要截图,而不是只看 JSON。

图像约束

  • 每张图默认调 2x,像素预算 1.6M(超出自动降 scale,并在图注里报出实际值),单次最多 4 张

  • 每个导出失败的节点会逐个列出原因——静默丢图对模型来说和"画布是空的"长得一样

  • 插件沙箱没有 btoa/TextEncoder,base64 是手写的(src/code.ts


现状

已实现 status / ping / describe / screenshot / apply / tokens。 后续按 docs/plan.md 推进:components(系统化)→ script(逃生舱)→ 发布。

使用者视角的操作说明(装机、读面板、排障)见 docs/使用说明.md; 数据去向见 PRIVACY.md

与规划的一处偏离:规划里 text 是独立工具(理由是"字体是最大的坑"),实现时并入 apply。 理由:apply 本来就必须处理 TEXT 节点,独立工具会让同一件事有两种写法、并长期占两份 schema token。 字体风险在 apply 内部处理——loadFontAsync + fallback 链(Inter → Noto Sans SC → Arial), 且每次替换都写进结果的 notes,不会静默换字体。

改完 profile 补丁必须重启 DSH。 实测 patchReload: "live" 不会重载补丁里的 host 插件 ——改完等 30s,MCP server 进程连 pid 都不变。改配置前先用 --dump-config 离线自检。

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables AI coding agents to read and write a user's Figma file through the Figma Plugin API, offline and privately, without API tokens or rate limits.
    32 npm
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables MCP-compatible AI clients to read and modify the user's currently open Figma file by executing JavaScript in Figma's sandbox, all through a local bridge with status monitoring, node jumping, and automatic rollback on errors.
    1
    MIT
  • A
    license
    Not graded
    quality
    A
    maintenance
    Enables AI coding assistants to draw UI directly on a Figma Desktop canvas and read existing designs back as structured JSON, tokens, CSS, and screenshots, all over a localhost bridge without external API keys.
    398 npm
    MIT