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