@liner-fe/figma-mcp 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.
- package/README.md +24 -0
- package/dist/server/server.cjs +385 -0
- package/package.json +71 -0
package/README.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
## Quickstart
|
|
2
|
+
|
|
3
|
+
This plugin was created with [Plugma](https://github.com/gavinmcfarland/plugma) using the [React](https://react.dev/) framework.
|
|
4
|
+
|
|
5
|
+
### Requirements
|
|
6
|
+
|
|
7
|
+
- [Node.js](https://nodejs.org/en)
|
|
8
|
+
- [Figma desktop app](https://www.figma.com/downloads/)
|
|
9
|
+
|
|
10
|
+
### 실행 방법
|
|
11
|
+
|
|
12
|
+
1. **개발 서버 실행**
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
cd shared/figma-plugin
|
|
16
|
+
yarn start
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
2. **Figma에서 플러그인 실행**
|
|
20
|
+
- Figma 데스크톱 앱에서 `Plugins` → `Development` → `Import plugin from manifest...` 클릭
|
|
21
|
+
- `shared/figma-plugin/dist/manifest.json` 파일 선택
|
|
22
|
+
- 플러그인이 설치되면 `Plugins` → `Development`에서 실행 가능
|
|
23
|
+
- 플러그인이 실행되면 `connect` 버튼을 클릭하여 mcp서버 사용 가능
|
|
24
|
+
- HMR이 적용되고 있음
|
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __copyProps = (to, from, except, desc) => {
|
|
8
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
9
|
+
for (let key of __getOwnPropNames(from))
|
|
10
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
11
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
12
|
+
}
|
|
13
|
+
return to;
|
|
14
|
+
};
|
|
15
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
16
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
17
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
18
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
19
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
20
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
21
|
+
mod
|
|
22
|
+
));
|
|
23
|
+
|
|
24
|
+
// src/server.ts
|
|
25
|
+
var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
26
|
+
var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
27
|
+
var import_zod = require("zod");
|
|
28
|
+
|
|
29
|
+
// src/figmaClient.ts
|
|
30
|
+
var import_crypto = require("crypto");
|
|
31
|
+
var import_ws = __toESM(require("ws"), 1);
|
|
32
|
+
function createFigmaClient({ logger: logger2, serverUrl: serverUrl2, wsUrlBase }) {
|
|
33
|
+
let ws = null;
|
|
34
|
+
const pendingRequests = /* @__PURE__ */ new Map();
|
|
35
|
+
function connectToFigma2(port = 3055) {
|
|
36
|
+
if (ws && ws.readyState === import_ws.default.OPEN) {
|
|
37
|
+
logger2.info("Already connected to Figma");
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const wsUrl = serverUrl2 === "localhost" ? `${wsUrlBase}:${port}` : wsUrlBase;
|
|
41
|
+
logger2.info(`Connecting to Figma socket server at ${wsUrl}...`);
|
|
42
|
+
ws = new import_ws.default(wsUrl);
|
|
43
|
+
ws.on("open", () => {
|
|
44
|
+
logger2.info("Connected to Figma socket server");
|
|
45
|
+
});
|
|
46
|
+
ws.on("message", (data) => {
|
|
47
|
+
try {
|
|
48
|
+
const json = JSON.parse(data);
|
|
49
|
+
const myResponse = json.message;
|
|
50
|
+
logger2.debug(`Received message: ${JSON.stringify(myResponse)}`);
|
|
51
|
+
logger2.log("myResponse" + JSON.stringify(myResponse));
|
|
52
|
+
if (myResponse.id && pendingRequests.has(myResponse.id) && myResponse.result) {
|
|
53
|
+
const request = pendingRequests.get(myResponse.id);
|
|
54
|
+
clearTimeout(request.timeout);
|
|
55
|
+
if (myResponse.error) {
|
|
56
|
+
logger2.error(`Error from Figma: ${myResponse.error}`);
|
|
57
|
+
request.reject(new Error(myResponse.error));
|
|
58
|
+
} else if (myResponse.result) {
|
|
59
|
+
request.resolve(myResponse.result);
|
|
60
|
+
}
|
|
61
|
+
pendingRequests.delete(myResponse.id);
|
|
62
|
+
} else {
|
|
63
|
+
logger2.info(`Received broadcast message: ${JSON.stringify(myResponse)}`);
|
|
64
|
+
}
|
|
65
|
+
} catch (error) {
|
|
66
|
+
logger2.error(
|
|
67
|
+
`Error parsing message: ${error instanceof Error ? error.message : String(error)}`
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
ws.on("error", (error) => {
|
|
72
|
+
logger2.error(`Socket error: ${error}`);
|
|
73
|
+
});
|
|
74
|
+
ws.on("close", () => {
|
|
75
|
+
logger2.info("Disconnected from Figma socket server");
|
|
76
|
+
ws = null;
|
|
77
|
+
for (const [id, request] of pendingRequests.entries()) {
|
|
78
|
+
clearTimeout(request.timeout);
|
|
79
|
+
request.reject(new Error("Connection closed"));
|
|
80
|
+
pendingRequests.delete(id);
|
|
81
|
+
}
|
|
82
|
+
logger2.info("Attempting to reconnect in 2 seconds...");
|
|
83
|
+
setTimeout(() => connectToFigma2(port), 2e3);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
function sendCommandToFigma2(command, params = {}) {
|
|
87
|
+
return new Promise((resolve, reject) => {
|
|
88
|
+
if (!ws || ws.readyState !== import_ws.default.OPEN) {
|
|
89
|
+
connectToFigma2();
|
|
90
|
+
reject(new Error("Not connected to Figma. Attempting to connect..."));
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const id = (0, import_crypto.randomUUID)();
|
|
94
|
+
const request = {
|
|
95
|
+
id,
|
|
96
|
+
type: "message",
|
|
97
|
+
message: {
|
|
98
|
+
id,
|
|
99
|
+
command,
|
|
100
|
+
params: {
|
|
101
|
+
...params
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
const timeout = setTimeout(() => {
|
|
106
|
+
if (pendingRequests.has(id)) {
|
|
107
|
+
pendingRequests.delete(id);
|
|
108
|
+
logger2.error(`Request ${id} to Figma timed out after 30 seconds`);
|
|
109
|
+
reject(new Error("Request to Figma timed out"));
|
|
110
|
+
}
|
|
111
|
+
}, 3e4);
|
|
112
|
+
pendingRequests.set(id, { resolve, reject, timeout });
|
|
113
|
+
logger2.info(`Sending command to Figma: ${command}`);
|
|
114
|
+
logger2.debug(`Request details: ${JSON.stringify(request)}`);
|
|
115
|
+
ws.send(JSON.stringify(request));
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
connectToFigma: connectToFigma2,
|
|
120
|
+
sendCommandToFigma: sendCommandToFigma2
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// src/server.ts
|
|
125
|
+
var logger = {
|
|
126
|
+
info: (message) => process.stderr.write(`[INFO] ${message}
|
|
127
|
+
`),
|
|
128
|
+
debug: (message) => process.stderr.write(`[DEBUG] ${message}
|
|
129
|
+
`),
|
|
130
|
+
warn: (message) => process.stderr.write(`[WARN] ${message}
|
|
131
|
+
`),
|
|
132
|
+
error: (message) => process.stderr.write(`[ERROR] ${message}
|
|
133
|
+
`),
|
|
134
|
+
log: (message) => process.stderr.write(`[LOG] ${message}
|
|
135
|
+
`)
|
|
136
|
+
};
|
|
137
|
+
var RULES_FILES = Object.keys(__RULES__);
|
|
138
|
+
function levenshtein(a, b) {
|
|
139
|
+
const m = a.length;
|
|
140
|
+
const n = b.length;
|
|
141
|
+
if (m === 0) return n;
|
|
142
|
+
if (n === 0) return m;
|
|
143
|
+
const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
|
|
144
|
+
for (let i = 0; i <= m; i++) dp[i][0] = i;
|
|
145
|
+
for (let j = 0; j <= n; j++) dp[0][j] = j;
|
|
146
|
+
for (let i = 1; i <= m; i++) {
|
|
147
|
+
for (let j = 1; j <= n; j++) {
|
|
148
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
149
|
+
dp[i][j] = Math.min(
|
|
150
|
+
dp[i - 1][j] + 1,
|
|
151
|
+
// 삭제
|
|
152
|
+
dp[i][j - 1] + 1,
|
|
153
|
+
// 삽입
|
|
154
|
+
dp[i - 1][j - 1] + cost
|
|
155
|
+
// 교체
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return dp[m][n];
|
|
160
|
+
}
|
|
161
|
+
function getSimilarityScore(target, candidate) {
|
|
162
|
+
if (target === candidate) return 100;
|
|
163
|
+
if (candidate.startsWith(target) || target.startsWith(candidate)) return 95;
|
|
164
|
+
if (candidate.includes(target) || target.includes(candidate)) return 85;
|
|
165
|
+
const distance = levenshtein(target, candidate);
|
|
166
|
+
const maxLen = Math.max(target.length, candidate.length);
|
|
167
|
+
const normalized = 1 - distance / maxLen;
|
|
168
|
+
const score = Math.floor(normalized * 80);
|
|
169
|
+
return score > 0 ? score : 0;
|
|
170
|
+
}
|
|
171
|
+
var STORIES_EXT = ".stories.mdx";
|
|
172
|
+
function findSimilarStoriesFiles(componentName) {
|
|
173
|
+
const compName = componentName.toLowerCase();
|
|
174
|
+
const files = RULES_FILES.filter((file) => file.toLowerCase().endsWith(STORIES_EXT));
|
|
175
|
+
const scored = files.map((file) => {
|
|
176
|
+
const baseName = file.toLowerCase().replace(new RegExp(`${STORIES_EXT.replace(".", "\\.")}$`), "");
|
|
177
|
+
return {
|
|
178
|
+
file,
|
|
179
|
+
baseName,
|
|
180
|
+
score: getSimilarityScore(compName, baseName)
|
|
181
|
+
};
|
|
182
|
+
}).filter(({ score }) => score > 0).sort((a, b) => b.score - a.score);
|
|
183
|
+
return scored;
|
|
184
|
+
}
|
|
185
|
+
var server = new import_mcp.McpServer(
|
|
186
|
+
{
|
|
187
|
+
name: "@jongh/figma-plugin",
|
|
188
|
+
version: "1.0.0"
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
capabilities: {
|
|
192
|
+
logging: logger,
|
|
193
|
+
resources: {
|
|
194
|
+
subscribe: true,
|
|
195
|
+
listChanged: true
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
);
|
|
200
|
+
var args = process.argv.slice(2);
|
|
201
|
+
var serverArg = args.find((arg) => arg.startsWith("--server="));
|
|
202
|
+
var serverUrl = serverArg ? serverArg.split("=")[1] : "localhost";
|
|
203
|
+
var WS_URL = serverUrl === "localhost" ? `ws://${serverUrl}` : `wss://${serverUrl}`;
|
|
204
|
+
var { connectToFigma, sendCommandToFigma } = createFigmaClient({
|
|
205
|
+
logger,
|
|
206
|
+
serverUrl,
|
|
207
|
+
wsUrlBase: WS_URL
|
|
208
|
+
});
|
|
209
|
+
server.registerTool(
|
|
210
|
+
"get_selection",
|
|
211
|
+
{
|
|
212
|
+
description: "Get information about the current selection in Figma",
|
|
213
|
+
inputSchema: {}
|
|
214
|
+
},
|
|
215
|
+
async () => {
|
|
216
|
+
try {
|
|
217
|
+
const result = await sendCommandToFigma("get_selection");
|
|
218
|
+
return {
|
|
219
|
+
content: [
|
|
220
|
+
{
|
|
221
|
+
type: "text",
|
|
222
|
+
text: JSON.stringify(result, null, 2)
|
|
223
|
+
}
|
|
224
|
+
]
|
|
225
|
+
};
|
|
226
|
+
} catch (error) {
|
|
227
|
+
return {
|
|
228
|
+
content: [
|
|
229
|
+
{
|
|
230
|
+
type: "text",
|
|
231
|
+
text: `Error getting selection: ${error instanceof Error ? error.message : String(error)}`
|
|
232
|
+
}
|
|
233
|
+
]
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
);
|
|
238
|
+
server.registerPrompt(
|
|
239
|
+
"design system components",
|
|
240
|
+
{
|
|
241
|
+
description: "help analyze design system common components"
|
|
242
|
+
},
|
|
243
|
+
() => {
|
|
244
|
+
return {
|
|
245
|
+
messages: [
|
|
246
|
+
{
|
|
247
|
+
role: "assistant",
|
|
248
|
+
content: {
|
|
249
|
+
type: "text",
|
|
250
|
+
text: `\uACF5\uD1B5 \uCEF4\uD3EC\uB10C\uD2B8\uC758 \uAD6C\uD604 \uC815\uBCF4\uB97C \uD30C\uC545\uD558\uC5EC \uCF54\uB4DC \uC81C\uC791\uC5D0 \uD65C\uC6A9\uD574\uC57C \uD569\uB2C8\uB2E4. \uCF54\uB4DC \uC0AC\uC6A9 \uC608\uC2DC, \uC124\uBA85 \uB4F1\uC744 \uC815\uD655\uD558\uAC8C \uD30C\uC545\uD558\uC5EC \uC2E4\uC81C \uCF54\uB4DC \uC81C\uC791\uC5D0 \uC815\uD655\uD558\uAC8C \uD65C\uC6A9\uD560 \uC218 \uC788\uC5B4\uC57C \uD569\uB2C8\uB2E4.
|
|
251
|
+
\uD2B9\uD788 Figma \uC778\uD130\uD398\uC774\uC2A4\uC640 \uCF54\uB4DC \uAC04\uC758 \uAD6C\uD604 \uCC28\uC774\uB97C \uC815\uD655\uD558\uAC8C \uD30C\uC545\uD558\uACE0 \uCF54\uB4DC\uC5D0 \uB9DE\uAC8C \uD65C\uC6A9\uD574\uC57C \uD569\uB2C8\uB2E4.`
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
],
|
|
255
|
+
description: "Troubleshooting guide for connection issues between Figma plugin and MCP server"
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
);
|
|
259
|
+
server.registerTool(
|
|
260
|
+
"get_rule_of_components",
|
|
261
|
+
{
|
|
262
|
+
title: "Rule of design system component",
|
|
263
|
+
description: [
|
|
264
|
+
"\uB514\uC790\uC778 \uC2DC\uC2A4\uD15C\uC5D0\uC11C \uC0AC\uC6A9\uD558\uB294 \uACF5\uD1B5 \uCEF4\uD3EC\uB10C\uD2B8\uC758 \uAD6C\uD604 \uADDC\uCE59(\uAD6C\uC870, props, \uC2A4\uD0C0\uC77C \uB4F1)\uC744 \uC870\uD68C\uD558\uB294 MCP \uB3C4\uAD6C\uC785\uB2C8\uB2E4.",
|
|
265
|
+
"Figma \uB514\uC790\uC778\uC5D0\uC11C \uC0AC\uC6A9\uB41C \uACF5\uD1B5 \uCEF4\uD3EC\uB10C\uD2B8\uC758 \uC774\uB984\uC744 componentName \uC778\uC790\uB85C \uB118\uACA8 \uD638\uCD9C\uD558\uBA74,",
|
|
266
|
+
"\uD574\uB2F9 \uCEF4\uD3EC\uB10C\uD2B8\uC758 \uAD6C\uD604 \uC815\uBCF4\uB97C \uBC18\uD658\uD558\uC5EC \u2018MCP\uB97C \uD1B5\uD574 \uC0AC\uC6A9\uB41C \uACF5\uD1B5 \uCEF4\uD3EC\uB10C\uD2B8\uC758 \uAD6C\uD604 \uC815\uBCF4\uB97C \uD30C\uC545\uD55C \uB4A4\u2019 \uD398\uC774\uC9C0 \uCF54\uB4DC\uB97C \uC791\uC131\uD560 \uB54C \uC0AC\uC6A9\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.",
|
|
267
|
+
"\uC608: Figma\uC5D0\uC11C Button \uCEF4\uD3EC\uB10C\uD2B8\uAC00 \uC4F0\uC600\uB2E4\uBA74 componentName\uC5D0 'button'\uC744 \uB123\uC5B4 \uC774 \uB3C4\uAD6C\uB97C \uD638\uCD9C\uD574 \uAD6C\uD604 \uADDC\uCE59\uC744 \uC870\uD68C\uD558\uC138\uC694."
|
|
268
|
+
].join(" "),
|
|
269
|
+
inputSchema: import_zod.z.object({
|
|
270
|
+
componentName: import_zod.z.string().describe(
|
|
271
|
+
"Figma/\uB514\uC790\uC778 \uC2DC\uC2A4\uD15C\uC5D0\uC11C \uC0AC\uC6A9\uB41C \uACF5\uD1B5 \uCEF4\uD3EC\uB10C\uD2B8 \uC774\uB984 (\uC608: 'button', 'input', 'modal')"
|
|
272
|
+
)
|
|
273
|
+
})
|
|
274
|
+
},
|
|
275
|
+
async ({ componentName }) => {
|
|
276
|
+
const similarFiles = findSimilarStoriesFiles(componentName);
|
|
277
|
+
const texts = similarFiles.map(({ file }) => __RULES__[file]);
|
|
278
|
+
return {
|
|
279
|
+
content: [
|
|
280
|
+
{
|
|
281
|
+
type: "text",
|
|
282
|
+
text: `${texts.join("")}`
|
|
283
|
+
}
|
|
284
|
+
]
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
);
|
|
288
|
+
server.registerResource(
|
|
289
|
+
"file",
|
|
290
|
+
// 리소스 ID
|
|
291
|
+
new import_mcp.ResourceTemplate("file://{path}/", {
|
|
292
|
+
list: async () => {
|
|
293
|
+
return {
|
|
294
|
+
resources: RULES_FILES.map((filePath) => ({
|
|
295
|
+
uri: `file://${filePath}`,
|
|
296
|
+
name: filePath,
|
|
297
|
+
title: filePath,
|
|
298
|
+
description: "local file from design-rules",
|
|
299
|
+
mimeType: "text/plain",
|
|
300
|
+
_meta: {
|
|
301
|
+
filePath
|
|
302
|
+
}
|
|
303
|
+
}))
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
}),
|
|
307
|
+
{
|
|
308
|
+
title: "design rule",
|
|
309
|
+
description: "important rules and guidelines for resolving design data"
|
|
310
|
+
},
|
|
311
|
+
// resources/read 핸들러
|
|
312
|
+
async (uri, props) => {
|
|
313
|
+
const filePath = props.path;
|
|
314
|
+
const text = __RULES__[filePath] ?? "";
|
|
315
|
+
return {
|
|
316
|
+
contents: [
|
|
317
|
+
{
|
|
318
|
+
uri: uri.href,
|
|
319
|
+
mimeType: "text/plain",
|
|
320
|
+
text
|
|
321
|
+
}
|
|
322
|
+
]
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
);
|
|
326
|
+
server.registerPrompt(
|
|
327
|
+
"data_analysis_strategy",
|
|
328
|
+
{
|
|
329
|
+
description: "Best practices for analyzing Figma design data"
|
|
330
|
+
},
|
|
331
|
+
() => {
|
|
332
|
+
return {
|
|
333
|
+
messages: [
|
|
334
|
+
{
|
|
335
|
+
role: "assistant",
|
|
336
|
+
content: {
|
|
337
|
+
type: "text",
|
|
338
|
+
text: `When analyzing Figma design data, follow these strategies
|
|
339
|
+
|
|
340
|
+
1. Data Extraction Workflow:
|
|
341
|
+
- Use get_selection() to focus on specific areas of interest
|
|
342
|
+
- Use get_document_info() when connection is enabled
|
|
343
|
+
|
|
344
|
+
2. Component Analysis:
|
|
345
|
+
- Identify reusable components vs one-off elements
|
|
346
|
+
- Look for design system patterns (colors, typography, spacing)
|
|
347
|
+
- Note component variants and their properties
|
|
348
|
+
- Extract design tokens (colors, fonts, spacing values)
|
|
349
|
+
|
|
350
|
+
3. Layout Analysis:
|
|
351
|
+
- Analyze auto-layout settings and constraints
|
|
352
|
+
- Document spacing patterns and grid systems
|
|
353
|
+
- Identify responsive design patterns
|
|
354
|
+
- Note alignment and positioning strategies
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
`
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
],
|
|
363
|
+
description: "Best practices for working with Figma designs"
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
);
|
|
367
|
+
async function main() {
|
|
368
|
+
try {
|
|
369
|
+
connectToFigma();
|
|
370
|
+
} catch (error) {
|
|
371
|
+
logger.warn(
|
|
372
|
+
`Could not connect to Figma initially: ${error instanceof Error ? error.message : String(error)}`
|
|
373
|
+
);
|
|
374
|
+
logger.warn("Will try to connect when the first command is sent");
|
|
375
|
+
}
|
|
376
|
+
const transport = new import_stdio.StdioServerTransport();
|
|
377
|
+
await server.connect(transport);
|
|
378
|
+
logger.info("FigmaMCP server running on stdio");
|
|
379
|
+
}
|
|
380
|
+
main().catch((error) => {
|
|
381
|
+
logger.error(
|
|
382
|
+
`Error starting FigmaMCP server: ${error instanceof Error ? error.message : String(error)}`
|
|
383
|
+
);
|
|
384
|
+
process.exit(1);
|
|
385
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@liner-fe/figma-mcp",
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"bin": "dist/server/server.cjs",
|
|
6
|
+
"files": [
|
|
7
|
+
"dist/server/server.cjs"
|
|
8
|
+
],
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"@inquirer/prompts": "^7",
|
|
11
|
+
"@modelcontextprotocol/sdk": "^1.24",
|
|
12
|
+
"es-toolkit": "^1.39.10",
|
|
13
|
+
"figma-api": "^2.0.1-beta",
|
|
14
|
+
"react": "^18.3.1",
|
|
15
|
+
"react-dom": "^18.3.1",
|
|
16
|
+
"ws": "^8",
|
|
17
|
+
"xml-js": "^1.6.11",
|
|
18
|
+
"zod": "^4"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@figma/plugin-typings": "^1.100.2",
|
|
22
|
+
"@types/react": "^18.3.12",
|
|
23
|
+
"@types/react-dom": "^18.3.1",
|
|
24
|
+
"@types/ws": "^8.5.10",
|
|
25
|
+
"@vitejs/plugin-react": "^4.3.3",
|
|
26
|
+
"concurrently": "^8",
|
|
27
|
+
"plugma": "^1",
|
|
28
|
+
"tsup": "^8.5.0",
|
|
29
|
+
"tsx": "^4.7.0",
|
|
30
|
+
"vite": "^5.4.10",
|
|
31
|
+
"vite-plugin-generate-file": "^0.3.1",
|
|
32
|
+
"liner-figma": "1.0.0"
|
|
33
|
+
},
|
|
34
|
+
"publishConfig": {
|
|
35
|
+
"access": "public"
|
|
36
|
+
},
|
|
37
|
+
"plugma": {
|
|
38
|
+
"manifest": {
|
|
39
|
+
"id": "e-replace",
|
|
40
|
+
"name": "e",
|
|
41
|
+
"main": "src/main.ts",
|
|
42
|
+
"ui": "src/ui.tsx",
|
|
43
|
+
"editorType": [
|
|
44
|
+
"figma",
|
|
45
|
+
"figjam",
|
|
46
|
+
"dev"
|
|
47
|
+
],
|
|
48
|
+
"capabilities": [
|
|
49
|
+
"inspect"
|
|
50
|
+
],
|
|
51
|
+
"networkAccess": {
|
|
52
|
+
"allowedDomains": [
|
|
53
|
+
"none"
|
|
54
|
+
],
|
|
55
|
+
"devAllowedDomains": [
|
|
56
|
+
"http://localhost:*",
|
|
57
|
+
"ws://localhost:9001"
|
|
58
|
+
]
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
"scripts": {
|
|
63
|
+
"dev": "plugma dev",
|
|
64
|
+
"socket": "tsx src/socket.ts",
|
|
65
|
+
"start": "concurrently \"pnpm run socket\" \"pnpm run dev\" --names \"SOCKET,MCP\" --prefix-colors \"blue,green\"",
|
|
66
|
+
"build:server": "pnpm tsup",
|
|
67
|
+
"build": "pnpm build:server",
|
|
68
|
+
"build:package": "pnpm build",
|
|
69
|
+
"generate-from-link": "tsx src/generate-from-link.ts"
|
|
70
|
+
}
|
|
71
|
+
}
|