@ject-5-fe/figma-plugin 0.1.5 → 0.3.0
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/dist/server/server.cjs +256 -202
- package/package.json +5 -4
package/dist/server/server.cjs
CHANGED
|
@@ -1,14 +1,9 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
1
|
var __create = Object.create;
|
|
3
2
|
var __defProp = Object.defineProperty;
|
|
4
3
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
5
|
var __getProtoOf = Object.getPrototypeOf;
|
|
7
6
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __export = (target, all) => {
|
|
9
|
-
for (var name in all)
|
|
10
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
-
};
|
|
12
7
|
var __copyProps = (to, from, except, desc) => {
|
|
13
8
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
9
|
for (let key of __getOwnPropNames(from))
|
|
@@ -25,20 +20,116 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
25
20
|
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
21
|
mod
|
|
27
22
|
));
|
|
28
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
23
|
|
|
30
24
|
// src/server.ts
|
|
31
|
-
var server_exports = {};
|
|
32
|
-
__export(server_exports, {
|
|
33
|
-
compressData: () => compressData
|
|
34
|
-
});
|
|
35
|
-
module.exports = __toCommonJS(server_exports);
|
|
36
25
|
var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
37
26
|
var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
27
|
+
var import_fs = __toESM(require("fs"), 1);
|
|
28
|
+
var import_path = __toESM(require("path"), 1);
|
|
29
|
+
var import_zod = require("zod");
|
|
30
|
+
|
|
31
|
+
// src/figmaClient.ts
|
|
38
32
|
var import_uuid = require("uuid");
|
|
39
33
|
var import_ws = __toESM(require("ws"), 1);
|
|
40
|
-
|
|
41
|
-
|
|
34
|
+
function createFigmaClient({
|
|
35
|
+
logger: logger2,
|
|
36
|
+
serverUrl: serverUrl2,
|
|
37
|
+
wsUrlBase
|
|
38
|
+
}) {
|
|
39
|
+
let ws = null;
|
|
40
|
+
const pendingRequests = /* @__PURE__ */ new Map();
|
|
41
|
+
function connectToFigma2(port = 3055) {
|
|
42
|
+
if (ws && ws.readyState === import_ws.default.OPEN) {
|
|
43
|
+
logger2.info("Already connected to Figma");
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const wsUrl = serverUrl2 === "localhost" ? `${wsUrlBase}:${port}` : wsUrlBase;
|
|
47
|
+
logger2.info(`Connecting to Figma socket server at ${wsUrl}...`);
|
|
48
|
+
ws = new import_ws.default(wsUrl);
|
|
49
|
+
ws.on("open", () => {
|
|
50
|
+
logger2.info("Connected to Figma socket server");
|
|
51
|
+
});
|
|
52
|
+
ws.on("message", (data) => {
|
|
53
|
+
try {
|
|
54
|
+
const json = JSON.parse(data);
|
|
55
|
+
const myResponse = json.message;
|
|
56
|
+
logger2.debug(`Received message: ${JSON.stringify(myResponse)}`);
|
|
57
|
+
logger2.log("myResponse" + JSON.stringify(myResponse));
|
|
58
|
+
if (myResponse.id && pendingRequests.has(myResponse.id) && myResponse.result) {
|
|
59
|
+
const request = pendingRequests.get(myResponse.id);
|
|
60
|
+
clearTimeout(request.timeout);
|
|
61
|
+
if (myResponse.error) {
|
|
62
|
+
logger2.error(`Error from Figma: ${myResponse.error}`);
|
|
63
|
+
request.reject(new Error(myResponse.error));
|
|
64
|
+
} else if (myResponse.result) {
|
|
65
|
+
request.resolve(myResponse.result);
|
|
66
|
+
}
|
|
67
|
+
pendingRequests.delete(myResponse.id);
|
|
68
|
+
} else {
|
|
69
|
+
logger2.info(
|
|
70
|
+
`Received broadcast message: ${JSON.stringify(myResponse)}`
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
} catch (error) {
|
|
74
|
+
logger2.error(
|
|
75
|
+
`Error parsing message: ${error instanceof Error ? error.message : String(error)}`
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
ws.on("error", (error) => {
|
|
80
|
+
logger2.error(`Socket error: ${error}`);
|
|
81
|
+
});
|
|
82
|
+
ws.on("close", () => {
|
|
83
|
+
logger2.info("Disconnected from Figma socket server");
|
|
84
|
+
ws = null;
|
|
85
|
+
for (const [id, request] of pendingRequests.entries()) {
|
|
86
|
+
clearTimeout(request.timeout);
|
|
87
|
+
request.reject(new Error("Connection closed"));
|
|
88
|
+
pendingRequests.delete(id);
|
|
89
|
+
}
|
|
90
|
+
logger2.info("Attempting to reconnect in 2 seconds...");
|
|
91
|
+
setTimeout(() => connectToFigma2(port), 2e3);
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
function sendCommandToFigma2(command, params = {}) {
|
|
95
|
+
return new Promise((resolve, reject) => {
|
|
96
|
+
if (!ws || ws.readyState !== import_ws.default.OPEN) {
|
|
97
|
+
connectToFigma2();
|
|
98
|
+
reject(new Error("Not connected to Figma. Attempting to connect..."));
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const id = (0, import_uuid.v4)();
|
|
102
|
+
const request = {
|
|
103
|
+
id,
|
|
104
|
+
type: "message",
|
|
105
|
+
message: {
|
|
106
|
+
id,
|
|
107
|
+
command,
|
|
108
|
+
params: {
|
|
109
|
+
...params
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
const timeout = setTimeout(() => {
|
|
114
|
+
if (pendingRequests.has(id)) {
|
|
115
|
+
pendingRequests.delete(id);
|
|
116
|
+
logger2.error(`Request ${id} to Figma timed out after 30 seconds`);
|
|
117
|
+
reject(new Error("Request to Figma timed out"));
|
|
118
|
+
}
|
|
119
|
+
}, 3e4);
|
|
120
|
+
pendingRequests.set(id, { resolve, reject, timeout });
|
|
121
|
+
logger2.info(`Sending command to Figma: ${command}`);
|
|
122
|
+
logger2.debug(`Request details: ${JSON.stringify(request)}`);
|
|
123
|
+
ws.send(JSON.stringify(request));
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
connectToFigma: connectToFigma2,
|
|
128
|
+
sendCommandToFigma: sendCommandToFigma2
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// src/server.ts
|
|
42
133
|
var logger = {
|
|
43
134
|
info: (message) => process.stderr.write(`[INFO] ${message}
|
|
44
135
|
`),
|
|
@@ -51,38 +142,95 @@ var logger = {
|
|
|
51
142
|
log: (message) => process.stderr.write(`[LOG] ${message}
|
|
52
143
|
`)
|
|
53
144
|
};
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
145
|
+
var BASE_DIR = import_path.default.join(__dirname, "rules");
|
|
146
|
+
function levenshtein(a, b) {
|
|
147
|
+
const m = a.length;
|
|
148
|
+
const n = b.length;
|
|
149
|
+
if (m === 0) return n;
|
|
150
|
+
if (n === 0) return m;
|
|
151
|
+
const dp = Array.from(
|
|
152
|
+
{ length: m + 1 },
|
|
153
|
+
() => Array(n + 1).fill(0)
|
|
61
154
|
);
|
|
62
|
-
|
|
155
|
+
for (let i = 0; i <= m; i++) dp[i][0] = i;
|
|
156
|
+
for (let j = 0; j <= n; j++) dp[0][j] = j;
|
|
157
|
+
for (let i = 1; i <= m; i++) {
|
|
158
|
+
for (let j = 1; j <= n; j++) {
|
|
159
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
160
|
+
dp[i][j] = Math.min(
|
|
161
|
+
dp[i - 1][j] + 1,
|
|
162
|
+
// 삭제
|
|
163
|
+
dp[i][j - 1] + 1,
|
|
164
|
+
// 삽입
|
|
165
|
+
dp[i - 1][j - 1] + cost
|
|
166
|
+
// 교체
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return dp[m][n];
|
|
63
171
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
172
|
+
function getSimilarityScore(target, candidate) {
|
|
173
|
+
if (target === candidate) return 100;
|
|
174
|
+
if (candidate.startsWith(target) || target.startsWith(candidate)) return 95;
|
|
175
|
+
if (candidate.includes(target) || target.includes(candidate)) return 85;
|
|
176
|
+
const distance = levenshtein(target, candidate);
|
|
177
|
+
const maxLen = Math.max(target.length, candidate.length);
|
|
178
|
+
const normalized = 1 - distance / maxLen;
|
|
179
|
+
const score = Math.floor(normalized * 80);
|
|
180
|
+
return score > 0 ? score : 0;
|
|
181
|
+
}
|
|
182
|
+
var STORIES_EXT = ".stories.mdx";
|
|
183
|
+
function findSimilarStoriesFiles(baseDir, componentName) {
|
|
184
|
+
const compName = componentName.toLowerCase();
|
|
185
|
+
const files = import_fs.default.readdirSync(baseDir).filter((file) => file.toLowerCase().endsWith(STORIES_EXT));
|
|
186
|
+
const scored = files.map((file) => {
|
|
187
|
+
const baseName = file.toLowerCase().replace(new RegExp(`${STORIES_EXT.replace(".", "\\.")}$`), "");
|
|
188
|
+
return {
|
|
189
|
+
file,
|
|
190
|
+
baseName,
|
|
191
|
+
score: getSimilarityScore(compName, baseName)
|
|
192
|
+
};
|
|
193
|
+
}).filter(({ score }) => score > 0).sort((a, b) => b.score - a.score);
|
|
194
|
+
return scored;
|
|
195
|
+
}
|
|
196
|
+
var server = new import_mcp.McpServer(
|
|
197
|
+
{
|
|
198
|
+
name: "@jongh/figma-plugin",
|
|
199
|
+
version: "1.0.0"
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
capabilities: {
|
|
203
|
+
logging: logger,
|
|
204
|
+
resources: {
|
|
205
|
+
subscribe: true,
|
|
206
|
+
listChanged: true
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
);
|
|
70
211
|
var args = process.argv.slice(2);
|
|
71
212
|
var serverArg = args.find((arg) => arg.startsWith("--server="));
|
|
72
213
|
var serverUrl = serverArg ? serverArg.split("=")[1] : "localhost";
|
|
73
214
|
var WS_URL = serverUrl === "localhost" ? `ws://${serverUrl}` : `wss://${serverUrl}`;
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
215
|
+
var { connectToFigma, sendCommandToFigma } = createFigmaClient({
|
|
216
|
+
logger,
|
|
217
|
+
serverUrl,
|
|
218
|
+
wsUrlBase: WS_URL
|
|
219
|
+
});
|
|
220
|
+
server.registerTool(
|
|
221
|
+
"get_selection",
|
|
222
|
+
{
|
|
223
|
+
description: "Get information about the current selection in Figma",
|
|
224
|
+
inputSchema: {}
|
|
225
|
+
},
|
|
78
226
|
async () => {
|
|
79
227
|
try {
|
|
80
|
-
const result = await sendCommandToFigma("
|
|
228
|
+
const result = await sendCommandToFigma("get_selection");
|
|
81
229
|
return {
|
|
82
230
|
content: [
|
|
83
231
|
{
|
|
84
232
|
type: "text",
|
|
85
|
-
text:
|
|
233
|
+
text: "test"
|
|
86
234
|
}
|
|
87
235
|
]
|
|
88
236
|
};
|
|
@@ -91,119 +239,112 @@ server.tool(
|
|
|
91
239
|
content: [
|
|
92
240
|
{
|
|
93
241
|
type: "text",
|
|
94
|
-
text: `Error getting
|
|
242
|
+
text: `Error getting selection: ${error instanceof Error ? error.message : String(error)}`
|
|
95
243
|
}
|
|
96
244
|
]
|
|
97
245
|
};
|
|
98
246
|
}
|
|
99
247
|
}
|
|
100
248
|
);
|
|
101
|
-
server.
|
|
102
|
-
"
|
|
103
|
-
new import_mcp.ResourceTemplate("figma://selection/{sessionId}", { list: void 0 }),
|
|
249
|
+
server.registerPrompt(
|
|
250
|
+
"design system components",
|
|
104
251
|
{
|
|
105
|
-
|
|
106
|
-
description: "Stores the full Figma selection data when it's too large to return inline"
|
|
252
|
+
description: "help analyze design system common components"
|
|
107
253
|
},
|
|
108
|
-
|
|
109
|
-
const result = await sendCommandToFigma("get_selection");
|
|
254
|
+
() => {
|
|
110
255
|
return {
|
|
111
|
-
|
|
256
|
+
messages: [
|
|
112
257
|
{
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
258
|
+
role: "assistant",
|
|
259
|
+
content: {
|
|
260
|
+
type: "text",
|
|
261
|
+
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.
|
|
262
|
+
\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.`
|
|
263
|
+
}
|
|
116
264
|
}
|
|
117
|
-
]
|
|
265
|
+
],
|
|
266
|
+
description: "Troubleshooting guide for connection issues between Figma plugin and MCP server"
|
|
118
267
|
};
|
|
119
268
|
}
|
|
120
269
|
);
|
|
121
|
-
server.
|
|
122
|
-
"
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
270
|
+
server.registerTool(
|
|
271
|
+
"get_rule_of_components",
|
|
272
|
+
{
|
|
273
|
+
title: "Rule of design system component",
|
|
274
|
+
description: [
|
|
275
|
+
"\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.",
|
|
276
|
+
"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,",
|
|
277
|
+
"\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.",
|
|
278
|
+
"\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."
|
|
279
|
+
].join(" "),
|
|
280
|
+
inputSchema: import_zod.z.object({
|
|
281
|
+
componentName: import_zod.z.string().describe(
|
|
282
|
+
"Figma/\uB514\uC790\uC778 \uC2DC\uC2A4\uD15C\uC5D0\uC11C \uC0AC\uC6A9\uB41C \uACF5\uD1B5 \uCEF4\uD3EC\uB10C\uD2B8 \uC774\uB984 (\uC608: 'button', 'input', 'modal')"
|
|
283
|
+
)
|
|
284
|
+
})
|
|
285
|
+
},
|
|
286
|
+
async ({ componentName }) => {
|
|
287
|
+
const similarFiles = findSimilarStoriesFiles(BASE_DIR, componentName);
|
|
288
|
+
const texts = similarFiles.map(
|
|
289
|
+
({ file }) => import_fs.default.readFileSync(import_path.default.join(BASE_DIR, file), "utf-8")
|
|
290
|
+
);
|
|
127
291
|
return {
|
|
128
292
|
content: [
|
|
129
293
|
{
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
uri: resourceUri,
|
|
133
|
-
name: "Current Figma Selection",
|
|
134
|
-
mimeType: "application/json",
|
|
135
|
-
description: "Full raw selection JSON from Figma"
|
|
294
|
+
type: "text",
|
|
295
|
+
text: `${texts.join("")}`
|
|
136
296
|
}
|
|
137
297
|
]
|
|
138
298
|
};
|
|
139
299
|
}
|
|
140
300
|
);
|
|
141
|
-
server.
|
|
142
|
-
"
|
|
143
|
-
|
|
144
|
-
{
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
async ({ nodeId }) => {
|
|
148
|
-
try {
|
|
149
|
-
const result = await sendCommandToFigma("get_node_info", { nodeId });
|
|
150
|
-
return {
|
|
151
|
-
content: [
|
|
152
|
-
{
|
|
153
|
-
type: "text",
|
|
154
|
-
text: JSON.stringify(result)
|
|
155
|
-
}
|
|
156
|
-
]
|
|
157
|
-
};
|
|
158
|
-
} catch (error) {
|
|
301
|
+
server.registerResource(
|
|
302
|
+
"file",
|
|
303
|
+
// 리소스 ID
|
|
304
|
+
new import_mcp.ResourceTemplate("file://{path}/", {
|
|
305
|
+
list: async () => {
|
|
306
|
+
const paths = import_fs.default.readdirSync(BASE_DIR);
|
|
159
307
|
return {
|
|
160
|
-
|
|
161
|
-
{
|
|
162
|
-
|
|
163
|
-
|
|
308
|
+
resources: paths.map((path2) => ({
|
|
309
|
+
uri: `file://${path2}`,
|
|
310
|
+
name: path2,
|
|
311
|
+
title: path2,
|
|
312
|
+
description: "local file from design-rules",
|
|
313
|
+
mimeType: "text/plain",
|
|
314
|
+
// 필요시 확장자 보고 바꾸셔도 됩니다
|
|
315
|
+
_meta: {
|
|
316
|
+
filePath: path2
|
|
164
317
|
}
|
|
165
|
-
|
|
318
|
+
}))
|
|
166
319
|
};
|
|
167
320
|
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
321
|
+
}),
|
|
322
|
+
{
|
|
323
|
+
title: "design rule",
|
|
324
|
+
description: "important rules and guidelines for resolving design data"
|
|
325
|
+
},
|
|
326
|
+
// resources/read 핸들러
|
|
327
|
+
async (uri, props) => {
|
|
328
|
+
const text = import_fs.default.readFileSync(
|
|
329
|
+
import_path.default.join(BASE_DIR, props.path),
|
|
330
|
+
"utf-8"
|
|
331
|
+
);
|
|
174
332
|
return {
|
|
175
|
-
|
|
333
|
+
contents: [
|
|
176
334
|
{
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
text: `When users report connection issues, ask them to check:
|
|
181
|
-
|
|
182
|
-
1. Figma Plugin Connection:
|
|
183
|
-
- Did you click the "Connect" button in the Figma plugin?
|
|
184
|
-
- Is the Figma plugin running and showing a connected status?
|
|
185
|
-
- Try refreshing the plugin or restarting it if needed
|
|
186
|
-
|
|
187
|
-
2. MCP Connection in Cursor:
|
|
188
|
-
- Go to Cursor Settings/Preferences
|
|
189
|
-
- Check if the MCP server connection is properly configured(green light)
|
|
190
|
-
- Verify the MCP server is running and connected
|
|
191
|
-
- Look for any connection error messages in the settings
|
|
192
|
-
|
|
193
|
-
If both are properly connected and issues persist, try:
|
|
194
|
-
- Restarting both the Figma plugin and Cursor
|
|
195
|
-
- Checking if the WebSocket connection is blocked by firewall
|
|
196
|
-
- Verifying the correct server URL and port configuration`
|
|
197
|
-
}
|
|
335
|
+
uri: uri.href,
|
|
336
|
+
mimeType: "text/plain",
|
|
337
|
+
text
|
|
198
338
|
}
|
|
199
|
-
]
|
|
200
|
-
description: "Troubleshooting guide for connection issues between Figma plugin and MCP server"
|
|
339
|
+
]
|
|
201
340
|
};
|
|
202
341
|
}
|
|
203
342
|
);
|
|
204
|
-
server.
|
|
343
|
+
server.registerPrompt(
|
|
205
344
|
"data_analysis_strategy",
|
|
206
|
-
|
|
345
|
+
{
|
|
346
|
+
description: "Best practices for analyzing Figma design data"
|
|
347
|
+
},
|
|
207
348
|
() => {
|
|
208
349
|
return {
|
|
209
350
|
messages: [
|
|
@@ -229,6 +370,8 @@ server.prompt(
|
|
|
229
370
|
- Identify responsive design patterns
|
|
230
371
|
- Note alignment and positioning strategies
|
|
231
372
|
|
|
373
|
+
|
|
374
|
+
|
|
232
375
|
|
|
233
376
|
`
|
|
234
377
|
}
|
|
@@ -238,91 +381,6 @@ server.prompt(
|
|
|
238
381
|
};
|
|
239
382
|
}
|
|
240
383
|
);
|
|
241
|
-
function connectToFigma(port = 3055) {
|
|
242
|
-
if (ws && ws.readyState === import_ws.default.OPEN) {
|
|
243
|
-
logger.info("Already connected to Figma");
|
|
244
|
-
return;
|
|
245
|
-
}
|
|
246
|
-
const wsUrl = serverUrl === "localhost" ? `${WS_URL}:${port}` : WS_URL;
|
|
247
|
-
logger.info(`Connecting to Figma socket server at ${wsUrl}...`);
|
|
248
|
-
ws = new import_ws.default(wsUrl);
|
|
249
|
-
ws.on("open", () => {
|
|
250
|
-
logger.info("Connected to Figma socket server");
|
|
251
|
-
});
|
|
252
|
-
ws.on("message", (data) => {
|
|
253
|
-
try {
|
|
254
|
-
const json = JSON.parse(data);
|
|
255
|
-
const myResponse = json.message;
|
|
256
|
-
logger.debug(`Received message: ${JSON.stringify(myResponse)}`);
|
|
257
|
-
logger.log("myResponse" + JSON.stringify(myResponse));
|
|
258
|
-
if (myResponse.id && pendingRequests.has(myResponse.id) && myResponse.result) {
|
|
259
|
-
const request = pendingRequests.get(myResponse.id);
|
|
260
|
-
clearTimeout(request.timeout);
|
|
261
|
-
if (myResponse.error) {
|
|
262
|
-
logger.error(`Error from Figma: ${myResponse.error}`);
|
|
263
|
-
request.reject(new Error(myResponse.error));
|
|
264
|
-
} else {
|
|
265
|
-
if (myResponse.result) {
|
|
266
|
-
request.resolve(myResponse.result);
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
pendingRequests.delete(myResponse.id);
|
|
270
|
-
} else {
|
|
271
|
-
logger.info(`Received broadcast message: ${JSON.stringify(myResponse)}`);
|
|
272
|
-
}
|
|
273
|
-
} catch (error) {
|
|
274
|
-
logger.error(
|
|
275
|
-
`Error parsing message: ${error instanceof Error ? error.message : String(error)}`
|
|
276
|
-
);
|
|
277
|
-
}
|
|
278
|
-
});
|
|
279
|
-
ws.on("error", (error) => {
|
|
280
|
-
logger.error(`Socket error: ${error}`);
|
|
281
|
-
});
|
|
282
|
-
ws.on("close", () => {
|
|
283
|
-
logger.info("Disconnected from Figma socket server");
|
|
284
|
-
ws = null;
|
|
285
|
-
for (const [id, request] of pendingRequests.entries()) {
|
|
286
|
-
clearTimeout(request.timeout);
|
|
287
|
-
request.reject(new Error("Connection closed"));
|
|
288
|
-
pendingRequests.delete(id);
|
|
289
|
-
}
|
|
290
|
-
logger.info("Attempting to reconnect in 2 seconds...");
|
|
291
|
-
setTimeout(() => connectToFigma(port), 2e3);
|
|
292
|
-
});
|
|
293
|
-
}
|
|
294
|
-
function sendCommandToFigma(command, params = {}) {
|
|
295
|
-
return new Promise((resolve, reject) => {
|
|
296
|
-
if (!ws || ws.readyState !== import_ws.default.OPEN) {
|
|
297
|
-
connectToFigma();
|
|
298
|
-
reject(new Error("Not connected to Figma. Attempting to connect..."));
|
|
299
|
-
return;
|
|
300
|
-
}
|
|
301
|
-
const id = (0, import_uuid.v4)();
|
|
302
|
-
const request = {
|
|
303
|
-
id,
|
|
304
|
-
type: "message",
|
|
305
|
-
message: {
|
|
306
|
-
id,
|
|
307
|
-
command,
|
|
308
|
-
params: {
|
|
309
|
-
...params
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
};
|
|
313
|
-
const timeout = setTimeout(() => {
|
|
314
|
-
if (pendingRequests.has(id)) {
|
|
315
|
-
pendingRequests.delete(id);
|
|
316
|
-
logger.error(`Request ${id} to Figma timed out after 30 seconds`);
|
|
317
|
-
reject(new Error("Request to Figma timed out"));
|
|
318
|
-
}
|
|
319
|
-
}, 3e4);
|
|
320
|
-
pendingRequests.set(id, { resolve, reject, timeout });
|
|
321
|
-
logger.info(`Sending command to Figma: ${command}`);
|
|
322
|
-
logger.debug(`Request details: ${JSON.stringify(request)}`);
|
|
323
|
-
ws.send(JSON.stringify(request));
|
|
324
|
-
});
|
|
325
|
-
}
|
|
326
384
|
async function main() {
|
|
327
385
|
try {
|
|
328
386
|
connectToFigma();
|
|
@@ -342,7 +400,3 @@ main().catch((error) => {
|
|
|
342
400
|
);
|
|
343
401
|
process.exit(1);
|
|
344
402
|
});
|
|
345
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
346
|
-
0 && (module.exports = {
|
|
347
|
-
compressData
|
|
348
|
-
});
|
package/package.json
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ject-5-fe/figma-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"dev": "plugma dev",
|
|
7
7
|
"socket": "tsx src/socket.ts",
|
|
8
8
|
"start": "concurrently \"yarn run socket\" \"yarn run dev\" --names \"SOCKET,MCP\" --prefix-colors \"blue,green\"",
|
|
9
|
-
"build:server": "tsup"
|
|
9
|
+
"build:server": "tsup",
|
|
10
|
+
"export-icons": "tsx src/cli/exportIcons.ts"
|
|
10
11
|
},
|
|
11
12
|
"bin": "dist/server/server.cjs",
|
|
12
13
|
"files": [
|
|
13
14
|
"dist/server/server.cjs"
|
|
14
15
|
],
|
|
15
16
|
"dependencies": {
|
|
16
|
-
"@modelcontextprotocol/sdk": "^1",
|
|
17
|
-
"es-toolkit": "^1.
|
|
17
|
+
"@modelcontextprotocol/sdk": "^1.24",
|
|
18
|
+
"es-toolkit": "^1.39.10",
|
|
18
19
|
"react": "^18.3.1",
|
|
19
20
|
"react-dom": "^18.3.1",
|
|
20
21
|
"uuid": "^11",
|