@autobest-ui/agent 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +69 -10
- package/mcp/figma-mcp-bridge/LICENSE +21 -0
- package/mcp/figma-mcp-bridge/README.md +25 -0
- package/mcp/figma-mcp-bridge/config.toml.example +10 -0
- package/mcp/figma-mcp-bridge/index.test.js +47 -0
- package/mcp/figma-mcp-bridge/package.json +16 -0
- package/mcp/figma-mcp-bridge/skills/figma-bridge/SKILL.md +98 -0
- package/mcp/figma-mcp-bridge/src/index.js +68 -0
- package/mcp/figma-mcp-bridge/src/server.js +159 -0
- package/mcp/figma-mcp-bridge/src/tools/context.js +75 -0
- package/mcp/figma-mcp-bridge/src/tools/index.js +1727 -0
- package/mcp/figma-mcp-bridge/src/tools/mutations.js +4423 -0
- package/mcp/figma-mcp-bridge/src/tools/nodes.js +78 -0
- package/mcp/figma-mcp-bridge/src/tools/pages.js +55 -0
- package/mcp/figma-mcp-bridge/src/websocket.js +255 -0
- package/mcp/rag-mcp-bridge/README.md +24 -12
- package/package.json +9 -4
- package/plugins/figma-plugin/LICENSE +21 -0
- package/plugins/figma-plugin/README.md +25 -0
- package/plugins/figma-plugin/code.js +6608 -0
- package/plugins/figma-plugin/manifest.json +32 -0
- package/plugins/figma-plugin/scripts/setup.mjs +72 -0
- package/plugins/figma-plugin/scripts/setup.test.mjs +45 -0
- package/plugins/figma-plugin/ui.html +235 -0
package/README.md
CHANGED
|
@@ -20,30 +20,63 @@ npx --yes --package=@autobest-ui/agent@latest autobest-agent-sync react
|
|
|
20
20
|
|
|
21
21
|
## MCP
|
|
22
22
|
|
|
23
|
-
安装 Node.js 20
|
|
23
|
+
安装 Node.js 20 或更高版本后,无需在业务项目中安装依赖。直接在终端验证 MCP 时,必须同时提供对应的环境变量。
|
|
24
|
+
|
|
25
|
+
先启动 PRD Knowledge HTTP API,再验证 RAG MCP:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
RAG_API_BASE_URL="http://127.0.0.1:3000/api/knowledge" \
|
|
29
|
+
npx --yes \
|
|
30
|
+
--package=@autobest-ui/agent@latest \
|
|
31
|
+
autobest-rag-mcp
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
验证 Azure PR MCP:
|
|
24
35
|
|
|
25
36
|
```bash
|
|
26
|
-
|
|
27
|
-
npx --yes
|
|
37
|
+
AZURE_DEVOPS_PAT="your-personal-access-token" \
|
|
38
|
+
npx --yes \
|
|
39
|
+
--package=@autobest-ui/agent@latest \
|
|
40
|
+
autobest-azurepr-mcp
|
|
28
41
|
```
|
|
29
42
|
|
|
30
|
-
|
|
43
|
+
启动成功后,MCP 会通过 stdio 持续等待客户端请求,终端没有输出属于正常状态,可按 `Ctrl+C` 停止。供 Codex 长期使用时,不要只执行上述临时命令,应将完整配置加入 `~/.codex/config.toml` 后重启 Codex。
|
|
44
|
+
|
|
45
|
+
同一个 npm 包暴露三个独立命令:
|
|
31
46
|
|
|
32
47
|
| 命令 | 用途 | 必需配置 |
|
|
33
48
|
| ---------------------- | ------------------------------ | ------------------ |
|
|
34
49
|
| `autobest-rag-mcp` | 连接 PRD Knowledge RAG API | `RAG_API_BASE_URL` |
|
|
35
50
|
| `autobest-azurepr-mcp` | 读取 Azure DevOps Pull Request | `AZURE_DEVOPS_PAT` |
|
|
51
|
+
| `figma-mcp-bridge` | 连接本机 Figma Plugin | 无 |
|
|
36
52
|
|
|
37
53
|
Codex 配置示例分别位于:
|
|
38
54
|
|
|
39
55
|
- [RAG MCP 配置](mcp/rag-mcp-bridge/config.toml.example)
|
|
40
56
|
- [Azure PR MCP 配置](mcp/azurepr-mcp-bridge/config.toml.example)
|
|
57
|
+
- [Figma MCP 配置](mcp/figma-mcp-bridge/config.toml.example)
|
|
41
58
|
|
|
42
59
|
生产环境可将 `@latest` 替换为明确版本,以固定 MCP 行为。
|
|
43
60
|
|
|
44
61
|
如果 `@autobest-ui` 发布在私有 npm registry,使用前需要在 npm 的 `.npmrc` 中配置该 scope 对应的 registry 和认证信息。
|
|
45
62
|
|
|
46
|
-
## Plugin
|
|
63
|
+
## Figma Plugin
|
|
64
|
+
|
|
65
|
+
Figma Plugin 与 `figma-mcp-bridge` 分目录维护,并通过同一个 npm 包提供两个独立命令。安装或更新到稳定目录:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
npx --yes --package=@autobest-ui/agent@latest figma-plugin
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
命令会输出 `manifest.json` 的稳定路径。首次安装后,在 Figma 中通过 **Plugins -> Development -> Import plugin from manifest** 导入该文件。详细说明见 [Figma Plugin 使用说明](plugins/figma-plugin/README.md)。
|
|
72
|
+
|
|
73
|
+
卸载本地文件:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
npx --yes --package=@autobest-ui/agent@latest figma-plugin uninstall
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Codex Plugin
|
|
47
80
|
|
|
48
81
|
Autobest Delivery 将交付闭环 Skills 和隔离的 Playwright MCP 运行器作为一个 Codex Plugin 安装。需要 Node.js 20 或更高版本,以及支持 `codex plugin` 命令的 Codex CLI。
|
|
49
82
|
|
|
@@ -126,7 +159,7 @@ Common Skills 会写入 `~/.agents/skills`,React Skills 会写入目标项目
|
|
|
126
159
|
RAG MCP:
|
|
127
160
|
|
|
128
161
|
```bash
|
|
129
|
-
RAG_API_BASE_URL=http://127.0.0.1:3000 \
|
|
162
|
+
RAG_API_BASE_URL="http://127.0.0.1:3000/api/knowledge" \
|
|
130
163
|
npx --yes \
|
|
131
164
|
--package="$AGENT_TARBALL_PATH" \
|
|
132
165
|
autobest-rag-mcp
|
|
@@ -141,6 +174,22 @@ npx --yes \
|
|
|
141
174
|
autobest-azurepr-mcp
|
|
142
175
|
```
|
|
143
176
|
|
|
177
|
+
Figma MCP:
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
npx --yes \
|
|
181
|
+
--package="$AGENT_TARBALL_PATH" \
|
|
182
|
+
figma-mcp-bridge
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Figma Plugin:
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
npx --yes \
|
|
189
|
+
--package="$AGENT_TARBALL_PATH" \
|
|
190
|
+
figma-plugin
|
|
191
|
+
```
|
|
192
|
+
|
|
144
193
|
MCP 使用 stdio 协议,启动后持续等待客户端请求属于正常行为,可按 `Ctrl+C` 停止。
|
|
145
194
|
|
|
146
195
|
### 5. 在 Codex 中联调 MCP
|
|
@@ -157,7 +206,7 @@ args = [
|
|
|
157
206
|
]
|
|
158
207
|
|
|
159
208
|
[mcp_servers.rag-mcp-bridge.env]
|
|
160
|
-
RAG_API_BASE_URL = "http://127.0.0.1:3000"
|
|
209
|
+
RAG_API_BASE_URL = "http://127.0.0.1:3000/api/knowledge"
|
|
161
210
|
|
|
162
211
|
[mcp_servers.azurepr-mcp-bridge]
|
|
163
212
|
command = "npx"
|
|
@@ -167,11 +216,21 @@ args = [
|
|
|
167
216
|
"autobest-azurepr-mcp"
|
|
168
217
|
]
|
|
169
218
|
|
|
170
|
-
[
|
|
171
|
-
|
|
219
|
+
env_vars = ["AZURE_DEVOPS_PAT"]
|
|
220
|
+
|
|
221
|
+
[mcp_servers.figma-mcp-bridge]
|
|
222
|
+
command = "npx"
|
|
223
|
+
args = [
|
|
224
|
+
"--yes",
|
|
225
|
+
"--package=/absolute/path/to/autobest-ui-agent-x.y.z.tgz",
|
|
226
|
+
"figma-mcp-bridge"
|
|
227
|
+
]
|
|
228
|
+
startup_timeout_sec = 30
|
|
229
|
+
tool_timeout_sec = 120
|
|
230
|
+
enabled = true
|
|
172
231
|
```
|
|
173
232
|
|
|
174
|
-
|
|
233
|
+
配置 Azure PR MCP 前,还需要在启动 Codex 的环境中设置 `AZURE_DEVOPS_PAT`。修改 `config.toml` 后重启 Codex,再从新会话调用对应 MCP 工具。重新打包后如果 tarball 文件名或路径发生变化,需要同步更新 `config.toml`。
|
|
175
234
|
|
|
176
235
|
## 本地验证
|
|
177
236
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Magic Spells
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# figma-mcp-bridge
|
|
2
|
+
|
|
3
|
+
`figma-mcp-bridge` 是本地 STDIO MCP 服务。它在 Codex 与 Figma Plugin 之间建立 WebSocket 桥接,使 Agent 可以读取和修改当前打开的 Figma Design 或 FigJam 文档。
|
|
4
|
+
|
|
5
|
+
## 安装与配置
|
|
6
|
+
|
|
7
|
+
先安装 Figma Plugin:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx --yes --package=@autobest-ui/agent@latest figma-plugin
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
然后将 [config.toml.example](config.toml.example) 中的配置加入 `~/.codex/config.toml`,重启 Codex。Codex 会通过 npm 安装并启动 MCP:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx --yes --package=@autobest-ui/agent@latest figma-mcp-bridge
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
MCP 使用 stdio 通信,启动后持续等待客户端请求属于正常状态。WebSocket 默认从 `3055` 端口开始;如果端口已被占用,会依次尝试到 `3070`。
|
|
20
|
+
|
|
21
|
+
## 连接 Figma
|
|
22
|
+
|
|
23
|
+
在 Figma 中通过 **Plugins -> Development -> Import plugin from manifest** 导入安装器输出的 `manifest.json`。打开目标文件并运行插件,将插件显示的端口设置为 MCP 实际监听端口。状态显示 `Connected` 后即可使用。
|
|
24
|
+
|
|
25
|
+
生产环境可将 `@latest` 替换为明确版本,以固定 MCP 行为。
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
[mcp_servers.figma-mcp-bridge]
|
|
2
|
+
command = "npx"
|
|
3
|
+
args = ["--yes", "--package=@autobest-ui/agent@latest", "figma-mcp-bridge"]
|
|
4
|
+
startup_timeout_sec = 30
|
|
5
|
+
tool_timeout_sec = 120
|
|
6
|
+
enabled = true
|
|
7
|
+
|
|
8
|
+
[mcp_servers.figma-mcp-bridge.env]
|
|
9
|
+
# 可选。默认从 3055 开始;端口占用时服务会依次尝试到 3070。
|
|
10
|
+
FIGMA_BRIDGE_PORT = "3055"
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import test from 'node:test';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
7
|
+
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
8
|
+
|
|
9
|
+
const directory = path.dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
|
|
11
|
+
test('ships a valid skill frontmatter', async () => {
|
|
12
|
+
const skill = await readFile(path.join(directory, 'skills', 'figma-bridge', 'SKILL.md'), 'utf8');
|
|
13
|
+
assert.match(skill, /^---\nname: figma-bridge\ndescription: [^\n]+\n---\n/);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test('initializes and exposes Figma tools and skill resources over stdio', async () => {
|
|
17
|
+
const client = new Client({
|
|
18
|
+
name: 'figma-mcp-bridge-test',
|
|
19
|
+
version: '1.0.0'
|
|
20
|
+
});
|
|
21
|
+
const transport = new StdioClientTransport({
|
|
22
|
+
command: process.execPath,
|
|
23
|
+
args: [path.join(directory, 'src', 'index.js')],
|
|
24
|
+
cwd: directory,
|
|
25
|
+
env: {
|
|
26
|
+
...process.env,
|
|
27
|
+
FIGMA_BRIDGE_PORT: '0'
|
|
28
|
+
},
|
|
29
|
+
stderr: 'pipe'
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
await client.connect(transport);
|
|
34
|
+
assert.equal(client.getServerVersion()?.name, 'figma-mcp-bridge');
|
|
35
|
+
assert.match(client.getInstructions() ?? '', /Figma MCP Bridge v0\.4\.0/);
|
|
36
|
+
|
|
37
|
+
const { tools } = await client.listTools();
|
|
38
|
+
assert.ok(tools.length >= 90);
|
|
39
|
+
assert.ok(tools.some(({ name }) => name === 'figma_get_context'));
|
|
40
|
+
assert.ok(tools.some(({ name }) => name === 'figma_create_frame'));
|
|
41
|
+
|
|
42
|
+
const { resources } = await client.listResources();
|
|
43
|
+
assert.ok(resources.some(({ uri }) => uri === 'skill://figma-bridge/SKILL.md'));
|
|
44
|
+
} finally {
|
|
45
|
+
await client.close();
|
|
46
|
+
}
|
|
47
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@autobest/figma-mcp-bridge",
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"description": "STDIO MCP server bridging Codex to the Autobest Figma plugin",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "src/index.js",
|
|
8
|
+
"engines": {
|
|
9
|
+
"node": ">=20"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@modelcontextprotocol/sdk": "1.30.0",
|
|
13
|
+
"ws": "8.18.3",
|
|
14
|
+
"zod": "4.4.3"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: figma-bridge
|
|
3
|
+
description: Operate figma-mcp-bridge for reliable Figma Design and FigJam reads, edits, verification, and export review.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Figma MCP Bridge — Operation Skill
|
|
7
|
+
|
|
8
|
+
How to work through the figma-mcp-bridge effectively and honestly. Read this
|
|
9
|
+
before any write-heavy design session. Applies to bridge **v0.4.0+**.
|
|
10
|
+
|
|
11
|
+
## Verification discipline — the core of this skill
|
|
12
|
+
|
|
13
|
+
**1. Verify bindings by readback, not by assumption.**
|
|
14
|
+
`figma_get_nodes` (full depth) returns `boundVariables` (node-level variable
|
|
15
|
+
bindings), `explicitVariableModes` (pinned modes), `layoutWrap`,
|
|
16
|
+
`counterAxisSpacing`, and `clipsContent`. After binding, pinning, or layout
|
|
17
|
+
work, read the node back and confirm the field says what you intended. Mutating
|
|
18
|
+
tools echo readbacks in their responses (`verified: true`, resulting
|
|
19
|
+
`explicitVariableModes`, per-side stroke weights, etc.) — check them.
|
|
20
|
+
|
|
21
|
+
**2. Export the render and look at it before reporting done.**
|
|
22
|
+
`figma_export_node` writes the image to disk and returns the path — Read the
|
|
23
|
+
file and actually look at it. Property readback cannot see composition: real
|
|
24
|
+
sessions passed every numeric check while renders showed a portrait video well
|
|
25
|
+
where a 16:9 one belonged, a heading breaking mid-word, and a button hugging at
|
|
26
|
+
140px instead of spanning its form. Looking catches what measuring cannot.
|
|
27
|
+
|
|
28
|
+
**3. Report honestly.**
|
|
29
|
+
- If a tool errors, report the failure — never record the work as done.
|
|
30
|
+
- If a readback disagrees with a success response, report that too.
|
|
31
|
+
- Never paper over a missing capability by hardcoding a value.
|
|
32
|
+
- The bridge errors instead of silently no-oping (see error codes below);
|
|
33
|
+
treat those errors as information about Figma's real constraints, not as
|
|
34
|
+
obstacles to retry around.
|
|
35
|
+
|
|
36
|
+
## Errors that mean "Figma forbids this" (don't retry — change approach)
|
|
37
|
+
|
|
38
|
+
- `INSTANCE_SUBLAYER_RESTRICTED` — size binds, resizes, and reorders inside an
|
|
39
|
+
instance are not allowed. Make the change on the component master; it flows
|
|
40
|
+
to every instance.
|
|
41
|
+
- `MODE_NOT_FOUND` / `COLLECTION_NOT_FOUND` — the modeId/collectionId is wrong;
|
|
42
|
+
the error lists valid modes.
|
|
43
|
+
- `WRONG_EDITOR` / `FIGMA_DESIGN_ONLY` — the tool is gated to FigJam or Figma
|
|
44
|
+
Design; the error names the current editor type (there are five: figma,
|
|
45
|
+
figjam, dev, slides, buzz).
|
|
46
|
+
- `BIND_NOT_APPLIED` / `STYLE_NOT_APPLIED` / `RESIZE_NO_OP` /
|
|
47
|
+
`REORDER_FAILED` / `LIMIT_NOT_APPLIED` — the write did not land and the
|
|
48
|
+
bridge is telling you instead of pretending. Report it.
|
|
49
|
+
|
|
50
|
+
## Tool guidance
|
|
51
|
+
|
|
52
|
+
- **Sizing a child to its parent:** use `figma_set_layout_align: STRETCH`, not
|
|
53
|
+
`figma_resize_nodes`. STRETCH preserves width/height variable binds; resize
|
|
54
|
+
may destroy them (the bridge re-applies and warns, but STRETCH avoids the
|
|
55
|
+
problem entirely).
|
|
56
|
+
- **Pinning variable modes:** `figma_set_variable_mode` sets or clears
|
|
57
|
+
(`clear: true`) an explicit mode per collection on nodes *and pages*. Pins
|
|
58
|
+
belong on preview/page frames. Never pin a component master — every instance
|
|
59
|
+
inherits it, per-collection, and the partial correctness hides the fault.
|
|
60
|
+
- **Text styles:** create with `figma_create_text_style`, bind `fontSize` (and
|
|
61
|
+
other text fields) to variables via `figma_set_variable` with `styleId`,
|
|
62
|
+
apply with `figma_apply_style`, delete with `figma_delete_style`.
|
|
63
|
+
- **Borders on one side:** `figma_set_strokes` per-side weights
|
|
64
|
+
(`strokeTopWeight` etc.); `strokes` may be omitted to change weights only.
|
|
65
|
+
A mixed weight reads back as the string `'MIXED'` plus the four per-side
|
|
66
|
+
values.
|
|
67
|
+
- **Min/max sizes:** `figma_set_size_limits`; pass explicit `null` to clear.
|
|
68
|
+
`figma_unbind_variable` on a min/max field also clears the residual literal.
|
|
69
|
+
- **Reordering:** `figma_reorder_node` `position` is the final index among
|
|
70
|
+
siblings (0 = back, `childCount-1` = front), verified by readback.
|
|
71
|
+
- **Hiding:** `figma_set_visible` — don't fake it with opacity 0.
|
|
72
|
+
- **Exports:** the returned `path` is the deliverable; Read it. No need to
|
|
73
|
+
inflate `scale` to force anything.
|
|
74
|
+
- **Constraints:** set `figma_set_constraints` BEFORE converting the parent to
|
|
75
|
+
auto-layout; inside auto-layout parents it's rejected, and it can't be
|
|
76
|
+
overridden on instance sublayers at all.
|
|
77
|
+
|
|
78
|
+
## Auto-layout facts that bite
|
|
79
|
+
|
|
80
|
+
- `SPACE_BETWEEN` is inert when any child has `layoutGrow: 1` (no free space
|
|
81
|
+
to distribute) — `itemSpacing` is ignored then too.
|
|
82
|
+
- `figma_set_layout_align: CENTER` is a no-op on a stretched child — use
|
|
83
|
+
`INHERIT` plus the parent's `counterAxisAlignItems: CENTER`.
|
|
84
|
+
- Reading wrap: `layoutWrap` and `counterAxisSpacing` come back in full node
|
|
85
|
+
reads; compact children include x/y for geometry checks.
|
|
86
|
+
- `figma_set_text` does not decode HTML entities — send literal characters
|
|
87
|
+
(`&`, not `&`).
|
|
88
|
+
|
|
89
|
+
## Concurrency (multi-agent sessions)
|
|
90
|
+
|
|
91
|
+
- One WebSocket to one open document. More than ~2 write-heavy agents produces
|
|
92
|
+
transient `Unable to establish connection` errors — retry them; if an agent
|
|
93
|
+
stalls completely, kill and restart it.
|
|
94
|
+
- **Never call `figma_set_current_page` or `figma_set_selection` from a
|
|
95
|
+
sub-agent** — it yanks the shared view out from under every other client.
|
|
96
|
+
- The plugin must be connected to THIS server's port: check
|
|
97
|
+
`figma_server_info` / `figma_get_context` first and surface the port to the
|
|
98
|
+
user if disconnected.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Figma MCP Bridge - Entry Point
|
|
5
|
+
*
|
|
6
|
+
* Starts the WebSocket server for Figma plugin communication
|
|
7
|
+
* and the MCP server for Agent communication.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
11
|
+
import { FigmaBridge } from './websocket.js';
|
|
12
|
+
import { createServer } from './server.js';
|
|
13
|
+
|
|
14
|
+
const PORT = parseInt(process.env.FIGMA_BRIDGE_PORT || '3055', 10);
|
|
15
|
+
|
|
16
|
+
async function main() {
|
|
17
|
+
// eslint-disable-next-line no-console
|
|
18
|
+
console.error('[FigmaMCP] Starting Figma MCP Bridge...');
|
|
19
|
+
|
|
20
|
+
// Create and start WebSocket bridge
|
|
21
|
+
const bridge = new FigmaBridge(PORT);
|
|
22
|
+
await bridge.start();
|
|
23
|
+
|
|
24
|
+
// Log connection events
|
|
25
|
+
bridge.on('connected', info => {
|
|
26
|
+
// eslint-disable-next-line no-console
|
|
27
|
+
console.error(`[FigmaMCP] Figma connected: ${info.fileName}`);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
bridge.on('disconnected', () => {
|
|
31
|
+
// eslint-disable-next-line no-console
|
|
32
|
+
console.error('[FigmaMCP] Figma disconnected');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Create MCP server
|
|
36
|
+
const server = createServer(bridge);
|
|
37
|
+
|
|
38
|
+
// Connect to stdio transport (Agent communication)
|
|
39
|
+
const transport = new StdioServerTransport();
|
|
40
|
+
await server.connect(transport);
|
|
41
|
+
|
|
42
|
+
// eslint-disable-next-line no-console
|
|
43
|
+
console.error(
|
|
44
|
+
`[FigmaMCP] MCP server running, WebSocket bound to port ${bridge.port}. Waiting for Figma plugin connection...`
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
// Graceful shutdown helper
|
|
48
|
+
const shutdown = async reason => {
|
|
49
|
+
// eslint-disable-next-line no-console
|
|
50
|
+
console.error(`[FigmaMCP] Shutting down (${reason})...`);
|
|
51
|
+
await bridge.stop();
|
|
52
|
+
process.exit(0);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// Handle graceful shutdown
|
|
56
|
+
process.on('SIGINT', () => shutdown('SIGINT'));
|
|
57
|
+
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
|
58
|
+
|
|
59
|
+
// Handle stdio close (when the Agent closes the connection)
|
|
60
|
+
process.stdin.on('close', () => shutdown('stdin closed'));
|
|
61
|
+
transport.onclose = () => shutdown('transport closed');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
main().catch(error => {
|
|
65
|
+
// eslint-disable-next-line no-console
|
|
66
|
+
console.error('[FigmaMCP] Fatal error:', error);
|
|
67
|
+
process.exit(1);
|
|
68
|
+
});
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Server setup
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { readFileSync, readdirSync, existsSync } from 'fs';
|
|
6
|
+
import { fileURLToPath } from 'url';
|
|
7
|
+
import { dirname, join } from 'path';
|
|
8
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
9
|
+
import { registerTools } from './tools/index.js';
|
|
10
|
+
|
|
11
|
+
// Read package.json once at module load so version stays in sync with the published artifact
|
|
12
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
let pkgVersion = '0.0.0';
|
|
14
|
+
try {
|
|
15
|
+
const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8'));
|
|
16
|
+
pkgVersion = pkg.version;
|
|
17
|
+
} catch (_) {
|
|
18
|
+
// Fall back to placeholder if package.json can't be read
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Create and configure the MCP server
|
|
23
|
+
* @param {FigmaBridge} bridge - Figma bridge instance (must be started — bridge.port must reflect the bound port)
|
|
24
|
+
* @returns {McpServer} Configured MCP server
|
|
25
|
+
*/
|
|
26
|
+
export function createServer(bridge) {
|
|
27
|
+
const port = bridge.port;
|
|
28
|
+
const instructions = `# Figma MCP Bridge v${pkgVersion}
|
|
29
|
+
|
|
30
|
+
## CONNECTION INFO — CHECK FIRST
|
|
31
|
+
|
|
32
|
+
This MCP server is bridging an Agent to Figma via a WebSocket on **port ${port}**.
|
|
33
|
+
|
|
34
|
+
Multiple Agent sessions can run concurrently and the bridge falls back through ports 3055–3070, so the port may differ from the default. **At the start of any new Figma-related conversation, call \`figma_get_context\` to check connection state. If it returns \`connected: false\`, proactively tell the user:**
|
|
35
|
+
|
|
36
|
+
> "The Figma MCP bridge is running on port **${port}**. Open the Figma plugin and set its port input to **${port}**, then re-run the plugin if it was already open."
|
|
37
|
+
|
|
38
|
+
Don't make the user discover the port themselves — surface it the first time you notice they aren't connected.
|
|
39
|
+
|
|
40
|
+
## SKILLS — READ BEFORE WRITE-HEAVY WORK
|
|
41
|
+
|
|
42
|
+
This server ships its own skills as MCP resources. **Before any session that creates or edits design content (not just single reads), read \`skill://figma-bridge/SKILL.md\`** — it covers verification discipline (readback + export-and-look), the error codes that mean "Figma forbids this", bind-preserving sizing, mode pinning, concurrency rules, and auto-layout traps. List resources to discover any additional skills shipped with this server version.
|
|
43
|
+
|
|
44
|
+
## FigJam Support
|
|
45
|
+
|
|
46
|
+
This server supports both Figma design files AND FigJam files. FigJam-specific tools (sticky notes, flowchart shapes, connectors, tables, code blocks, link previews) are gated to FigJam files and return a \`WRONG_EDITOR\` error if called against a Figma design file.
|
|
47
|
+
|
|
48
|
+
**Editor-restricted tools:**
|
|
49
|
+
- FigJam-only (return \`WRONG_EDITOR\` in design files): all sticky / shape-with-text / connector / table / code-block / link-preview tools
|
|
50
|
+
- Figma Design only (return \`FIGMA_DESIGN_ONLY\` in FigJam): \`figma_create_page\`, \`figma_duplicate_page\`. FigJam files have pages but the plugin API does not expose page creation; pages must be created via the FigJam UI by the user.
|
|
51
|
+
|
|
52
|
+
For flowcharts in FigJam:
|
|
53
|
+
- \`figma_create_shape_with_text\` with \`shapeType\` (ROUNDED_RECTANGLE for processes, DIAMOND for decisions, ENG_DATABASE for data stores, etc.)
|
|
54
|
+
- \`figma_create_connector\` with \`{ start: { nodeId, magnet: 'AUTO' }, end: { nodeId, magnet: 'AUTO' } }\` — \`endCap\` defaults to \`ARROW_EQUILATERAL\` so connectors look like arrows
|
|
55
|
+
- Wrap the diagram in a \`figma_create_section\` for grouping
|
|
56
|
+
|
|
57
|
+
## IMPORTANT: Always Use Search Tools First
|
|
58
|
+
|
|
59
|
+
When working with Figma documents, ALWAYS prefer search tools over bulk retrieval:
|
|
60
|
+
|
|
61
|
+
### For Variables
|
|
62
|
+
- **USE**: \`figma_search_variables\` (~500 tokens) - Filter by name pattern, type, collection
|
|
63
|
+
- **AVOID**: \`figma_get_local_variables\` (25k+ tokens, may truncate)
|
|
64
|
+
|
|
65
|
+
Example:
|
|
66
|
+
\`\`\`
|
|
67
|
+
figma_search_variables({ namePattern: "colors/*", type: "COLOR", compact: true })
|
|
68
|
+
\`\`\`
|
|
69
|
+
|
|
70
|
+
### For Nodes
|
|
71
|
+
- **USE**: \`figma_search_nodes\` - Find frames/elements by name within a scope
|
|
72
|
+
- **USE**: \`figma_get_children\` - Browse hierarchy one level at a time
|
|
73
|
+
- **AVOID**: Repeated \`figma_get_nodes\` calls to traverse the tree
|
|
74
|
+
|
|
75
|
+
Example:
|
|
76
|
+
\`\`\`
|
|
77
|
+
figma_search_nodes({ parentId: "0:1", nameContains: "Button", types: ["FRAME", "COMPONENT"] })
|
|
78
|
+
\`\`\`
|
|
79
|
+
|
|
80
|
+
### For Components
|
|
81
|
+
- **USE**: \`figma_search_components\` - Find by name pattern
|
|
82
|
+
- Returns compact results with component metadata
|
|
83
|
+
|
|
84
|
+
### For Styles
|
|
85
|
+
- **USE**: \`figma_search_styles\` - Find by name and type
|
|
86
|
+
- **AVOID**: \`figma_get_local_styles\` for large documents
|
|
87
|
+
|
|
88
|
+
## Workflow
|
|
89
|
+
|
|
90
|
+
1. **Start with context**: Call \`figma_get_context\` to understand the current document and selection
|
|
91
|
+
2. **Search first**: Use search tools to find specific elements by name
|
|
92
|
+
3. **Get details only when needed**: Use \`figma_get_nodes\` with \`depth: "minimal"\` or \`"compact"\` for efficiency
|
|
93
|
+
4. **Use full depth sparingly**: Only use \`depth: "full"\` when you need all node properties
|
|
94
|
+
|
|
95
|
+
## Token Optimization
|
|
96
|
+
|
|
97
|
+
| Tool | Tokens | Use Case |
|
|
98
|
+
|------|--------|----------|
|
|
99
|
+
| \`figma_search_*\` | ~50/result | Finding specific elements |
|
|
100
|
+
| \`figma_get_children\` | ~50/node | Browsing hierarchy |
|
|
101
|
+
| \`figma_get_nodes\` (minimal) | ~100/node | Tree traversal |
|
|
102
|
+
| \`figma_get_nodes\` (full) | ~500/node | Detailed inspection |
|
|
103
|
+
| \`figma_get_local_variables\` | 25k+ | AVOID - use search instead
|
|
104
|
+
`;
|
|
105
|
+
const server = new McpServer(
|
|
106
|
+
{
|
|
107
|
+
name: 'figma-mcp-bridge',
|
|
108
|
+
version: pkgVersion
|
|
109
|
+
},
|
|
110
|
+
{ instructions }
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
// Register all Figma tools
|
|
114
|
+
registerTools(server, bridge);
|
|
115
|
+
|
|
116
|
+
// Serve every skills/<name>/SKILL.md as an MCP resource (skill://<name>/SKILL.md)
|
|
117
|
+
// so agents get the bridge's operating knowledge without installing anything.
|
|
118
|
+
registerSkillResources(server);
|
|
119
|
+
|
|
120
|
+
return server;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Register the markdown skills shipped in skills/ as MCP resources.
|
|
125
|
+
* Files are read lazily per request so a dev checkout picks up edits
|
|
126
|
+
* without a server restart. Missing dir (or a race on a deleted file)
|
|
127
|
+
* degrades to no/absent resources rather than a crash.
|
|
128
|
+
* @param {McpServer} server
|
|
129
|
+
*/
|
|
130
|
+
function registerSkillResources(server) {
|
|
131
|
+
const skillsDir = join(__dirname, '..', 'skills');
|
|
132
|
+
if (!existsSync(skillsDir)) return;
|
|
133
|
+
|
|
134
|
+
for (const entry of readdirSync(skillsDir, { withFileTypes: true })) {
|
|
135
|
+
if (!entry.isDirectory()) continue;
|
|
136
|
+
const skillPath = join(skillsDir, entry.name, 'SKILL.md');
|
|
137
|
+
if (!existsSync(skillPath)) continue;
|
|
138
|
+
|
|
139
|
+
const uri = `skill://${entry.name}/SKILL.md`;
|
|
140
|
+
server.registerResource(
|
|
141
|
+
entry.name,
|
|
142
|
+
uri,
|
|
143
|
+
{
|
|
144
|
+
title: `Skill: ${entry.name}`,
|
|
145
|
+
description: `Operating skill shipped with figma-mcp-bridge. Read before write-heavy ${entry.name} work.`,
|
|
146
|
+
mimeType: 'text/markdown'
|
|
147
|
+
},
|
|
148
|
+
async () => ({
|
|
149
|
+
contents: [
|
|
150
|
+
{
|
|
151
|
+
uri,
|
|
152
|
+
mimeType: 'text/markdown',
|
|
153
|
+
text: readFileSync(skillPath, 'utf8')
|
|
154
|
+
}
|
|
155
|
+
]
|
|
156
|
+
})
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* figma_get_context tool
|
|
3
|
+
* Returns current document context including connection status, file info, and selection
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export const contextTool = {
|
|
7
|
+
name: 'figma_get_context',
|
|
8
|
+
description:
|
|
9
|
+
'Get the current Figma document context including file info, current page, and selection. Use this to understand what document is open and what the user has selected.',
|
|
10
|
+
inputSchema: {
|
|
11
|
+
type: 'object',
|
|
12
|
+
properties: {},
|
|
13
|
+
required: []
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export async function handleGetContext(bridge) {
|
|
18
|
+
if (!bridge.isConnected()) {
|
|
19
|
+
return {
|
|
20
|
+
content: [
|
|
21
|
+
{
|
|
22
|
+
type: 'text',
|
|
23
|
+
text: JSON.stringify(
|
|
24
|
+
{
|
|
25
|
+
connected: false,
|
|
26
|
+
message: 'Figma plugin is not connected. Please open Figma and run the Autobest Figma Plugin.'
|
|
27
|
+
},
|
|
28
|
+
null,
|
|
29
|
+
2
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
]
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
const result = await bridge.sendCommand('get_context', {});
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
content: [
|
|
41
|
+
{
|
|
42
|
+
type: 'text',
|
|
43
|
+
text: JSON.stringify(
|
|
44
|
+
{
|
|
45
|
+
connected: true,
|
|
46
|
+
...result
|
|
47
|
+
},
|
|
48
|
+
null,
|
|
49
|
+
2
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
]
|
|
53
|
+
};
|
|
54
|
+
} catch (error) {
|
|
55
|
+
return {
|
|
56
|
+
content: [
|
|
57
|
+
{
|
|
58
|
+
type: 'text',
|
|
59
|
+
text: JSON.stringify(
|
|
60
|
+
{
|
|
61
|
+
connected: true,
|
|
62
|
+
error: {
|
|
63
|
+
code: error.code || 'UNKNOWN_ERROR',
|
|
64
|
+
message: error.message
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
null,
|
|
68
|
+
2
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
],
|
|
72
|
+
isError: true
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
}
|