@seed-design/mcp 0.0.6 → 0.0.15
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/bin/main.mjs +16904 -0
- package/bin/socket.mjs +155 -0
- package/package.json +8 -3
- package/src/bin/main.ts +52 -0
- package/src/bin/socket.ts +194 -0
- package/src/logger.ts +32 -34
- package/src/prompts.ts +27 -0
- package/src/responses.ts +90 -0
- package/src/tools.ts +436 -0
- package/src/types.ts +67 -0
- package/src/websocket.ts +248 -0
- package/bin/index.mjs +0 -4964
- package/src/bin/index.ts +0 -34
- package/src/config.ts +0 -166
- package/src/figma.ts +0 -211
- package/src/index.ts +0 -36
- package/src/save-image.ts +0 -16
- package/src/server.ts +0 -288
package/src/websocket.ts
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import { v4 as uuidv4 } from "uuid";
|
|
2
|
+
import WebSocket from "ws";
|
|
3
|
+
import { logger } from "./logger";
|
|
4
|
+
import type { CommandProgressUpdate, FigmaCommand, FigmaResponse } from "./types";
|
|
5
|
+
|
|
6
|
+
export interface FigmaWebSocketClient {
|
|
7
|
+
connectToFigma: (port?: number) => void;
|
|
8
|
+
joinChannel: (channelName: string) => Promise<void>;
|
|
9
|
+
sendCommandToFigma: (
|
|
10
|
+
command: FigmaCommand,
|
|
11
|
+
params?: unknown,
|
|
12
|
+
timeoutMs?: number,
|
|
13
|
+
) => Promise<unknown>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Define a more specific type with an index signature to allow any property access
|
|
17
|
+
interface ProgressMessage {
|
|
18
|
+
message: FigmaResponse | any;
|
|
19
|
+
type?: string;
|
|
20
|
+
id?: string;
|
|
21
|
+
[key: string]: any; // Allow any other properties
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function createFigmaWebSocketClient(serverUrl: string) {
|
|
25
|
+
const WS_URL = serverUrl === "localhost" ? `ws://${serverUrl}` : `wss://${serverUrl}`;
|
|
26
|
+
|
|
27
|
+
// Track which channel each client is in
|
|
28
|
+
let currentChannel: string | null = null;
|
|
29
|
+
|
|
30
|
+
// WebSocket connection and request tracking
|
|
31
|
+
let ws: WebSocket | null = null;
|
|
32
|
+
const pendingRequests = new Map<
|
|
33
|
+
string,
|
|
34
|
+
{
|
|
35
|
+
resolve: (value: unknown) => void;
|
|
36
|
+
reject: (reason: unknown) => void;
|
|
37
|
+
timeout: ReturnType<typeof setTimeout>;
|
|
38
|
+
lastActivity: number; // Add timestamp for last activity
|
|
39
|
+
}
|
|
40
|
+
>();
|
|
41
|
+
|
|
42
|
+
// Update the connectToFigma function
|
|
43
|
+
function connectToFigma(port = 3055) {
|
|
44
|
+
// If already connected, do nothing
|
|
45
|
+
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
46
|
+
logger.info("Already connected to Figma");
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const wsUrl = serverUrl === "localhost" ? `${WS_URL}:${port}` : WS_URL;
|
|
51
|
+
logger.info(`Connecting to Figma socket server at ${wsUrl}...`);
|
|
52
|
+
ws = new WebSocket(wsUrl);
|
|
53
|
+
|
|
54
|
+
ws.on("open", () => {
|
|
55
|
+
logger.info("Connected to Figma socket server");
|
|
56
|
+
// Reset channel on new connection
|
|
57
|
+
currentChannel = null;
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
ws.on("message", (data: any) => {
|
|
61
|
+
try {
|
|
62
|
+
const json = JSON.parse(data) as ProgressMessage;
|
|
63
|
+
|
|
64
|
+
// Handle progress updates
|
|
65
|
+
if (json.type === "progress_update") {
|
|
66
|
+
const progressData = json.message.data as CommandProgressUpdate;
|
|
67
|
+
const requestId = json.id || "";
|
|
68
|
+
|
|
69
|
+
if (requestId && pendingRequests.has(requestId)) {
|
|
70
|
+
const request = pendingRequests.get(requestId)!;
|
|
71
|
+
|
|
72
|
+
// Update last activity timestamp
|
|
73
|
+
request.lastActivity = Date.now();
|
|
74
|
+
|
|
75
|
+
// Reset the timeout to prevent timeouts during long-running operations
|
|
76
|
+
clearTimeout(request.timeout);
|
|
77
|
+
|
|
78
|
+
// Create a new timeout
|
|
79
|
+
request.timeout = setTimeout(() => {
|
|
80
|
+
if (pendingRequests.has(requestId)) {
|
|
81
|
+
logger.error(`Request ${requestId} timed out after extended period of inactivity`);
|
|
82
|
+
pendingRequests.delete(requestId);
|
|
83
|
+
request.reject(new Error("Request to Figma timed out"));
|
|
84
|
+
}
|
|
85
|
+
}, 60000); // 60 second timeout for inactivity
|
|
86
|
+
|
|
87
|
+
// Log progress
|
|
88
|
+
logger.info(
|
|
89
|
+
`Progress update for ${progressData.commandType}: ${progressData.progress}% - ${progressData.message}`,
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
// For completed updates, we could resolve the request early if desired
|
|
93
|
+
if (progressData.status === "completed" && progressData.progress === 100) {
|
|
94
|
+
// Optionally resolve early with partial data
|
|
95
|
+
// request.resolve(progressData.payload);
|
|
96
|
+
// pendingRequests.delete(requestId);
|
|
97
|
+
|
|
98
|
+
// Instead, just log the completion, wait for final result from Figma
|
|
99
|
+
logger.info(
|
|
100
|
+
`Operation ${progressData.commandType} completed, waiting for final result`,
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Handle regular responses
|
|
108
|
+
const myResponse = json.message;
|
|
109
|
+
logger.debug(`Received message: ${JSON.stringify(myResponse)}`);
|
|
110
|
+
logger.log("myResponse" + JSON.stringify(myResponse));
|
|
111
|
+
|
|
112
|
+
// Handle response to a request
|
|
113
|
+
if (
|
|
114
|
+
myResponse.id &&
|
|
115
|
+
pendingRequests.has(myResponse.id) &&
|
|
116
|
+
(myResponse.result || myResponse.error)
|
|
117
|
+
) {
|
|
118
|
+
const request = pendingRequests.get(myResponse.id)!;
|
|
119
|
+
clearTimeout(request.timeout);
|
|
120
|
+
|
|
121
|
+
if (myResponse.error) {
|
|
122
|
+
logger.error(`Error from Figma: ${myResponse.error}`);
|
|
123
|
+
request.reject(new Error(myResponse.error));
|
|
124
|
+
} else {
|
|
125
|
+
if (myResponse.result) {
|
|
126
|
+
request.resolve(myResponse.result);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
pendingRequests.delete(myResponse.id);
|
|
131
|
+
} else {
|
|
132
|
+
// Handle broadcast messages or events not associated with a request ID
|
|
133
|
+
logger.info(`Received broadcast message: ${JSON.stringify(myResponse)}`);
|
|
134
|
+
}
|
|
135
|
+
} catch (error) {
|
|
136
|
+
logger.error(
|
|
137
|
+
`Error parsing message: ${error instanceof Error ? error.message : String(error)}`,
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
ws.on("error", (error) => {
|
|
143
|
+
logger.error(`Socket error: ${error}`);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
ws.on("close", () => {
|
|
147
|
+
logger.info("Disconnected from Figma socket server");
|
|
148
|
+
ws = null;
|
|
149
|
+
|
|
150
|
+
// Reject all pending requests
|
|
151
|
+
for (const [id, request] of pendingRequests.entries()) {
|
|
152
|
+
clearTimeout(request.timeout);
|
|
153
|
+
request.reject(new Error("Connection closed"));
|
|
154
|
+
pendingRequests.delete(id);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Attempt to reconnect
|
|
158
|
+
logger.info("Attempting to reconnect in 2 seconds...");
|
|
159
|
+
setTimeout(() => connectToFigma(port), 2000);
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Function to join a channel
|
|
164
|
+
async function joinChannel(channelName: string): Promise<void> {
|
|
165
|
+
if (!ws || ws.readyState !== WebSocket.OPEN) {
|
|
166
|
+
throw new Error("Not connected to Figma");
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
try {
|
|
170
|
+
await sendCommandToFigma("join", { channel: channelName });
|
|
171
|
+
currentChannel = channelName;
|
|
172
|
+
logger.info(`Joined channel: ${channelName}`);
|
|
173
|
+
} catch (error) {
|
|
174
|
+
logger.error(
|
|
175
|
+
`Failed to join channel: ${error instanceof Error ? error.message : String(error)}`,
|
|
176
|
+
);
|
|
177
|
+
throw error;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Function to send commands to Figma
|
|
182
|
+
function sendCommandToFigma(
|
|
183
|
+
command: FigmaCommand,
|
|
184
|
+
params: unknown = {},
|
|
185
|
+
timeoutMs = 30000,
|
|
186
|
+
): Promise<unknown> {
|
|
187
|
+
return new Promise((resolve, reject) => {
|
|
188
|
+
// If not connected, try to connect first
|
|
189
|
+
if (!ws || ws.readyState !== WebSocket.OPEN) {
|
|
190
|
+
connectToFigma();
|
|
191
|
+
reject(new Error("Not connected to Figma. Attempting to connect..."));
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Check if we need a channel for this command
|
|
196
|
+
const requiresChannel = command !== "join";
|
|
197
|
+
if (requiresChannel && !currentChannel) {
|
|
198
|
+
reject(new Error("Must join a channel before sending commands"));
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const id = uuidv4();
|
|
203
|
+
const request = {
|
|
204
|
+
id,
|
|
205
|
+
type: command === "join" ? "join" : "message",
|
|
206
|
+
...(command === "join"
|
|
207
|
+
? { channel: (params as any).channel }
|
|
208
|
+
: { channel: currentChannel }),
|
|
209
|
+
message: {
|
|
210
|
+
id,
|
|
211
|
+
command,
|
|
212
|
+
params: {
|
|
213
|
+
...(params as any),
|
|
214
|
+
commandId: id, // Include the command ID in params
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
// Set timeout for request
|
|
220
|
+
const timeout = setTimeout(() => {
|
|
221
|
+
if (pendingRequests.has(id)) {
|
|
222
|
+
pendingRequests.delete(id);
|
|
223
|
+
logger.error(`Request ${id} to Figma timed out after ${timeoutMs / 1000} seconds`);
|
|
224
|
+
reject(new Error("Request to Figma timed out"));
|
|
225
|
+
}
|
|
226
|
+
}, timeoutMs);
|
|
227
|
+
|
|
228
|
+
// Store the promise callbacks to resolve/reject later
|
|
229
|
+
pendingRequests.set(id, {
|
|
230
|
+
resolve,
|
|
231
|
+
reject,
|
|
232
|
+
timeout,
|
|
233
|
+
lastActivity: Date.now(),
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
// Send the request
|
|
237
|
+
logger.info(`Sending command to Figma: ${command}`);
|
|
238
|
+
logger.debug(`Request details: ${JSON.stringify(request)}`);
|
|
239
|
+
ws.send(JSON.stringify(request));
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return {
|
|
244
|
+
connectToFigma,
|
|
245
|
+
joinChannel,
|
|
246
|
+
sendCommandToFigma,
|
|
247
|
+
};
|
|
248
|
+
}
|