@autobest-ui/agent 1.0.0 → 1.0.2

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.
Files changed (30) hide show
  1. package/README.md +69 -10
  2. package/mcp/azurepr-mcp-bridge/azure-devops.js +82 -112
  3. package/mcp/azurepr-mcp-bridge/index.js +21 -25
  4. package/mcp/azurepr-mcp-bridge/index.test.js +50 -59
  5. package/mcp/figma-mcp-bridge/LICENSE +21 -0
  6. package/mcp/figma-mcp-bridge/README.md +25 -0
  7. package/mcp/figma-mcp-bridge/config.toml.example +10 -0
  8. package/mcp/figma-mcp-bridge/index.test.js +47 -0
  9. package/mcp/figma-mcp-bridge/package.json +16 -0
  10. package/mcp/figma-mcp-bridge/skills/figma-bridge/SKILL.md +98 -0
  11. package/mcp/figma-mcp-bridge/src/index.js +68 -0
  12. package/mcp/figma-mcp-bridge/src/server.js +159 -0
  13. package/mcp/figma-mcp-bridge/src/tools/context.js +75 -0
  14. package/mcp/figma-mcp-bridge/src/tools/index.js +2148 -0
  15. package/mcp/figma-mcp-bridge/src/tools/mutations.js +5829 -0
  16. package/mcp/figma-mcp-bridge/src/tools/nodes.js +99 -0
  17. package/mcp/figma-mcp-bridge/src/tools/pages.js +70 -0
  18. package/mcp/figma-mcp-bridge/src/websocket.js +255 -0
  19. package/mcp/rag-mcp-bridge/README.md +24 -12
  20. package/mcp/rag-mcp-bridge/config.toml.example +1 -1
  21. package/mcp/rag-mcp-bridge/index.js +80 -125
  22. package/mcp/rag-mcp-bridge/index.test.js +21 -25
  23. package/package.json +9 -4
  24. package/plugins/figma-plugin/LICENSE +21 -0
  25. package/plugins/figma-plugin/README.md +25 -0
  26. package/plugins/figma-plugin/code.js +6608 -0
  27. package/plugins/figma-plugin/manifest.json +32 -0
  28. package/plugins/figma-plugin/scripts/setup.mjs +72 -0
  29. package/plugins/figma-plugin/scripts/setup.test.mjs +45 -0
  30. package/plugins/figma-plugin/ui.html +235 -0
@@ -10,40 +10,37 @@
10
10
  * Database access, Markdown parsing, embeddings, and RAG stay in Express.
11
11
  */
12
12
 
13
- import { readdir, stat } from "node:fs/promises";
14
- import path from "node:path";
15
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
16
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
17
- import { z } from "zod";
13
+ import { readdir, stat } from 'node:fs/promises';
14
+ import path from 'node:path';
15
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
16
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
17
+ import { z } from 'zod';
18
18
 
19
19
  const DEFAULT_HTTP_TIMEOUT_MS = 120_000;
20
20
 
21
21
  function apiBaseUrlFromEnvironment() {
22
22
  const configuredUrl = process.env.RAG_API_BASE_URL?.trim();
23
23
  if (!configuredUrl) {
24
- throw new Error(
25
- "RAG_API_BASE_URL is required; configure it in the MCP server environment",
26
- );
24
+ throw new Error('RAG_API_BASE_URL is required; configure it in the MCP server environment');
27
25
  }
28
26
 
29
27
  let url;
30
28
  try {
31
29
  url = new URL(configuredUrl);
32
30
  } catch {
33
- throw new Error("RAG_API_BASE_URL must be a valid URL");
31
+ throw new Error('RAG_API_BASE_URL must be a valid URL');
34
32
  }
35
- if (url.protocol !== "http:" && url.protocol !== "https:") {
36
- throw new Error("RAG_API_BASE_URL must use http or https");
33
+ if (url.protocol !== 'http:' && url.protocol !== 'https:') {
34
+ throw new Error('RAG_API_BASE_URL must use http or https');
37
35
  }
38
- return url.toString().replace(/\/$/, "");
36
+ return url.toString().replace(/\/$/, '');
39
37
  }
40
38
 
41
39
  function httpTimeoutFromEnvironment() {
42
- const configuredTimeout =
43
- process.env.RAG_MCP_HTTP_TIMEOUT_MS ?? String(DEFAULT_HTTP_TIMEOUT_MS);
40
+ const configuredTimeout = process.env.RAG_MCP_HTTP_TIMEOUT_MS ?? String(DEFAULT_HTTP_TIMEOUT_MS);
44
41
  const timeout = Number(configuredTimeout);
45
42
  if (!Number.isSafeInteger(timeout) || timeout <= 0) {
46
- throw new Error("RAG_MCP_HTTP_TIMEOUT_MS must be a positive integer");
43
+ throw new Error('RAG_MCP_HTTP_TIMEOUT_MS must be a positive integer');
47
44
  }
48
45
  return timeout;
49
46
  }
@@ -52,31 +49,18 @@ const API_BASE_URL = apiBaseUrlFromEnvironment();
52
49
  const HTTP_TIMEOUT_MS = httpTimeoutFromEnvironment();
53
50
 
54
51
  const pathsSchema = z
55
- .union([
56
- z.string().min(1),
57
- z.array(z.string().min(1)).min(1),
58
- ])
59
- .describe("单个Markdown文件、Markdown文件数组或文件夹的绝对路径");
52
+ .union([z.string().min(1), z.array(z.string().min(1)).min(1)])
53
+ .describe('单个Markdown文件、Markdown文件数组或文件夹的绝对路径');
60
54
  const platformSchema = z
61
- .enum(["app", "california-web", "shared"])
62
- .describe("知识所属平台;必须明确指定,防止APP与加州网站需求串库");
63
- const moduleSchema = z
64
- .string()
65
- .min(1)
66
- .optional()
67
- .describe("可选业务模块标识,例如vehicle-selector");
55
+ .enum(['app', 'california-web', 'shared'])
56
+ .describe('知识所属平台;必须明确指定,防止APP与加州网站需求串库');
57
+ const moduleSchema = z.string().min(1).optional().describe('可选业务模块标识,例如vehicle-selector');
68
58
  const pageSchema = z
69
59
  .string()
70
60
  .min(1)
71
61
  .optional()
72
- .describe(
73
- "业务页面键,例如home、pl、pn、pd、name-search;录入多个文件时不要传,由后端按文件名分别推断",
74
- );
75
- const docKeySchema = z
76
- .string()
77
- .min(1)
78
- .optional()
79
- .describe("需要限定到单一文档时传knowledge_doc.doc_key绝对路径");
62
+ .describe('业务页面键,例如home、pl、pn、pd、name-search;录入多个文件时不要传,由后端按文件名分别推断');
63
+ const docKeySchema = z.string().min(1).optional().describe('需要限定到单一文档时传knowledge_doc.doc_key绝对路径');
80
64
 
81
65
  /** Recursively collects Markdown files from a directory in stable order. */
82
66
  async function scanMarkdownDirectory(directoryPath) {
@@ -88,7 +72,7 @@ async function scanMarkdownDirectory(directoryPath) {
88
72
  const entryPath = path.join(directoryPath, entry.name);
89
73
  if (entry.isDirectory()) {
90
74
  markdownFiles.push(...(await scanMarkdownDirectory(entryPath)));
91
- } else if (entry.isFile() && path.extname(entry.name).toLowerCase() === ".md") {
75
+ } else if (entry.isFile() && path.extname(entry.name).toLowerCase() === '.md') {
92
76
  markdownFiles.push(path.resolve(entryPath));
93
77
  }
94
78
  }
@@ -101,8 +85,8 @@ async function expandMarkdownPaths(pathsInput) {
101
85
  const markdownFiles = [];
102
86
 
103
87
  for (const input of inputs) {
104
- if (typeof input !== "string" || !input.trim()) {
105
- throw new Error("paths中的每一项都必须是非空字符串");
88
+ if (typeof input !== 'string' || !input.trim()) {
89
+ throw new Error('paths中的每一项都必须是非空字符串');
106
90
  }
107
91
  if (!path.isAbsolute(input.trim())) {
108
92
  throw new Error(`必须使用绝对路径: ${input}`);
@@ -112,21 +96,16 @@ async function expandMarkdownPaths(pathsInput) {
112
96
  const pathStat = await stat(absolutePath);
113
97
  if (pathStat.isDirectory()) {
114
98
  markdownFiles.push(...(await scanMarkdownDirectory(absolutePath)));
115
- } else if (
116
- pathStat.isFile() &&
117
- path.extname(absolutePath).toLowerCase() === ".md"
118
- ) {
99
+ } else if (pathStat.isFile() && path.extname(absolutePath).toLowerCase() === '.md') {
119
100
  markdownFiles.push(absolutePath);
120
101
  } else {
121
102
  throw new Error(`路径不是Markdown文件或文件夹: ${absolutePath}`);
122
103
  }
123
104
  }
124
105
 
125
- const uniquePaths = [...new Set(markdownFiles)].sort((left, right) =>
126
- left.localeCompare(right),
127
- );
106
+ const uniquePaths = [...new Set(markdownFiles)].sort((left, right) => left.localeCompare(right));
128
107
  if (uniquePaths.length === 0) {
129
- throw new Error("指定路径中没有找到任何.md文件");
108
+ throw new Error('指定路径中没有找到任何.md文件');
130
109
  }
131
110
  return uniquePaths;
132
111
  }
@@ -134,10 +113,10 @@ async function expandMarkdownPaths(pathsInput) {
134
113
  /** Posts JSON to Express and returns its parsed response body. */
135
114
  async function postJson(endpoint, body) {
136
115
  const response = await fetch(`${API_BASE_URL}${endpoint}`, {
137
- method: "POST",
138
- headers: { "content-type": "application/json" },
116
+ method: 'POST',
117
+ headers: { 'content-type': 'application/json' },
139
118
  body: JSON.stringify(body),
140
- signal: AbortSignal.timeout(HTTP_TIMEOUT_MS),
119
+ signal: AbortSignal.timeout(HTTP_TIMEOUT_MS)
141
120
  });
142
121
  const responseText = await response.text();
143
122
  let responseBody;
@@ -148,10 +127,7 @@ async function postJson(endpoint, body) {
148
127
  }
149
128
 
150
129
  if (!response.ok) {
151
- const details =
152
- typeof responseBody === "string"
153
- ? responseBody
154
- : JSON.stringify(responseBody);
130
+ const details = typeof responseBody === 'string' ? responseBody : JSON.stringify(responseBody);
155
131
  throw new Error(`Express后端返回HTTP ${response.status}: ${details}`);
156
132
  }
157
133
  return responseBody;
@@ -161,12 +137,12 @@ async function postJson(endpoint, body) {
161
137
  async function getJson(endpoint, query) {
162
138
  const url = new URL(`${API_BASE_URL}${endpoint}`);
163
139
  for (const [name, value] of Object.entries(query)) {
164
- if (value === undefined || value === null || value === "") continue;
140
+ if (value === undefined || value === null || value === '') continue;
165
141
  url.searchParams.set(name, value);
166
142
  }
167
143
  const response = await fetch(url, {
168
- method: "GET",
169
- signal: AbortSignal.timeout(HTTP_TIMEOUT_MS),
144
+ method: 'GET',
145
+ signal: AbortSignal.timeout(HTTP_TIMEOUT_MS)
170
146
  });
171
147
  const responseText = await response.text();
172
148
  let responseBody;
@@ -176,10 +152,7 @@ async function getJson(endpoint, query) {
176
152
  responseBody = responseText;
177
153
  }
178
154
  if (!response.ok) {
179
- const details =
180
- typeof responseBody === "string"
181
- ? responseBody
182
- : JSON.stringify(responseBody);
155
+ const details = typeof responseBody === 'string' ? responseBody : JSON.stringify(responseBody);
183
156
  throw new Error(`Express后端返回HTTP ${response.status}: ${details}`);
184
157
  }
185
158
  return responseBody;
@@ -189,10 +162,10 @@ function toolSuccess(payload) {
189
162
  return {
190
163
  content: [
191
164
  {
192
- type: "text",
193
- text: JSON.stringify(payload, null, 2),
194
- },
195
- ],
165
+ type: 'text',
166
+ text: JSON.stringify(payload, null, 2)
167
+ }
168
+ ]
196
169
  };
197
170
  }
198
171
 
@@ -200,154 +173,136 @@ function toolError(error) {
200
173
  const message = error instanceof Error ? error.message : String(error);
201
174
  return {
202
175
  isError: true,
203
- content: [{ type: "text", text: message }],
176
+ content: [{ type: 'text', text: message }]
204
177
  };
205
178
  }
206
179
 
207
180
  const server = new McpServer(
208
- { name: "rag-mcp-bridge", version: "1.0.0" },
181
+ { name: 'rag-mcp-bridge', version: '1.0.0' },
209
182
  {
210
183
  instructions:
211
- "Use retrieve_knowledge before implementing requirement-sensitive code. " +
212
- "Every tool call requires an explicit platform: app, california-web, or shared. " +
213
- "add_prd_file and update_spec_file accept one file, an array of files, or a directory. " +
214
- "Only call update_spec_file after the PR is merged and leader review confirms the spec is effective.",
215
- },
184
+ 'Use retrieve_knowledge before implementing requirement-sensitive code. ' +
185
+ 'Every tool call requires an explicit platform: app, california-web, or shared. ' +
186
+ 'add_prd_file and update_spec_file accept one file, an array of files, or a directory. ' +
187
+ 'Only call update_spec_file after the PR is merged and leader review confirms the spec is effective.'
188
+ }
216
189
  );
217
190
 
218
191
  server.registerTool(
219
- "add_prd_file",
192
+ 'add_prd_file',
220
193
  {
221
- title: "录入或更新PRD基线",
194
+ title: '录入或更新PRD基线',
222
195
  description:
223
- "录入/更新PRD基线。paths支持单个md绝对路径、md绝对路径数组或文件夹绝对路径;文件夹会递归扫描全部.md文件。后端负责解析截图并将旧文档标记为作废。",
196
+ '录入/更新PRD基线。paths支持单个md绝对路径、md绝对路径数组或文件夹绝对路径;文件夹会递归扫描全部.md文件。后端负责解析截图并将旧文档标记为作废。',
224
197
  inputSchema: {
225
198
  paths: pathsSchema,
226
199
  platform: platformSchema,
227
200
  module: moduleSchema,
228
- page: pageSchema,
201
+ page: pageSchema
229
202
  },
230
203
  annotations: {
231
204
  readOnlyHint: false,
232
205
  destructiveHint: false,
233
206
  idempotentHint: false,
234
- openWorldHint: false,
235
- },
207
+ openWorldHint: false
208
+ }
236
209
  },
237
210
  async ({ paths, platform, module, page }) => {
238
211
  try {
239
212
  const expandedPaths = await expandMarkdownPaths(paths);
240
213
  return toolSuccess(
241
- await postJson("/add-prd", {
214
+ await postJson('/add-prd', {
242
215
  paths: expandedPaths,
243
216
  platform,
244
217
  module,
245
- page,
246
- }),
218
+ page
219
+ })
247
220
  );
248
221
  } catch (error) {
249
222
  return toolError(error);
250
223
  }
251
- },
224
+ }
252
225
  );
253
226
 
254
227
  server.registerTool(
255
- "update_spec_file",
228
+ 'update_spec_file',
256
229
  {
257
- title: "录入迭代变更Spec",
230
+ title: '录入迭代变更Spec',
258
231
  description:
259
- "仅在PR合并且leader review确认生效后调用。paths支持单个spec.md绝对路径、路径数组或文件夹;只需传改动spec。sprintVersion必须递增,数值越大需求越新。",
232
+ '仅在PR合并且leader review确认生效后调用。paths支持单个spec.md绝对路径、路径数组或文件夹;只需传改动spec。sprintVersion必须递增,数值越大需求越新。',
260
233
  inputSchema: {
261
234
  paths: pathsSchema,
262
235
  sprintVersion: z.number().int().positive(),
263
236
  iterationTag: z.string().min(1),
264
237
  platform: platformSchema,
265
238
  module: moduleSchema,
266
- page: pageSchema,
239
+ page: pageSchema
267
240
  },
268
241
  annotations: {
269
242
  readOnlyHint: false,
270
243
  destructiveHint: false,
271
244
  idempotentHint: false,
272
- openWorldHint: false,
273
- },
245
+ openWorldHint: false
246
+ }
274
247
  },
275
248
  async ({ paths, sprintVersion, iterationTag, platform, module, page }) => {
276
249
  try {
277
250
  const expandedPaths = await expandMarkdownPaths(paths);
278
251
  return toolSuccess(
279
- await postJson("/add-spec", {
252
+ await postJson('/add-spec', {
280
253
  paths: expandedPaths,
281
254
  sprintVersion,
282
255
  iterationTag,
283
256
  platform,
284
257
  module,
285
- page,
286
- }),
258
+ page
259
+ })
287
260
  );
288
261
  } catch (error) {
289
262
  return toolError(error);
290
263
  }
291
- },
264
+ }
292
265
  );
293
266
 
294
267
  server.registerTool(
295
- "retrieve_knowledge",
268
+ 'retrieve_knowledge',
296
269
  {
297
- title: "检索PRD与迭代Spec",
270
+ title: '检索PRD与迭代Spec',
298
271
  description:
299
- "Codex编写需求相关代码前调用。后端执行向量+BM25-RRF混合检索并按sprint_version业务排序。enableStruct=true时才生成结构化PRD;includeImages=true时才保留图片并替换为数据库图片URL,两者默认均为false。",
272
+ 'Codex编写需求相关代码前调用。后端执行向量+BM25-RRF混合检索并按sprint_version业务排序。enableStruct=true时才生成结构化PRD;includeImages=true时才保留图片并替换为数据库图片URL,两者默认均为false。',
300
273
  inputSchema: {
301
274
  query: z.string().min(1),
302
275
  platform: platformSchema,
303
- module: moduleSchema.describe(
304
- "可选业务模块硬过滤,例如vehicle-selector",
305
- ),
306
- page: pageSchema.describe(
307
- "可选页面硬过滤,例如首页传home;不传时后端会从明确的查询词自动推断",
308
- ),
276
+ module: moduleSchema.describe('可选业务模块硬过滤,例如vehicle-selector'),
277
+ page: pageSchema.describe('可选页面硬过滤,例如首页传home;不传时后端会从明确的查询词自动推断'),
309
278
  docKey: docKeySchema,
310
- enableStruct: z
311
- .boolean()
312
- .default(false)
313
- .describe("是否调用LLM生成structuredPrd,默认false"),
314
- includeImages: z
315
- .boolean()
316
- .default(false)
317
- .describe("是否返回可供UI显示的数据库图片URL,默认false"),
279
+ enableStruct: z.boolean().default(false).describe('是否调用LLM生成structuredPrd,默认false'),
280
+ includeImages: z.boolean().default(false).describe('是否返回可供UI显示的数据库图片URL,默认false')
318
281
  },
319
282
  annotations: {
320
283
  readOnlyHint: true,
321
284
  destructiveHint: false,
322
285
  idempotentHint: true,
323
- openWorldHint: false,
324
- },
286
+ openWorldHint: false
287
+ }
325
288
  },
326
- async ({
327
- query,
328
- platform,
329
- module,
330
- page,
331
- docKey,
332
- enableStruct = false,
333
- includeImages = false,
334
- }) => {
289
+ async ({ query, platform, module, page, docKey, enableStruct = false, includeImages = false }) => {
335
290
  try {
336
291
  return toolSuccess(
337
- await getJson("/search", {
292
+ await getJson('/search', {
338
293
  query: query.trim(),
339
294
  platform,
340
295
  module,
341
296
  page,
342
297
  docKey,
343
298
  enableStruct: String(enableStruct === true),
344
- includeImages: String(includeImages === true),
345
- }),
299
+ includeImages: String(includeImages === true)
300
+ })
346
301
  );
347
302
  } catch (error) {
348
303
  return toolError(error);
349
304
  }
350
- },
305
+ }
351
306
  );
352
307
 
353
308
  async function main() {
@@ -355,7 +310,7 @@ async function main() {
355
310
  await server.connect(transport);
356
311
  }
357
312
 
358
- main().catch((error) => {
359
- console.error("Failed to start rag-mcp-bridge:", error);
313
+ main().catch(error => {
314
+ console.error('Failed to start rag-mcp-bridge:', error);
360
315
  process.exitCode = 1;
361
316
  });
@@ -1,54 +1,50 @@
1
- import assert from "node:assert/strict";
2
- import { spawnSync } from "node:child_process";
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";
1
+ import assert from 'node:assert/strict';
2
+ import { spawnSync } from 'node:child_process';
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
8
 
9
9
  const directory = path.dirname(fileURLToPath(import.meta.url));
10
10
 
11
- test("initializes and exposes the RAG tools over stdio", async () => {
12
- const client = new Client({ name: "rag-mcp-bridge-test", version: "1.0.0" });
11
+ test('initializes and exposes the RAG tools over stdio', async () => {
12
+ const client = new Client({ name: 'rag-mcp-bridge-test', version: '1.0.0' });
13
13
  const transport = new StdioClientTransport({
14
14
  command: process.execPath,
15
- args: [path.join(directory, "index.js")],
16
- cwd: path.resolve(directory, "../.."),
15
+ args: [path.join(directory, 'index.js')],
16
+ cwd: path.resolve(directory, '../..'),
17
17
  env: {
18
- RAG_API_BASE_URL: "http://127.0.0.1:3000/api/knowledge",
18
+ RAG_API_BASE_URL: 'http://192.168.1.12:3000/api/knowledge'
19
19
  },
20
- stderr: "pipe",
20
+ stderr: 'pipe'
21
21
  });
22
22
 
23
23
  try {
24
24
  await client.connect(transport);
25
25
 
26
- assert.equal(client.getServerVersion()?.name, "rag-mcp-bridge");
27
- assert.match(client.getInstructions() ?? "", /retrieve_knowledge/);
26
+ assert.equal(client.getServerVersion()?.name, 'rag-mcp-bridge');
27
+ assert.match(client.getInstructions() ?? '', /retrieve_knowledge/);
28
28
 
29
29
  const { tools } = await client.listTools();
30
30
  assert.deepEqual(
31
31
  tools.map(({ name }) => name),
32
- ["add_prd_file", "update_spec_file", "retrieve_knowledge"],
33
- );
34
- assert.equal(
35
- tools.find(({ name }) => name === "retrieve_knowledge")?.annotations
36
- ?.readOnlyHint,
37
- true,
32
+ ['add_prd_file', 'update_spec_file', 'retrieve_knowledge']
38
33
  );
34
+ assert.equal(tools.find(({ name }) => name === 'retrieve_knowledge')?.annotations?.readOnlyHint, true);
39
35
  } finally {
40
36
  await client.close();
41
37
  }
42
38
  });
43
39
 
44
- test("fails to start when RAG_API_BASE_URL is not configured", () => {
40
+ test('fails to start when RAG_API_BASE_URL is not configured', () => {
45
41
  const environment = { ...process.env };
46
42
  delete environment.RAG_API_BASE_URL;
47
43
 
48
- const result = spawnSync(process.execPath, [path.join(directory, "index.js")], {
49
- cwd: path.resolve(directory, "../.."),
44
+ const result = spawnSync(process.execPath, [path.join(directory, 'index.js')], {
45
+ cwd: path.resolve(directory, '../..'),
50
46
  env: environment,
51
- encoding: "utf8",
47
+ encoding: 'utf8'
52
48
  });
53
49
 
54
50
  assert.notEqual(result.status, 0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autobest-ui/agent",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "private": false,
5
5
  "description": "Autobest Agent skills/plugins/mcp assets + sync cli",
6
6
  "files": [
@@ -19,22 +19,27 @@
19
19
  "autobest-agent-sync": "./bin/sync-assets.mjs",
20
20
  "autobest-azurepr-mcp": "./mcp/azurepr-mcp-bridge/index.js",
21
21
  "autobest-delivery-setup": "./plugins/autobest-delivery/scripts/setup.mjs",
22
- "autobest-rag-mcp": "./mcp/rag-mcp-bridge/index.js"
22
+ "autobest-rag-mcp": "./mcp/rag-mcp-bridge/index.js",
23
+ "figma-mcp-bridge": "./mcp/figma-mcp-bridge/src/index.js",
24
+ "figma-plugin": "./plugins/figma-plugin/scripts/setup.mjs"
23
25
  },
24
26
  "scripts": {
25
27
  "mcp:azurepr": "node ./mcp/azurepr-mcp-bridge/index.js",
28
+ "mcp:figma": "node ./mcp/figma-mcp-bridge/src/index.js",
26
29
  "mcp:rag": "node ./mcp/rag-mcp-bridge/index.js",
27
30
  "test": "npm run test:skills && npm run test:mcp && npm run test:plugin",
28
31
  "test:skills": "node --test ./bin/sync-assets.test.mjs",
29
32
  "test:mcp:azurepr": "node --test ./mcp/azurepr-mcp-bridge/index.test.js",
33
+ "test:mcp:figma": "node --test ./mcp/figma-mcp-bridge/index.test.js",
30
34
  "test:mcp:rag": "node --test ./mcp/rag-mcp-bridge/index.test.js",
31
- "test:mcp": "npm run test:mcp:azurepr && npm run test:mcp:rag",
32
- "test:plugin": "node --test ./plugins/autobest-delivery/scripts/setup.test.mjs"
35
+ "test:mcp": "npm run test:mcp:azurepr && npm run test:mcp:figma && npm run test:mcp:rag",
36
+ "test:plugin": "node --test ./plugins/autobest-delivery/scripts/setup.test.mjs ./plugins/figma-plugin/scripts/setup.test.mjs"
33
37
  },
34
38
  "dependencies": {
35
39
  "@modelcontextprotocol/sdk": "1.30.0",
36
40
  "diff": "9.0.0",
37
41
  "fs-extra": "11.3.0",
42
+ "ws": "8.18.3",
38
43
  "zod": "4.4.3"
39
44
  },
40
45
  "publishConfig": {
@@ -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-plugin
2
+
3
+ `figma-plugin` 是与 `figma-mcp-bridge` 配套的 Figma Development Plugin。它在 Figma 内运行,并通过本机 WebSocket 与 MCP 通信。
4
+
5
+ ## 安装
6
+
7
+ ```bash
8
+ npx --yes --package=@autobest-ui/agent@latest figma-plugin
9
+ ```
10
+
11
+ 安装器会将插件文件同步到稳定目录:
12
+
13
+ ```text
14
+ ~/.autobest-agent/figma-plugins/figma-plugin
15
+ ```
16
+
17
+ 随后在 Figma 中执行 **Plugins -> Development -> Import plugin from manifest**,选择命令输出路径中的 `manifest.json`。再次执行安装命令会更新同一目录,无需重新导入。
18
+
19
+ 卸载本地文件:
20
+
21
+ ```bash
22
+ npx --yes --package=@autobest-ui/agent@latest figma-plugin uninstall
23
+ ```
24
+
25
+ 卸载后,Figma Development Plugins 列表中的记录需要在 Figma 内移除。