@fridaplatform-stk/figma2frida-mcp 1.0.1 → 1.0.3
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 +112 -152
- package/dist/figma2frida.js +598 -966
- package/dist/figma2frida.js.map +1 -1
- package/package.json +2 -2
package/dist/figma2frida.js
CHANGED
|
@@ -1,11 +1,55 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/figma2frida/figma2frida.ts
|
|
4
|
-
import "dotenv/config";
|
|
5
4
|
import { z } from "zod";
|
|
6
5
|
import yargs from "yargs";
|
|
7
6
|
import { hideBin } from "yargs/helpers";
|
|
8
7
|
import { FastMCP } from "fastmcp";
|
|
8
|
+
import { createRequire } from "module";
|
|
9
|
+
import { fileURLToPath } from "url";
|
|
10
|
+
import { dirname, join } from "path";
|
|
11
|
+
|
|
12
|
+
// src/figma2frida/utils/logger.ts
|
|
13
|
+
function getLogLevel() {
|
|
14
|
+
const isVerbose = process.argv.includes("--verbose") || process.env.DEBUG === "true";
|
|
15
|
+
const isProduction = process.env.NODE_ENV === "production";
|
|
16
|
+
if (isProduction && !isVerbose) {
|
|
17
|
+
return 4 /* SILENT */;
|
|
18
|
+
}
|
|
19
|
+
return 0 /* DEBUG */;
|
|
20
|
+
}
|
|
21
|
+
var isStdioMode = process.env.HTTP_STREAM !== "true" && process.env.HTTP_STREAM !== "1";
|
|
22
|
+
var writeToLog = (level, message, ...args) => {
|
|
23
|
+
const fullMessage = args.length > 0 ? `[${level}] ${message} ${args.map((arg) => typeof arg === "object" ? JSON.stringify(arg) : String(arg)).join(" ")}` : `[${level}] ${message}`;
|
|
24
|
+
if (isStdioMode) {
|
|
25
|
+
process.stderr.write(`${fullMessage}
|
|
26
|
+
`);
|
|
27
|
+
} else {
|
|
28
|
+
console.log(fullMessage);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
var logger = {
|
|
32
|
+
debug: (message, ...args) => {
|
|
33
|
+
if (getLogLevel() <= 0 /* DEBUG */) {
|
|
34
|
+
writeToLog("DEBUG", message, ...args);
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
info: (message, ...args) => {
|
|
38
|
+
if (getLogLevel() <= 1 /* INFO */) {
|
|
39
|
+
writeToLog("INFO", message, ...args);
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
warn: (message, ...args) => {
|
|
43
|
+
if (getLogLevel() <= 2 /* WARN */) {
|
|
44
|
+
writeToLog("WARN", message, ...args);
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
error: (message, ...args) => {
|
|
48
|
+
if (getLogLevel() <= 3 /* ERROR */) {
|
|
49
|
+
writeToLog("ERROR", message, ...args);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
};
|
|
9
53
|
|
|
10
54
|
// src/figma2frida/clients/figma-client.ts
|
|
11
55
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
@@ -22,21 +66,18 @@ var FigmaClient = class {
|
|
|
22
66
|
*/
|
|
23
67
|
async connect(retries = 3) {
|
|
24
68
|
if (this.connected && this.client) {
|
|
25
|
-
|
|
69
|
+
logger.debug("Already connected, skipping connection attempt");
|
|
26
70
|
return;
|
|
27
71
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
console.log(`[FIGMA CLIENT] Current status: connected=${this.connected}, client=${!!this.client}`);
|
|
33
|
-
console.log(`[FIGMA CLIENT] ========================================`);
|
|
72
|
+
logger.debug("Starting connection attempt");
|
|
73
|
+
logger.debug(`URL: ${this.figmaUrl}`);
|
|
74
|
+
logger.debug(`Max retries: ${retries}`);
|
|
75
|
+
logger.debug(`Current status: connected=${this.connected}, client=${!!this.client}`);
|
|
34
76
|
for (let attempt = 0; attempt < retries; attempt++) {
|
|
35
77
|
const attemptStartTime = Date.now();
|
|
36
|
-
|
|
37
|
-
console.log(`[FIGMA CLIENT] Attempt ${attempt + 1}/${retries} starting...`);
|
|
78
|
+
logger.debug(`Attempt ${attempt + 1}/${retries} starting...`);
|
|
38
79
|
try {
|
|
39
|
-
|
|
80
|
+
logger.debug("Creating MCP Client object...");
|
|
40
81
|
this.client = new Client(
|
|
41
82
|
{
|
|
42
83
|
name: "figma-proxy-client",
|
|
@@ -48,66 +89,55 @@ var FigmaClient = class {
|
|
|
48
89
|
}
|
|
49
90
|
}
|
|
50
91
|
);
|
|
51
|
-
|
|
52
|
-
|
|
92
|
+
logger.debug("Client object created");
|
|
93
|
+
logger.debug(`Creating SSE Transport for: ${this.figmaUrl}`);
|
|
53
94
|
const transport = new SSEClientTransport(new URL(this.figmaUrl));
|
|
54
|
-
|
|
55
|
-
|
|
95
|
+
logger.debug("Transport object created");
|
|
96
|
+
logger.debug("Attempting to connect...");
|
|
56
97
|
const connectStartTime = Date.now();
|
|
57
98
|
await this.client.connect(transport);
|
|
58
99
|
const connectTime = Date.now() - connectStartTime;
|
|
59
|
-
|
|
100
|
+
logger.debug(`Connection established in ${connectTime}ms`);
|
|
60
101
|
this.connected = true;
|
|
61
102
|
const totalTime = Date.now() - attemptStartTime;
|
|
62
|
-
|
|
63
|
-
console.log(`[FIGMA CLIENT] \u2705 CONNECTION SUCCESS!`);
|
|
64
|
-
console.log(`[FIGMA CLIENT] URL: ${this.figmaUrl}`);
|
|
65
|
-
console.log(`[FIGMA CLIENT] Attempt: ${attempt + 1}/${retries}`);
|
|
66
|
-
console.log(`[FIGMA CLIENT] Total time: ${totalTime}ms`);
|
|
67
|
-
console.log(`[FIGMA CLIENT] ========================================`);
|
|
103
|
+
logger.debug(`Connected to Figma MCP (${totalTime}ms)`);
|
|
68
104
|
return;
|
|
69
105
|
} catch (error) {
|
|
70
106
|
const attemptTime = Date.now() - attemptStartTime;
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
console.log(`[FIGMA CLIENT] Error type: ${error?.constructor?.name || typeof error}`);
|
|
75
|
-
console.log(`[FIGMA CLIENT] Error message: ${error instanceof Error ? error.message : String(error)}`);
|
|
107
|
+
logger.debug(`Connection attempt ${attempt + 1}/${retries} FAILED (${attemptTime}ms)`);
|
|
108
|
+
logger.debug(`Error type: ${error?.constructor?.name || typeof error}`);
|
|
109
|
+
logger.debug(`Error message: ${error instanceof Error ? error.message : String(error)}`);
|
|
76
110
|
if (error && typeof error === "object") {
|
|
77
111
|
const errorObj = error;
|
|
78
112
|
if ("code" in errorObj) {
|
|
79
|
-
|
|
113
|
+
logger.debug(`Error code: ${errorObj.code}`);
|
|
80
114
|
}
|
|
81
115
|
if ("cause" in errorObj) {
|
|
82
|
-
|
|
116
|
+
logger.debug("Error cause:", errorObj.cause);
|
|
83
117
|
}
|
|
84
118
|
if ("status" in errorObj) {
|
|
85
|
-
|
|
119
|
+
logger.debug(`HTTP status: ${errorObj.status}`);
|
|
86
120
|
}
|
|
87
121
|
if ("statusText" in errorObj) {
|
|
88
|
-
|
|
122
|
+
logger.debug(`HTTP status text: ${errorObj.statusText}`);
|
|
89
123
|
}
|
|
90
124
|
}
|
|
91
125
|
if (error instanceof Error && error.stack) {
|
|
92
|
-
|
|
93
|
-
console.log(error.stack);
|
|
126
|
+
logger.debug("Error stack:", error.stack);
|
|
94
127
|
}
|
|
95
128
|
if (attempt === retries - 1) {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
console.log(`[FIGMA CLIENT] Total attempts: ${retries}`);
|
|
99
|
-
console.log(`[FIGMA CLIENT] URL: ${this.figmaUrl}`);
|
|
100
|
-
console.log(`[FIGMA CLIENT] ========================================`);
|
|
129
|
+
logger.error(`All connection attempts failed (${retries} attempts)`);
|
|
130
|
+
logger.error(`URL: ${this.figmaUrl}`);
|
|
101
131
|
throw new Error(
|
|
102
132
|
`Failed to connect to Figma MCP at ${this.figmaUrl} after ${retries} attempts. Make sure Figma Desktop is running with MCP enabled.`
|
|
103
133
|
);
|
|
104
134
|
}
|
|
105
135
|
const waitTime = 1e3 * (attempt + 1);
|
|
106
|
-
|
|
136
|
+
logger.debug(`Waiting ${waitTime}ms before retry...`);
|
|
107
137
|
await new Promise(
|
|
108
138
|
(resolve) => setTimeout(resolve, waitTime)
|
|
109
139
|
);
|
|
110
|
-
|
|
140
|
+
logger.debug("Wait complete, retrying...");
|
|
111
141
|
}
|
|
112
142
|
}
|
|
113
143
|
}
|
|
@@ -116,10 +146,10 @@ var FigmaClient = class {
|
|
|
116
146
|
*/
|
|
117
147
|
async ensureConnected() {
|
|
118
148
|
if (!this.connected || !this.client) {
|
|
119
|
-
|
|
149
|
+
logger.debug("ensureConnected() called - not connected, attempting connection...");
|
|
120
150
|
await this.connect();
|
|
121
151
|
} else {
|
|
122
|
-
|
|
152
|
+
logger.debug("ensureConnected() called - already connected, skipping");
|
|
123
153
|
}
|
|
124
154
|
}
|
|
125
155
|
/**
|
|
@@ -133,20 +163,17 @@ var FigmaClient = class {
|
|
|
133
163
|
clientFrameworks: options?.clientFrameworks || "react",
|
|
134
164
|
forceCode: options?.forceCode ?? true
|
|
135
165
|
};
|
|
136
|
-
|
|
166
|
+
logger.debug("Calling get_design_context with args:", args);
|
|
137
167
|
try {
|
|
138
168
|
const result = await this.client.callTool({
|
|
139
169
|
name: "get_design_context",
|
|
140
170
|
arguments: args
|
|
141
171
|
});
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
`[FIGMA CLIENT] get_design_context response keys:`,
|
|
145
|
-
Object.keys(result)
|
|
146
|
-
);
|
|
172
|
+
logger.debug(`get_design_context response type: ${typeof result}`);
|
|
173
|
+
logger.debug(`get_design_context response keys: ${Object.keys(result).join(", ")}`);
|
|
147
174
|
return result;
|
|
148
175
|
} catch (error) {
|
|
149
|
-
|
|
176
|
+
logger.warn("Error calling get_design_context, attempting reconnect...");
|
|
150
177
|
this.connected = false;
|
|
151
178
|
await this.connect();
|
|
152
179
|
const result = await this.client.callTool({
|
|
@@ -171,20 +198,17 @@ var FigmaClient = class {
|
|
|
171
198
|
clientLanguages: options?.clientLanguages || "typescript",
|
|
172
199
|
clientFrameworks: options?.clientFrameworks || "react"
|
|
173
200
|
};
|
|
174
|
-
|
|
201
|
+
logger.debug("Calling get_metadata with args:", args);
|
|
175
202
|
try {
|
|
176
203
|
const result = await this.client.callTool({
|
|
177
204
|
name: "get_metadata",
|
|
178
205
|
arguments: args
|
|
179
206
|
});
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
`[FIGMA CLIENT] get_metadata response keys:`,
|
|
183
|
-
Object.keys(result)
|
|
184
|
-
);
|
|
207
|
+
logger.debug(`get_metadata response type: ${typeof result}`);
|
|
208
|
+
logger.debug(`get_metadata response keys: ${Object.keys(result).join(", ")}`);
|
|
185
209
|
return result;
|
|
186
210
|
} catch (error) {
|
|
187
|
-
|
|
211
|
+
logger.warn("Error calling get_metadata, attempting reconnect...");
|
|
188
212
|
this.connected = false;
|
|
189
213
|
await this.connect();
|
|
190
214
|
const result = await this.client.callTool({
|
|
@@ -210,7 +234,7 @@ var FigmaClient = class {
|
|
|
210
234
|
});
|
|
211
235
|
return result;
|
|
212
236
|
} catch (error) {
|
|
213
|
-
|
|
237
|
+
logger.warn("Error calling get_screenshot, attempting reconnect...");
|
|
214
238
|
this.connected = false;
|
|
215
239
|
await this.connect();
|
|
216
240
|
const result = await this.client.callTool({
|
|
@@ -232,7 +256,7 @@ var FigmaClient = class {
|
|
|
232
256
|
await this.client.close();
|
|
233
257
|
this.connected = false;
|
|
234
258
|
this.client = null;
|
|
235
|
-
|
|
259
|
+
logger.debug("Disconnected from Figma MCP");
|
|
236
260
|
}
|
|
237
261
|
}
|
|
238
262
|
};
|
|
@@ -241,51 +265,25 @@ var FigmaClient = class {
|
|
|
241
265
|
import "dotenv/config";
|
|
242
266
|
|
|
243
267
|
// src/figma2frida/services/pinecone/pinecone-service.ts
|
|
244
|
-
import { Pinecone } from "@pinecone-database/pinecone";
|
|
245
268
|
import "dotenv/config";
|
|
246
269
|
var PineconeSearchService = class {
|
|
247
|
-
apiKey;
|
|
248
270
|
indexName;
|
|
249
271
|
namespace;
|
|
250
272
|
minScore;
|
|
251
273
|
topK;
|
|
252
274
|
fridaEmbeddingUrl;
|
|
253
275
|
fridaApiKey;
|
|
254
|
-
|
|
255
|
-
|
|
276
|
+
fridaPineconeProxyUrl;
|
|
277
|
+
fridaEmbeddingModel;
|
|
256
278
|
constructor(options = {}) {
|
|
257
|
-
this.apiKey = options.apiKey || process.env.PINECONE_API_KEY || (() => {
|
|
258
|
-
throw new Error(
|
|
259
|
-
"PINECONE_API_KEY is required. Set it in .env or pass it in options."
|
|
260
|
-
);
|
|
261
|
-
})();
|
|
262
279
|
this.indexName = process.env.PINECONE_INDEX;
|
|
263
280
|
this.namespace = options.namespace;
|
|
264
281
|
this.minScore = parseFloat(process.env.PINECONE_MIN_SCORE);
|
|
265
282
|
this.topK = parseInt(process.env.PINECONE_TOP_K, 10);
|
|
266
|
-
this.fridaEmbeddingUrl =
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
})();
|
|
271
|
-
this.fridaApiKey = options.fridaApiKey || process.env.FRIDA_BEARER_TOKEN || (() => {
|
|
272
|
-
throw new Error(
|
|
273
|
-
"FRIDA_BEARER_TOKEN is required. Set it in .env or pass it in options."
|
|
274
|
-
);
|
|
275
|
-
})();
|
|
276
|
-
this.pinecone = new Pinecone({ apiKey: this.apiKey });
|
|
277
|
-
}
|
|
278
|
-
/**
|
|
279
|
-
* Initializes the Pinecone index (lazy loading)
|
|
280
|
-
* Namespace is set here when getting the index reference using method chaining
|
|
281
|
-
*/
|
|
282
|
-
async initialize() {
|
|
283
|
-
if (!this.index) {
|
|
284
|
-
console.log(`[PINECONE] Connecting to index: ${this.indexName}${this.namespace ? ` namespace "${this.namespace}"` : ""}`);
|
|
285
|
-
const indexRef = this.pinecone.index(this.indexName);
|
|
286
|
-
this.index = this.namespace ? indexRef.namespace(this.namespace) : indexRef;
|
|
287
|
-
console.log(`[PINECONE] Connected to index: ${this.indexName}${this.namespace ? ` namespace "${this.namespace}"` : ""}`);
|
|
288
|
-
}
|
|
283
|
+
this.fridaEmbeddingUrl = process.env.FRIDA_EMBEDDING_URL;
|
|
284
|
+
this.fridaApiKey = process.env.FRIDA_BEARER_TOKEN;
|
|
285
|
+
this.fridaPineconeProxyUrl = process.env.FRIDA_PINECONE_PROXY_URL;
|
|
286
|
+
this.fridaEmbeddingModel = process.env.FRIDA_EMBEDDING_MODEL;
|
|
289
287
|
}
|
|
290
288
|
/**
|
|
291
289
|
* Generates embedding for a text using Frida API
|
|
@@ -294,7 +292,7 @@ var PineconeSearchService = class {
|
|
|
294
292
|
*/
|
|
295
293
|
async generateEmbedding(text) {
|
|
296
294
|
try {
|
|
297
|
-
|
|
295
|
+
logger.debug("Calling Frida Embedding API...");
|
|
298
296
|
const startTime = Date.now();
|
|
299
297
|
const response = await fetch(this.fridaEmbeddingUrl, {
|
|
300
298
|
method: "POST",
|
|
@@ -305,173 +303,88 @@ var PineconeSearchService = class {
|
|
|
305
303
|
},
|
|
306
304
|
body: JSON.stringify({
|
|
307
305
|
input: text,
|
|
308
|
-
model:
|
|
306
|
+
model: this.fridaEmbeddingModel,
|
|
309
307
|
user_id: "pinecone-search",
|
|
310
308
|
email: "design_system@example.com"
|
|
311
309
|
})
|
|
312
310
|
});
|
|
313
|
-
|
|
311
|
+
logger.debug(`API response status: ${response.status}`);
|
|
314
312
|
if (!response.ok) {
|
|
315
313
|
const errorText = await response.text();
|
|
316
|
-
|
|
314
|
+
logger.error("API Error response:", errorText);
|
|
317
315
|
throw new Error(`API Error: ${response.status} ${response.statusText}`);
|
|
318
316
|
}
|
|
319
|
-
|
|
317
|
+
logger.debug("Parsing API response...");
|
|
320
318
|
const data = await response.json();
|
|
321
319
|
if (!data.data || !data.data[0] || !data.data[0].embedding) {
|
|
322
|
-
|
|
320
|
+
logger.error("Invalid response format:", JSON.stringify(data).substring(0, 200));
|
|
323
321
|
throw new Error("Invalid API response format");
|
|
324
322
|
}
|
|
325
323
|
const embedding = data.data[0].embedding;
|
|
326
324
|
const elapsed = Date.now() - startTime;
|
|
327
|
-
|
|
325
|
+
logger.debug(`\u2713 Embedding generated (${embedding.length} dims) in ${elapsed}ms`);
|
|
328
326
|
return embedding;
|
|
329
327
|
} catch (error) {
|
|
330
|
-
|
|
328
|
+
logger.error("Error generating embedding:", error);
|
|
331
329
|
throw new Error(`Failed to generate embedding: ${error instanceof Error ? error.message : String(error)}`);
|
|
332
330
|
}
|
|
333
331
|
}
|
|
334
332
|
/**
|
|
335
333
|
* Parses a Pinecone match to structured format
|
|
336
|
-
*
|
|
334
|
+
* New simplified schema: tag, name, framework, category, documentation
|
|
337
335
|
*/
|
|
338
336
|
parseMatch(match) {
|
|
339
337
|
const metadata = match.metadata || {};
|
|
340
|
-
let props = [];
|
|
341
|
-
try {
|
|
342
|
-
if (metadata.properties) {
|
|
343
|
-
const parsedProps = JSON.parse(
|
|
344
|
-
metadata.properties
|
|
345
|
-
);
|
|
346
|
-
if (Array.isArray(parsedProps)) {
|
|
347
|
-
props = parsedProps.map((p) => ({
|
|
348
|
-
name: p.name || "unknown",
|
|
349
|
-
type_signature: p.type_signature || "unknown",
|
|
350
|
-
default_value: p.default_value,
|
|
351
|
-
description: p.description || "",
|
|
352
|
-
is_utility_prop: p.is_utility_prop || false,
|
|
353
|
-
reflects_to_attribute: p.reflects_to_attribute || false
|
|
354
|
-
}));
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
} catch {
|
|
358
|
-
props = [];
|
|
359
|
-
}
|
|
360
|
-
let events = [];
|
|
361
|
-
try {
|
|
362
|
-
if (metadata.events) {
|
|
363
|
-
const parsedEvents = JSON.parse(
|
|
364
|
-
metadata.events
|
|
365
|
-
);
|
|
366
|
-
if (Array.isArray(parsedEvents)) {
|
|
367
|
-
events = parsedEvents.map((e) => ({
|
|
368
|
-
name: e.name || "unknown",
|
|
369
|
-
payload_type: e.payload_type || "any",
|
|
370
|
-
description: e.description || ""
|
|
371
|
-
}));
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
} catch {
|
|
375
|
-
events = [];
|
|
376
|
-
}
|
|
377
|
-
let methods = [];
|
|
378
|
-
try {
|
|
379
|
-
if (metadata.methods) {
|
|
380
|
-
const parsedMethods = JSON.parse(metadata.methods);
|
|
381
|
-
if (Array.isArray(parsedMethods)) {
|
|
382
|
-
methods = parsedMethods.map((m) => ({
|
|
383
|
-
name: m.name || "unknown",
|
|
384
|
-
parameters: m.parameters || "null",
|
|
385
|
-
description: m.description || ""
|
|
386
|
-
}));
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
} catch {
|
|
390
|
-
methods = [];
|
|
391
|
-
}
|
|
392
|
-
let slots = [];
|
|
393
|
-
try {
|
|
394
|
-
if (metadata.slots) {
|
|
395
|
-
const parsedSlots = JSON.parse(metadata.slots);
|
|
396
|
-
if (Array.isArray(parsedSlots)) {
|
|
397
|
-
slots = parsedSlots.map((s) => ({
|
|
398
|
-
name: s.name || "unknown",
|
|
399
|
-
description: s.description || ""
|
|
400
|
-
}));
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
} catch {
|
|
404
|
-
slots = [];
|
|
405
|
-
}
|
|
406
|
-
let usage_examples = [];
|
|
407
|
-
try {
|
|
408
|
-
if (metadata.usage_examples) {
|
|
409
|
-
const parsedExamples = JSON.parse(metadata.usage_examples);
|
|
410
|
-
if (Array.isArray(parsedExamples)) {
|
|
411
|
-
usage_examples = parsedExamples.map((ex) => ({
|
|
412
|
-
title: ex.title || "",
|
|
413
|
-
description: ex.description || "",
|
|
414
|
-
code: {
|
|
415
|
-
template: ex.code?.template || "",
|
|
416
|
-
script: ex.code?.script
|
|
417
|
-
}
|
|
418
|
-
}));
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
} catch {
|
|
422
|
-
usage_examples = [];
|
|
423
|
-
}
|
|
424
|
-
let usage_limitations = [];
|
|
425
|
-
try {
|
|
426
|
-
if (metadata.usage_limitations) {
|
|
427
|
-
const parsedLimitations = JSON.parse(metadata.usage_limitations);
|
|
428
|
-
if (Array.isArray(parsedLimitations)) {
|
|
429
|
-
usage_limitations = parsedLimitations;
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
} catch {
|
|
433
|
-
usage_limitations = [];
|
|
434
|
-
}
|
|
435
|
-
let hooks_or_services = [];
|
|
436
|
-
try {
|
|
437
|
-
if (metadata.hooks_or_services) {
|
|
438
|
-
const parsedHooks = JSON.parse(metadata.hooks_or_services);
|
|
439
|
-
if (Array.isArray(parsedHooks)) {
|
|
440
|
-
hooks_or_services = parsedHooks;
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
} catch {
|
|
444
|
-
hooks_or_services = [];
|
|
445
|
-
}
|
|
446
|
-
const tag = metadata.selector || metadata.id || "unknown";
|
|
447
338
|
return {
|
|
448
|
-
|
|
339
|
+
id: match.id || "unknown",
|
|
340
|
+
tag: metadata.tag || match.id || "unknown",
|
|
449
341
|
score: match.score || 0,
|
|
342
|
+
name: metadata.name || "",
|
|
343
|
+
framework: metadata.framework || "",
|
|
450
344
|
category: metadata.category || "General",
|
|
451
|
-
|
|
452
|
-
keywords: metadata.keywords || "",
|
|
453
|
-
props,
|
|
454
|
-
events,
|
|
455
|
-
// New fields
|
|
456
|
-
id: metadata.id,
|
|
457
|
-
selector: metadata.selector,
|
|
458
|
-
human_name: metadata.human_name,
|
|
459
|
-
full_readme: metadata.full_readme,
|
|
460
|
-
visual_variants_description: metadata.visual_variants_description,
|
|
461
|
-
framework_type: metadata.framework_type,
|
|
462
|
-
import_statement: metadata.import_statement,
|
|
463
|
-
is_global: metadata.is_global,
|
|
464
|
-
module_import: metadata.module_import,
|
|
465
|
-
content_injection_method: metadata.content_injection_method,
|
|
466
|
-
methods,
|
|
467
|
-
slots,
|
|
468
|
-
usage_limitations,
|
|
469
|
-
usage_examples,
|
|
470
|
-
hooks_or_services
|
|
345
|
+
documentation: metadata.documentation || ""
|
|
471
346
|
};
|
|
472
347
|
}
|
|
473
348
|
/**
|
|
474
|
-
*
|
|
349
|
+
* Queries Pinecone via Frida Proxy API
|
|
350
|
+
* @param embedding - Embedding vector
|
|
351
|
+
* @param topK - Number of results to return
|
|
352
|
+
* @param namespace - Optional namespace
|
|
353
|
+
* @returns Query response from Pinecone
|
|
354
|
+
*/
|
|
355
|
+
async queryPineconeProxy(embedding, topK, namespace) {
|
|
356
|
+
const url = `${this.fridaPineconeProxyUrl}/pinecone/data/${this.indexName}/query`;
|
|
357
|
+
logger.debug(`Querying Frida Pinecone Proxy: ${url}${namespace ? ` (namespace: "${namespace}")` : ""}`);
|
|
358
|
+
const requestBody = {
|
|
359
|
+
vector: embedding,
|
|
360
|
+
topK,
|
|
361
|
+
includeMetadata: true
|
|
362
|
+
};
|
|
363
|
+
if (namespace) {
|
|
364
|
+
requestBody.namespace = namespace;
|
|
365
|
+
}
|
|
366
|
+
const startTime = Date.now();
|
|
367
|
+
const response = await fetch(url, {
|
|
368
|
+
method: "POST",
|
|
369
|
+
headers: {
|
|
370
|
+
"Authorization": `Bearer ${this.fridaApiKey}`,
|
|
371
|
+
"Content-Type": "application/json",
|
|
372
|
+
"Accept": "application/json"
|
|
373
|
+
},
|
|
374
|
+
body: JSON.stringify(requestBody)
|
|
375
|
+
});
|
|
376
|
+
const queryTime = Date.now() - startTime;
|
|
377
|
+
logger.debug(`Proxy API response status: ${response.status} (${queryTime}ms)`);
|
|
378
|
+
if (!response.ok) {
|
|
379
|
+
const errorText = await response.text();
|
|
380
|
+
logger.error("Proxy API Error response:", errorText);
|
|
381
|
+
throw new Error(`Pinecone Proxy API Error: ${response.status} ${response.statusText} - ${errorText}`);
|
|
382
|
+
}
|
|
383
|
+
const data = await response.json();
|
|
384
|
+
return data;
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Searches for components in Pinecone via Frida Proxy
|
|
475
388
|
* @param query - Search query
|
|
476
389
|
* @param options - Search options
|
|
477
390
|
* @returns Structured results
|
|
@@ -480,23 +393,19 @@ var PineconeSearchService = class {
|
|
|
480
393
|
if (!query || typeof query !== "string") {
|
|
481
394
|
throw new Error("Query must be a non-empty string");
|
|
482
395
|
}
|
|
483
|
-
await this.initialize();
|
|
484
396
|
const minScore = options.minScore !== void 0 ? options.minScore : this.minScore;
|
|
485
397
|
const topK = options.topK !== void 0 ? options.topK : this.topK;
|
|
486
|
-
|
|
398
|
+
const namespace = options.namespace !== void 0 ? options.namespace : this.namespace;
|
|
399
|
+
logger.debug(`Starting search - Query: "${query}", topK: ${topK}, minScore: ${minScore}${namespace ? `, namespace: "${namespace}"` : ""}`);
|
|
487
400
|
const embedding = await this.generateEmbedding(query);
|
|
488
|
-
|
|
401
|
+
logger.debug(`Querying Pinecone index "${this.indexName}" via Frida Proxy${namespace ? ` namespace "${namespace}"` : ""}...`);
|
|
489
402
|
const startTime = Date.now();
|
|
490
|
-
const queryResponse = await this.
|
|
491
|
-
vector: embedding,
|
|
492
|
-
topK,
|
|
493
|
-
includeMetadata: true
|
|
494
|
-
});
|
|
403
|
+
const queryResponse = await this.queryPineconeProxy(embedding, topK, namespace);
|
|
495
404
|
const queryTime = Date.now() - startTime;
|
|
496
|
-
|
|
405
|
+
logger.debug(`Query completed in ${queryTime}ms - Found ${queryResponse.matches?.length || 0} matches`);
|
|
497
406
|
const allMatches = queryResponse.matches || [];
|
|
498
407
|
const relevantMatches = allMatches.filter((m) => (m.score || 0) >= minScore);
|
|
499
|
-
|
|
408
|
+
logger.debug(`Filtered results: ${relevantMatches.length} relevant (score >= ${minScore}) out of ${allMatches.length} total`);
|
|
500
409
|
return {
|
|
501
410
|
query,
|
|
502
411
|
totalMatches: allMatches.length,
|
|
@@ -504,7 +413,7 @@ var PineconeSearchService = class {
|
|
|
504
413
|
matches: relevantMatches.map((match) => this.parseMatch(match)),
|
|
505
414
|
lowScoreMatches: allMatches.filter((m) => (m.score || 0) < minScore).map((match) => {
|
|
506
415
|
const metadata = match.metadata || {};
|
|
507
|
-
const tag = metadata.
|
|
416
|
+
const tag = metadata.tag || metadata.id || match.id || "unknown";
|
|
508
417
|
return {
|
|
509
418
|
tag,
|
|
510
419
|
score: match.score || 0
|
|
@@ -514,23 +423,22 @@ var PineconeSearchService = class {
|
|
|
514
423
|
minScore,
|
|
515
424
|
topK,
|
|
516
425
|
indexName: this.indexName,
|
|
517
|
-
namespace
|
|
426
|
+
namespace
|
|
518
427
|
}
|
|
519
428
|
};
|
|
520
429
|
}
|
|
521
430
|
/**
|
|
522
|
-
* Gets details of a specific component by tag
|
|
523
|
-
* @param tag - Component tag
|
|
431
|
+
* Gets details of a specific component by tag or id
|
|
432
|
+
* @param tag - Component tag or id (e.g.: 'wtw-button-split', 'my-badge')
|
|
524
433
|
* @returns Component details or null if it doesn't exist
|
|
525
434
|
*/
|
|
526
435
|
async getComponentDetails(tag) {
|
|
527
436
|
if (!tag || typeof tag !== "string") {
|
|
528
437
|
throw new Error("Tag must be a non-empty string");
|
|
529
438
|
}
|
|
530
|
-
await this.initialize();
|
|
531
439
|
const results = await this.search(tag, { topK: 10, minScore: 0 });
|
|
532
440
|
const exactMatch = results.matches.find(
|
|
533
|
-
(m) => m.tag === tag || m.
|
|
441
|
+
(m) => m.tag === tag || m.id === tag
|
|
534
442
|
);
|
|
535
443
|
if (exactMatch) {
|
|
536
444
|
return exactMatch;
|
|
@@ -696,141 +604,6 @@ ${question}
|
|
|
696
604
|
|
|
697
605
|
## RESPONSE (Follow the mandatory structure above):`.trim();
|
|
698
606
|
}
|
|
699
|
-
/**
|
|
700
|
-
* Builds a specialized prompt for code generation from Figma designs
|
|
701
|
-
*/
|
|
702
|
-
buildCodeGenerationPrompt(originalCode, componentContext) {
|
|
703
|
-
const elementMatches = originalCode.match(/<\w+/g) || [];
|
|
704
|
-
const elementCount = elementMatches.length;
|
|
705
|
-
return `You are an expert in generating code using design system components.
|
|
706
|
-
|
|
707
|
-
**TASK:** Transform the Figma-generated code below to use design system components from the AVAILABLE COMPONENT INFORMATION.
|
|
708
|
-
|
|
709
|
-
**\u{1F6A8} CRITICAL RULES - MANDATORY:**
|
|
710
|
-
|
|
711
|
-
1. **PRESERVE EVERY SINGLE ELEMENT** - The original code has approximately ${elementCount} HTML elements. You MUST transform ALL of them, not just a few examples. Do NOT summarize, simplify, or create examples.
|
|
712
|
-
2. **PRESERVE ALL TEXT CONTENT EXACTLY** - Keep ALL text, labels, placeholders, button text, input values, and content exactly as shown in the original code. DO NOT change, invent, or modify any text content.
|
|
713
|
-
3. **PRESERVE ALL STRUCTURE** - Maintain EVERY div, container, class, style, id, data attribute, and layout element exactly as in the original.
|
|
714
|
-
4. **Use ONLY components from AVAILABLE COMPONENT INFORMATION** - Do NOT use components from other libraries or generic HTML elements unless no alternative exists
|
|
715
|
-
5. **Use EXACT prop names and types** from the component documentation (check type_signature field)
|
|
716
|
-
6. **Map HTML attributes to correct component props** based on component documentation
|
|
717
|
-
7. **Handle events properly** if the component emits them (check events array)
|
|
718
|
-
8. **Use slots or content_injection_method** if the component supports them
|
|
719
|
-
9. **Generate functional, copyable code** that works immediately
|
|
720
|
-
10. **Follow framework_type and import_statement** from the component data
|
|
721
|
-
|
|
722
|
-
**CRITICAL REQUIREMENTS:**
|
|
723
|
-
- Transform EVERY element in the original code (approximately ${elementCount} elements)
|
|
724
|
-
- Keep ALL text content exactly as it appears in the original (no changes to labels, placeholders, values, etc.)
|
|
725
|
-
- Maintain ALL CSS classes, inline styles, ids, data attributes, and structure
|
|
726
|
-
- Do NOT summarize, simplify, or create examples - transform the ENTIRE code
|
|
727
|
-
- If you cannot transform an element (component doesn't exist), preserve it as-is with all its attributes and content
|
|
728
|
-
|
|
729
|
-
**VALIDATION:**
|
|
730
|
-
After transformation, you should have approximately the same number of elements as the original (${elementCount}). Every element must be accounted for.
|
|
731
|
-
|
|
732
|
-
## AVAILABLE COMPONENT INFORMATION:
|
|
733
|
-
|
|
734
|
-
${componentContext}
|
|
735
|
-
|
|
736
|
-
## ORIGINAL CODE FROM FIGMA:
|
|
737
|
-
|
|
738
|
-
\`\`\`html
|
|
739
|
-
${originalCode}
|
|
740
|
-
\`\`\`
|
|
741
|
-
|
|
742
|
-
## YOUR TASK:
|
|
743
|
-
|
|
744
|
-
Transform the ENTIRE code above to use design system components. Return ONLY the complete transformed HTML code wrapped in \`\`\`html code blocks. Include ALL ${elementCount} elements and ALL text from the original. No explanations or markdown outside the code block.
|
|
745
|
-
|
|
746
|
-
**Transformed Code (must include all ${elementCount} elements with all original text):**
|
|
747
|
-
\`\`\`html
|
|
748
|
-
`;
|
|
749
|
-
}
|
|
750
|
-
/**
|
|
751
|
-
* Generates code using component documentation from Pinecone
|
|
752
|
-
* @param originalCode - Original code from Figma
|
|
753
|
-
* @param componentContext - Component documentation from Pinecone (JSON format)
|
|
754
|
-
* @returns Frida response with generated code
|
|
755
|
-
*/
|
|
756
|
-
async generateCode(originalCode, componentContext) {
|
|
757
|
-
if (!this.enabled) {
|
|
758
|
-
return {
|
|
759
|
-
success: false,
|
|
760
|
-
error: "Frida AI is not configured. Set FRIDA_BEARER_TOKEN and FRIDA_API_URL in .env",
|
|
761
|
-
response: null
|
|
762
|
-
};
|
|
763
|
-
}
|
|
764
|
-
if (!originalCode || typeof originalCode !== "string") {
|
|
765
|
-
return {
|
|
766
|
-
success: false,
|
|
767
|
-
error: "Original code must be a non-empty string",
|
|
768
|
-
response: null
|
|
769
|
-
};
|
|
770
|
-
}
|
|
771
|
-
console.log(`
|
|
772
|
-
[FRIDA CODE GEN] ========================================`);
|
|
773
|
-
console.log(`[FRIDA CODE GEN] \u{1F680} CALLING FRIDA AI FOR CODE GENERATION`);
|
|
774
|
-
console.log(`[FRIDA CODE GEN] ========================================`);
|
|
775
|
-
const prompt = this.buildCodeGenerationPrompt(originalCode, componentContext);
|
|
776
|
-
const codeGenMaxTokens = process.env.FRIDA_CODE_GEN_MAX_TOKENS ? parseInt(process.env.FRIDA_CODE_GEN_MAX_TOKENS, 10) : Math.max(this.maxTokens, 16384);
|
|
777
|
-
console.log(`[FRIDA CODE GEN] Model: ${this.model}`);
|
|
778
|
-
console.log(`[FRIDA CODE GEN] Original code length: ${originalCode.length} chars`);
|
|
779
|
-
console.log(`[FRIDA CODE GEN] Component context length: ${componentContext.length} chars`);
|
|
780
|
-
console.log(`[FRIDA CODE GEN] Max tokens: ${codeGenMaxTokens} (default: ${this.maxTokens})`);
|
|
781
|
-
try {
|
|
782
|
-
const startTime = Date.now();
|
|
783
|
-
const response = await fetch(this.apiUrl, {
|
|
784
|
-
method: "POST",
|
|
785
|
-
headers: {
|
|
786
|
-
"Content-Type": "application/json",
|
|
787
|
-
Authorization: `Bearer ${this.apiKey}`
|
|
788
|
-
},
|
|
789
|
-
body: JSON.stringify({
|
|
790
|
-
model: this.model,
|
|
791
|
-
input: prompt,
|
|
792
|
-
max_tokens: codeGenMaxTokens,
|
|
793
|
-
stream: false,
|
|
794
|
-
email: "design_system@example.com"
|
|
795
|
-
})
|
|
796
|
-
});
|
|
797
|
-
const fetchTime = Date.now() - startTime;
|
|
798
|
-
console.log(`[FRIDA CODE GEN] API request completed in ${fetchTime}ms - Status: ${response.status}`);
|
|
799
|
-
if (!response.ok) {
|
|
800
|
-
console.error(`[FRIDA CODE GEN] API Error: ${response.status} ${response.statusText}`);
|
|
801
|
-
throw new Error(`API Error: ${response.status} ${response.statusText}`);
|
|
802
|
-
}
|
|
803
|
-
const data = await response.json();
|
|
804
|
-
console.log(`[FRIDA CODE GEN] Response received successfully`);
|
|
805
|
-
const answer = data.response || data.output || data.choices?.[0]?.message?.content || data.content || JSON.stringify(data);
|
|
806
|
-
const tokensUsed = data.usage?.total_tokens || null;
|
|
807
|
-
const answerLength = typeof answer === "string" ? answer.length : String(answer).length;
|
|
808
|
-
console.log(`[FRIDA CODE GEN] ========================================`);
|
|
809
|
-
console.log(`[FRIDA CODE GEN] \u2705 CODE GENERATION COMPLETE`);
|
|
810
|
-
console.log(`[FRIDA CODE GEN] ========================================`);
|
|
811
|
-
console.log(`[FRIDA CODE GEN] \u{1F4CA} TOKENS USED: ${tokensUsed || "N/A"}`);
|
|
812
|
-
console.log(`[FRIDA CODE GEN] \u{1F4DD} Answer length: ${answerLength} chars`);
|
|
813
|
-
console.log(`[FRIDA CODE GEN] \u23F1\uFE0F Total time: ${fetchTime}ms`);
|
|
814
|
-
console.log(`[FRIDA CODE GEN] ========================================
|
|
815
|
-
`);
|
|
816
|
-
return {
|
|
817
|
-
success: true,
|
|
818
|
-
error: null,
|
|
819
|
-
response: typeof answer === "string" ? answer : String(answer),
|
|
820
|
-
metadata: {
|
|
821
|
-
model: this.model,
|
|
822
|
-
tokensUsed
|
|
823
|
-
}
|
|
824
|
-
};
|
|
825
|
-
} catch (error) {
|
|
826
|
-
console.error(`[FRIDA CODE GEN] Error calling Frida AI:`, error instanceof Error ? error.message : String(error));
|
|
827
|
-
return {
|
|
828
|
-
success: false,
|
|
829
|
-
error: error instanceof Error ? error.message : String(error),
|
|
830
|
-
response: null
|
|
831
|
-
};
|
|
832
|
-
}
|
|
833
|
-
}
|
|
834
607
|
/**
|
|
835
608
|
* Asks a question to Frida AI
|
|
836
609
|
* @param question - User question
|
|
@@ -852,16 +625,13 @@ Transform the ENTIRE code above to use design system components. Return ONLY the
|
|
|
852
625
|
response: null
|
|
853
626
|
};
|
|
854
627
|
}
|
|
855
|
-
|
|
856
|
-
[FRIDA] ========================================`);
|
|
857
|
-
console.log(`[FRIDA] \u{1F680} CALLING FRIDA AI`);
|
|
858
|
-
console.log(`[FRIDA] ========================================`);
|
|
628
|
+
logger.debug("CALLING FRIDA AI");
|
|
859
629
|
const prompt = this.buildPrompt(question, context);
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
630
|
+
logger.debug(`API URL: ${this.apiUrl}`);
|
|
631
|
+
logger.debug(`Model: ${this.model}`);
|
|
632
|
+
logger.debug(`Question length: ${question.length} chars`);
|
|
633
|
+
logger.debug(`Context length: ${context.length} chars`);
|
|
634
|
+
logger.debug(`Max tokens: ${this.maxTokens}`);
|
|
865
635
|
try {
|
|
866
636
|
const startTime = Date.now();
|
|
867
637
|
const response = await fetch(this.apiUrl, {
|
|
@@ -875,28 +645,24 @@ Transform the ENTIRE code above to use design system components. Return ONLY the
|
|
|
875
645
|
input: prompt,
|
|
876
646
|
max_tokens: this.maxTokens,
|
|
877
647
|
stream: false,
|
|
878
|
-
email: "
|
|
648
|
+
email: "figma_2frida_mcp@softtek.com"
|
|
879
649
|
})
|
|
880
650
|
});
|
|
881
651
|
const fetchTime = Date.now() - startTime;
|
|
882
|
-
|
|
652
|
+
logger.debug(`API request completed in ${fetchTime}ms - Status: ${response.status}`);
|
|
883
653
|
if (!response.ok) {
|
|
884
|
-
|
|
654
|
+
logger.error(`API Error: ${response.status} ${response.statusText}`);
|
|
885
655
|
throw new Error(`API Error: ${response.status} ${response.statusText}`);
|
|
886
656
|
}
|
|
887
657
|
const data = await response.json();
|
|
888
|
-
|
|
658
|
+
logger.debug("Response received successfully");
|
|
889
659
|
const answer = data.response || data.output || data.choices?.[0]?.message?.content || data.content || JSON.stringify(data);
|
|
890
660
|
const tokensUsed = data.usage?.total_tokens || null;
|
|
891
661
|
const answerLength = typeof answer === "string" ? answer.length : String(answer).length;
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
console.log(`[FRIDA] \u{1F4DD} Answer length: ${answerLength} chars`);
|
|
897
|
-
console.log(`[FRIDA] \u23F1\uFE0F Total time: ${fetchTime}ms`);
|
|
898
|
-
console.log(`[FRIDA] ========================================
|
|
899
|
-
`);
|
|
662
|
+
logger.debug("FRIDA AI RESPONSE RECEIVED");
|
|
663
|
+
logger.debug(`TOKENS USED: ${tokensUsed || "N/A"}`);
|
|
664
|
+
logger.debug(`Answer length: ${answerLength} chars`);
|
|
665
|
+
logger.debug(`Total time: ${fetchTime}ms`);
|
|
900
666
|
return {
|
|
901
667
|
success: true,
|
|
902
668
|
error: null,
|
|
@@ -907,7 +673,7 @@ Transform the ENTIRE code above to use design system components. Return ONLY the
|
|
|
907
673
|
}
|
|
908
674
|
};
|
|
909
675
|
} catch (error) {
|
|
910
|
-
|
|
676
|
+
logger.error("Error calling Frida AI:", error);
|
|
911
677
|
return {
|
|
912
678
|
success: false,
|
|
913
679
|
error: error instanceof Error ? error.message : String(error),
|
|
@@ -931,13 +697,13 @@ var Formatters = class {
|
|
|
931
697
|
minScore: searchResults.searchMetadata.minScore
|
|
932
698
|
},
|
|
933
699
|
components: searchResults.matches.map((match) => ({
|
|
700
|
+
id: match.id,
|
|
934
701
|
tag: match.tag,
|
|
935
702
|
score: match.score,
|
|
703
|
+
name: match.name,
|
|
704
|
+
framework: match.framework,
|
|
936
705
|
category: match.category,
|
|
937
|
-
|
|
938
|
-
keywords: match.keywords || "",
|
|
939
|
-
props: match.props,
|
|
940
|
-
events: match.events
|
|
706
|
+
documentation: match.documentation
|
|
941
707
|
})),
|
|
942
708
|
lowScoreComponents: searchResults.lowScoreMatches || []
|
|
943
709
|
};
|
|
@@ -965,59 +731,16 @@ Suggestion: Try more general terms or verify that components are indexed.`;
|
|
|
965
731
|
searchResults.matches.forEach((match, index) => {
|
|
966
732
|
markdown += `### ${index + 1}. ${match.tag} (Score: ${match.score.toFixed(4)})
|
|
967
733
|
|
|
734
|
+
**Name:** ${match.name || match.tag}
|
|
968
735
|
**Category:** ${match.category}
|
|
736
|
+
**Framework:** ${match.framework}
|
|
969
737
|
|
|
970
|
-
**
|
|
971
|
-
${match.
|
|
972
|
-
|
|
973
|
-
**Keywords:** ${match.keywords || "N/A"}
|
|
974
|
-
|
|
975
|
-
`;
|
|
976
|
-
if (match.props && match.props.length > 0) {
|
|
977
|
-
markdown += `**Properties (${match.props.length}):**
|
|
978
|
-
|
|
979
|
-
`;
|
|
980
|
-
match.props.forEach((prop, idx) => {
|
|
981
|
-
markdown += `${idx + 1}. \`${prop.name}\`
|
|
982
|
-
`;
|
|
983
|
-
markdown += ` - **Type:** \`${prop.type_signature}\`
|
|
984
|
-
`;
|
|
985
|
-
if (prop.default_value !== void 0) {
|
|
986
|
-
markdown += ` - **Default:** \`${prop.default_value}\`
|
|
987
|
-
`;
|
|
988
|
-
}
|
|
989
|
-
if (prop.description) {
|
|
990
|
-
markdown += ` - **Documentation:** ${prop.description}
|
|
991
|
-
`;
|
|
992
|
-
}
|
|
993
|
-
markdown += "\n";
|
|
994
|
-
});
|
|
995
|
-
} else {
|
|
996
|
-
markdown += `**Properties:** None
|
|
997
|
-
|
|
998
|
-
`;
|
|
999
|
-
}
|
|
1000
|
-
if (match.events && match.events.length > 0) {
|
|
1001
|
-
markdown += `**Events (${match.events.length}):**
|
|
738
|
+
**Documentation:**
|
|
739
|
+
${match.documentation || "No documentation available"}
|
|
1002
740
|
|
|
1003
|
-
|
|
1004
|
-
match.events.forEach((event, idx) => {
|
|
1005
|
-
markdown += `${idx + 1}. \`${event.name}\`
|
|
1006
|
-
`;
|
|
1007
|
-
markdown += ` - **Payload type:** ${event.payload_type}
|
|
1008
|
-
`;
|
|
1009
|
-
if (event.description) {
|
|
1010
|
-
markdown += ` - **Documentation:** ${event.description}
|
|
1011
|
-
`;
|
|
1012
|
-
}
|
|
1013
|
-
markdown += "\n";
|
|
1014
|
-
});
|
|
1015
|
-
} else {
|
|
1016
|
-
markdown += `**Events:** None
|
|
741
|
+
---
|
|
1017
742
|
|
|
1018
743
|
`;
|
|
1019
|
-
}
|
|
1020
|
-
markdown += "---\n\n";
|
|
1021
744
|
});
|
|
1022
745
|
if (searchResults.lowScoreMatches && searchResults.lowScoreMatches.length > 0) {
|
|
1023
746
|
markdown += `
|
|
@@ -1031,105 +754,6 @@ ${match.description || "No description available"}
|
|
|
1031
754
|
}
|
|
1032
755
|
return markdown;
|
|
1033
756
|
}
|
|
1034
|
-
/**
|
|
1035
|
-
* Formats an individual component in context for prompts
|
|
1036
|
-
* Returns JSON-structured format for better LLM understanding
|
|
1037
|
-
*/
|
|
1038
|
-
static toContextFormat(match) {
|
|
1039
|
-
const componentJson = {
|
|
1040
|
-
tag: match.tag,
|
|
1041
|
-
readme: match.description,
|
|
1042
|
-
props: match.props.map((p) => ({
|
|
1043
|
-
name: p.name,
|
|
1044
|
-
type: p.type_signature,
|
|
1045
|
-
default_value: p.default_value,
|
|
1046
|
-
docs: p.description || ""
|
|
1047
|
-
})),
|
|
1048
|
-
events: match.events.map((e) => ({
|
|
1049
|
-
name: e.name,
|
|
1050
|
-
payload_type: e.payload_type,
|
|
1051
|
-
docs: e.description || ""
|
|
1052
|
-
})),
|
|
1053
|
-
// Include new fields if available
|
|
1054
|
-
...match.usage_examples && match.usage_examples.length > 0 ? { usage_examples: match.usage_examples } : {},
|
|
1055
|
-
...match.methods && match.methods.length > 0 ? { methods: match.methods } : {},
|
|
1056
|
-
...match.slots && match.slots.length > 0 ? { slots: match.slots } : {}
|
|
1057
|
-
};
|
|
1058
|
-
const jsonStr = JSON.stringify(componentJson, null, 2);
|
|
1059
|
-
return `=== COMPONENT: ${match.tag || "N/A"} ===
|
|
1060
|
-
${match.human_name ? `HUMAN NAME: ${match.human_name}
|
|
1061
|
-
` : ""}CATEGORY: ${match.category || "General"}
|
|
1062
|
-
RELEVANCE SCORE: ${match.score.toFixed(4)}
|
|
1063
|
-
|
|
1064
|
-
DESCRIPTION:
|
|
1065
|
-
${match.description || "No description available"}
|
|
1066
|
-
|
|
1067
|
-
${match.full_readme ? `FULL README:
|
|
1068
|
-
${match.full_readme}
|
|
1069
|
-
|
|
1070
|
-
` : ""}${match.keywords ? `KEYWORDS: ${match.keywords}
|
|
1071
|
-
|
|
1072
|
-
` : ""}PROPERTIES (${match.props?.length || 0}):
|
|
1073
|
-
${match.props && match.props.length > 0 ? match.props.map((p, idx) => {
|
|
1074
|
-
return `${idx + 1}. \`${p.name}\`
|
|
1075
|
-
Type: \`${p.type_signature}\`
|
|
1076
|
-
${p.default_value !== void 0 ? `Default: \`${p.default_value}\`
|
|
1077
|
-
` : ""}Documentation: ${p.description || "No documentation"}`;
|
|
1078
|
-
}).join("\n\n") : "None"}
|
|
1079
|
-
|
|
1080
|
-
EVENTS (${match.events?.length || 0}):
|
|
1081
|
-
${match.events && match.events.length > 0 ? match.events.map((e, idx) => {
|
|
1082
|
-
return `${idx + 1}. ${e.name}
|
|
1083
|
-
Payload type: ${e.payload_type}
|
|
1084
|
-
Documentation: ${e.description || "No documentation"}`;
|
|
1085
|
-
}).join("\n\n") : "None"}
|
|
1086
|
-
|
|
1087
|
-
${match.methods && match.methods.length > 0 ? `METHODS (${match.methods.length}):
|
|
1088
|
-
${match.methods.map((m, idx) => {
|
|
1089
|
-
return `${idx + 1}. ${m.name}(${m.parameters})
|
|
1090
|
-
Documentation: ${m.description || "No documentation"}`;
|
|
1091
|
-
}).join("\n\n")}
|
|
1092
|
-
|
|
1093
|
-
` : ""}${match.usage_examples && match.usage_examples.length > 0 ? `USAGE EXAMPLES:
|
|
1094
|
-
${match.usage_examples.map((ex, idx) => {
|
|
1095
|
-
return `${idx + 1}. ${ex.title}
|
|
1096
|
-
${ex.description}
|
|
1097
|
-
Template: ${ex.code.template}
|
|
1098
|
-
${ex.code.script ? `Script: ${ex.code.script}` : ""}`;
|
|
1099
|
-
}).join("\n\n")}
|
|
1100
|
-
|
|
1101
|
-
` : ""}COMPONENT JSON STRUCTURE (for reference):
|
|
1102
|
-
\`\`\`json
|
|
1103
|
-
${jsonStr}
|
|
1104
|
-
\`\`\`
|
|
1105
|
-
|
|
1106
|
-
---`;
|
|
1107
|
-
}
|
|
1108
|
-
/**
|
|
1109
|
-
* Formats components as JSON array for component documentation
|
|
1110
|
-
* This format worked well with Copilot
|
|
1111
|
-
*/
|
|
1112
|
-
static toJSONContext(matches) {
|
|
1113
|
-
if (!matches || matches.length === 0) {
|
|
1114
|
-
return "No components found.";
|
|
1115
|
-
}
|
|
1116
|
-
const componentsArray = matches.map((match) => ({
|
|
1117
|
-
tag: match.tag,
|
|
1118
|
-
readme: match.description,
|
|
1119
|
-
props: match.props.map((p) => ({
|
|
1120
|
-
name: p.name,
|
|
1121
|
-
type: p.type_signature,
|
|
1122
|
-
default_value: p.default_value,
|
|
1123
|
-
docs: p.description || ""
|
|
1124
|
-
})),
|
|
1125
|
-
events: match.events.map((e) => ({
|
|
1126
|
-
name: e.name,
|
|
1127
|
-
payload_type: e.payload_type,
|
|
1128
|
-
docs: e.description || ""
|
|
1129
|
-
}))
|
|
1130
|
-
}));
|
|
1131
|
-
return JSON.stringify({ components: componentsArray }, null, 2);
|
|
1132
|
-
}
|
|
1133
757
|
/**
|
|
1134
758
|
* Formats multiple components for context in prompts
|
|
1135
759
|
* Uses JSON format for better LLM understanding
|
|
@@ -1139,50 +763,15 @@ ${jsonStr}
|
|
|
1139
763
|
return "No components found.";
|
|
1140
764
|
}
|
|
1141
765
|
const componentsArray = matches.map((match) => ({
|
|
766
|
+
id: match.id,
|
|
1142
767
|
tag: match.tag,
|
|
1143
|
-
|
|
768
|
+
name: match.name,
|
|
769
|
+
framework: match.framework,
|
|
1144
770
|
category: match.category,
|
|
1145
|
-
|
|
1146
|
-
props: match.props.map((p) => ({
|
|
1147
|
-
name: p.name,
|
|
1148
|
-
type: p.type_signature,
|
|
1149
|
-
default_value: p.default_value,
|
|
1150
|
-
docs: p.description || ""
|
|
1151
|
-
})),
|
|
1152
|
-
events: match.events.map((e) => ({
|
|
1153
|
-
name: e.name,
|
|
1154
|
-
payload_type: e.payload_type,
|
|
1155
|
-
docs: e.description || ""
|
|
1156
|
-
})),
|
|
1157
|
-
...match.usage_examples && match.usage_examples.length > 0 ? { usage_examples: match.usage_examples } : {},
|
|
1158
|
-
...match.methods && match.methods.length > 0 ? { methods: match.methods } : {}
|
|
771
|
+
documentation: match.documentation
|
|
1159
772
|
}));
|
|
1160
773
|
return JSON.stringify({ components: componentsArray }, null, 2);
|
|
1161
774
|
}
|
|
1162
|
-
/**
|
|
1163
|
-
* Formats results in pure JSON format
|
|
1164
|
-
*/
|
|
1165
|
-
static toJSON(searchResults) {
|
|
1166
|
-
return JSON.stringify(this.toStructured(searchResults), null, 2);
|
|
1167
|
-
}
|
|
1168
|
-
/**
|
|
1169
|
-
* Formats results according to the requested format
|
|
1170
|
-
*/
|
|
1171
|
-
static format(searchResults, format = "structured") {
|
|
1172
|
-
switch (format.toLowerCase()) {
|
|
1173
|
-
case "json":
|
|
1174
|
-
return this.toJSON(searchResults);
|
|
1175
|
-
case "markdown":
|
|
1176
|
-
case "md":
|
|
1177
|
-
return this.toMarkdown(searchResults);
|
|
1178
|
-
case "structured":
|
|
1179
|
-
return this.toStructured(searchResults);
|
|
1180
|
-
case "context":
|
|
1181
|
-
return this.matchesToContext(searchResults.matches || []);
|
|
1182
|
-
default:
|
|
1183
|
-
return this.toStructured(searchResults);
|
|
1184
|
-
}
|
|
1185
|
-
}
|
|
1186
775
|
};
|
|
1187
776
|
|
|
1188
777
|
// src/figma2frida/utils/design-query-extractor.ts
|
|
@@ -1220,22 +809,6 @@ var DesignQueryExtractor = class {
|
|
|
1220
809
|
}
|
|
1221
810
|
return fallbackQuery;
|
|
1222
811
|
}
|
|
1223
|
-
/**
|
|
1224
|
-
* Extracts multiple candidate queries for experimentation
|
|
1225
|
-
*/
|
|
1226
|
-
extractMultipleQueries(designContext) {
|
|
1227
|
-
const allQueries = [];
|
|
1228
|
-
for (const strategy of this.strategies) {
|
|
1229
|
-
try {
|
|
1230
|
-
const extracted = strategy.extract(designContext);
|
|
1231
|
-
allQueries.push(...extracted);
|
|
1232
|
-
} catch {
|
|
1233
|
-
}
|
|
1234
|
-
}
|
|
1235
|
-
return Array.from(
|
|
1236
|
-
new Set(allQueries.filter((q) => q && q.trim().length > 0))
|
|
1237
|
-
);
|
|
1238
|
-
}
|
|
1239
812
|
};
|
|
1240
813
|
var TextExtractionStrategy = class {
|
|
1241
814
|
name = "text-extraction";
|
|
@@ -1434,8 +1007,8 @@ async function handleComponentSuggestion(options) {
|
|
|
1434
1007
|
const now = Date.now();
|
|
1435
1008
|
const cached = componentSuggestionsCache.get(cacheKey);
|
|
1436
1009
|
if (cached && now - cached.timestamp < COMPONENT_CACHE_TTL_MS) {
|
|
1437
|
-
|
|
1438
|
-
`
|
|
1010
|
+
logger.debug(
|
|
1011
|
+
`Using cached result (age: ${((now - cached.timestamp) / 1e3).toFixed(1)}s)`
|
|
1439
1012
|
);
|
|
1440
1013
|
if (streamContent) {
|
|
1441
1014
|
await streamContent({
|
|
@@ -1467,14 +1040,14 @@ Cannot suggest components without design context.`
|
|
|
1467
1040
|
}
|
|
1468
1041
|
const extractor = getQueryExtractor();
|
|
1469
1042
|
const searchQuery = customQuery || extractor.extractQuery(designContext);
|
|
1470
|
-
|
|
1043
|
+
logger.debug(`Extracted query: "${searchQuery}"`);
|
|
1471
1044
|
if (streamContent) {
|
|
1472
1045
|
await streamContent({
|
|
1473
1046
|
type: "text",
|
|
1474
1047
|
text: `\u{1F50E} Searching Pinecone for: "${searchQuery}"...`
|
|
1475
1048
|
});
|
|
1476
1049
|
}
|
|
1477
|
-
|
|
1050
|
+
logger.debug("Calling Pinecone search service...");
|
|
1478
1051
|
const searchService = getPineconeService(namespace);
|
|
1479
1052
|
const searchStartTime = Date.now();
|
|
1480
1053
|
const searchResults = await searchService.search(searchQuery, {
|
|
@@ -1482,8 +1055,8 @@ Cannot suggest components without design context.`
|
|
|
1482
1055
|
topK
|
|
1483
1056
|
});
|
|
1484
1057
|
const searchTime = Date.now() - searchStartTime;
|
|
1485
|
-
|
|
1486
|
-
`
|
|
1058
|
+
logger.debug(
|
|
1059
|
+
`Pinecone search completed in ${searchTime}ms - Found ${searchResults.relevantMatches} relevant components`
|
|
1487
1060
|
);
|
|
1488
1061
|
if (searchResults.matches.length === 0) {
|
|
1489
1062
|
const namespaceInfo = searchResults.searchMetadata.namespace ? `
|
|
@@ -1507,7 +1080,7 @@ No components from your design system matched the design context with sufficient
|
|
|
1507
1080
|
|
|
1508
1081
|
**Suggestions:**
|
|
1509
1082
|
- Verify that components are indexed in Pinecone${searchResults.searchMetadata.namespace ? ` namespace "${searchResults.searchMetadata.namespace}"` : ""}
|
|
1510
|
-
- Set namespace via \`--
|
|
1083
|
+
- Set namespace via \`--project-id\` CLI argument if not set
|
|
1511
1084
|
- Try lowering the \`minScore\` parameter
|
|
1512
1085
|
- Provide a more specific \`query\` parameter`
|
|
1513
1086
|
}
|
|
@@ -1515,7 +1088,7 @@ No components from your design system matched the design context with sufficient
|
|
|
1515
1088
|
};
|
|
1516
1089
|
}
|
|
1517
1090
|
const componentTags = searchResults.matches.map((m) => m.tag).join(", ");
|
|
1518
|
-
const frameworkType = searchResults.matches[0]?.
|
|
1091
|
+
const frameworkType = searchResults.matches[0]?.framework || "html";
|
|
1519
1092
|
const prefixPatterns = Array.from(
|
|
1520
1093
|
new Set(
|
|
1521
1094
|
searchResults.matches.map((m) => {
|
|
@@ -1566,11 +1139,11 @@ ${componentTags || "No components found"}
|
|
|
1566
1139
|
|
|
1567
1140
|
3. **Use the recommended components:**
|
|
1568
1141
|
- \u2705 Use ONLY the components listed in the search results below
|
|
1569
|
-
- \u2705 Follow the
|
|
1570
|
-
- \u2705 Respect the component's
|
|
1142
|
+
- \u2705 Follow the documentation provided for each component
|
|
1143
|
+
- \u2705 Respect the component's framework and usage as documented
|
|
1571
1144
|
- \u2705 If a component is not in the list below, do NOT use it
|
|
1572
1145
|
|
|
1573
|
-
4. **Framework compatibility:** Check the \`
|
|
1146
|
+
4. **Framework compatibility:** Check the \`framework\` field for each component
|
|
1574
1147
|
|
|
1575
1148
|
---
|
|
1576
1149
|
|
|
@@ -1595,11 +1168,11 @@ ${Formatters.toMarkdown(searchResults)}
|
|
|
1595
1168
|
searchResults.matches
|
|
1596
1169
|
);
|
|
1597
1170
|
const question = `Based on this Figma design context, which component(s) from the design system should I use? Design description: ${searchQuery}`;
|
|
1598
|
-
|
|
1171
|
+
logger.debug(`Calling Frida AI with ${searchResults.matches.length} component matches...`);
|
|
1599
1172
|
const fridaStartTime = Date.now();
|
|
1600
1173
|
const fridaResponse = await frida.ask(question, contextForFrida);
|
|
1601
1174
|
const fridaTime = Date.now() - fridaStartTime;
|
|
1602
|
-
|
|
1175
|
+
logger.debug(`Frida AI completed in ${fridaTime}ms - Success: ${fridaResponse.success}`);
|
|
1603
1176
|
if (fridaResponse.success && fridaResponse.response) {
|
|
1604
1177
|
content.push({
|
|
1605
1178
|
type: "text",
|
|
@@ -1611,66 +1184,6 @@ ${fridaResponse.response}
|
|
|
1611
1184
|
|
|
1612
1185
|
`
|
|
1613
1186
|
});
|
|
1614
|
-
const topComponents = searchResults.matches.slice(0, 3);
|
|
1615
|
-
if (topComponents.length > 0) {
|
|
1616
|
-
let examplesText = `## \u{1F4DD} CODE GENERATION RULES - Recommended Usage
|
|
1617
|
-
|
|
1618
|
-
**MANDATORY: Use ONLY components from your design system. Here are examples:**
|
|
1619
|
-
|
|
1620
|
-
`;
|
|
1621
|
-
topComponents.forEach((comp) => {
|
|
1622
|
-
const componentName = comp.tag;
|
|
1623
|
-
const importStmt = comp.import_statement;
|
|
1624
|
-
const frameworkType2 = comp.framework_type || "Web Component";
|
|
1625
|
-
examplesText += `### ${comp.human_name || componentName}
|
|
1626
|
-
**Tag:** \`${componentName}\`
|
|
1627
|
-
**Framework:** ${frameworkType2}
|
|
1628
|
-
${importStmt ? `**Import:** \`${importStmt}\`` : ""}
|
|
1629
|
-
|
|
1630
|
-
`;
|
|
1631
|
-
if (comp.usage_examples && comp.usage_examples.length > 0) {
|
|
1632
|
-
comp.usage_examples.slice(0, 1).forEach((example) => {
|
|
1633
|
-
examplesText += `\u2705 **${example.title}**
|
|
1634
|
-
${example.description ? `*${example.description}*
|
|
1635
|
-
` : ""}
|
|
1636
|
-
\`\`\`html
|
|
1637
|
-
${example.code.template}
|
|
1638
|
-
\`\`\`
|
|
1639
|
-
${example.code.script ? `\`\`\`javascript
|
|
1640
|
-
${example.code.script}
|
|
1641
|
-
\`\`\`` : ""}
|
|
1642
|
-
|
|
1643
|
-
`;
|
|
1644
|
-
});
|
|
1645
|
-
} else {
|
|
1646
|
-
const propsExample = comp.props.filter((p) => !p.is_utility_prop).slice(0, 2).map((p) => `${p.name}="${p.default_value || "value"}"`).join(" ");
|
|
1647
|
-
examplesText += `\u2705 **Recommended Usage:**
|
|
1648
|
-
\`\`\`html
|
|
1649
|
-
<${componentName}${propsExample ? ` ${propsExample}` : ""}></${componentName}>
|
|
1650
|
-
\`\`\`
|
|
1651
|
-
|
|
1652
|
-
`;
|
|
1653
|
-
}
|
|
1654
|
-
if (comp.props.length > 0) {
|
|
1655
|
-
examplesText += `**Key Props:**
|
|
1656
|
-
${comp.props.slice(0, 3).map((p) => `- \`${p.name}\`: ${p.description || p.type_signature}`).join("\n")}
|
|
1657
|
-
|
|
1658
|
-
`;
|
|
1659
|
-
}
|
|
1660
|
-
examplesText += `---
|
|
1661
|
-
`;
|
|
1662
|
-
});
|
|
1663
|
-
examplesText += `
|
|
1664
|
-
**Remember:** Always use components from your design system as listed above. Follow the import statements and usage examples provided.
|
|
1665
|
-
|
|
1666
|
-
---
|
|
1667
|
-
|
|
1668
|
-
`;
|
|
1669
|
-
content.push({
|
|
1670
|
-
type: "text",
|
|
1671
|
-
text: examplesText
|
|
1672
|
-
});
|
|
1673
|
-
}
|
|
1674
1187
|
if (fridaResponse.metadata?.tokensUsed) {
|
|
1675
1188
|
content.push({
|
|
1676
1189
|
type: "text",
|
|
@@ -1714,10 +1227,10 @@ Showing raw search results instead.
|
|
|
1714
1227
|
data: content,
|
|
1715
1228
|
timestamp: now
|
|
1716
1229
|
});
|
|
1717
|
-
|
|
1230
|
+
logger.debug("Completed successfully");
|
|
1718
1231
|
return { content };
|
|
1719
1232
|
} catch (error) {
|
|
1720
|
-
|
|
1233
|
+
logger.error("Failed to suggest components:", error);
|
|
1721
1234
|
return {
|
|
1722
1235
|
content: [
|
|
1723
1236
|
{
|
|
@@ -1730,13 +1243,7 @@ Showing raw search results instead.
|
|
|
1730
1243
|
1. Is Figma Desktop running?
|
|
1731
1244
|
2. Is Pinecone configured? (Check PINECONE_API_KEY)
|
|
1732
1245
|
3. Are components indexed in Pinecone?
|
|
1733
|
-
4. Check the console for detailed error messages
|
|
1734
|
-
|
|
1735
|
-
${error instanceof Error && error.stack ? `
|
|
1736
|
-
**Stack:**
|
|
1737
|
-
\`\`\`
|
|
1738
|
-
${error.stack}
|
|
1739
|
-
\`\`\`` : ""}`
|
|
1246
|
+
4. Check the console for detailed error messages`
|
|
1740
1247
|
}
|
|
1741
1248
|
]
|
|
1742
1249
|
};
|
|
@@ -1985,61 +1492,127 @@ var ComponentLookupService = class {
|
|
|
1985
1492
|
}
|
|
1986
1493
|
};
|
|
1987
1494
|
|
|
1988
|
-
// src/figma2frida/
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
}
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
1495
|
+
// src/figma2frida/services/auth/token-validator.ts
|
|
1496
|
+
var VALIDATION_URL = "https://us-central1-figma2frida-mcp.cloudfunctions.net/validateFridaApiToken";
|
|
1497
|
+
async function validateFridaApiToken(apiToken) {
|
|
1498
|
+
logger.info("\u{1F510} Validating Frida API token...");
|
|
1499
|
+
try {
|
|
1500
|
+
const response = await fetch(VALIDATION_URL, {
|
|
1501
|
+
method: "POST",
|
|
1502
|
+
headers: {
|
|
1503
|
+
"Content-Type": "application/json"
|
|
1504
|
+
},
|
|
1505
|
+
body: JSON.stringify({
|
|
1506
|
+
data: {
|
|
1507
|
+
llmopsApiKey: apiToken
|
|
1508
|
+
}
|
|
1509
|
+
})
|
|
1510
|
+
});
|
|
1511
|
+
const responseData = await response.json();
|
|
1512
|
+
if (response.ok && "result" in responseData && responseData.result?.valid === true) {
|
|
1513
|
+
logger.info(`\u2705 ${responseData.result.message || "API token is valid"}`);
|
|
1514
|
+
return;
|
|
1515
|
+
}
|
|
1516
|
+
if ("error" in responseData) {
|
|
1517
|
+
const error = responseData.error;
|
|
1518
|
+
const errorMessage = error.message || "Unknown error";
|
|
1519
|
+
const errorStatus = error.status || "UNKNOWN";
|
|
1520
|
+
logger.error(`\u274C Token validation failed: ${errorMessage}`);
|
|
1521
|
+
logger.error(` Status: ${errorStatus}`);
|
|
1522
|
+
if (errorStatus === "RESOURCE_EXHAUSTED") {
|
|
1523
|
+
logger.error(" Rate limit exceeded. Please try again later.");
|
|
1524
|
+
} else if (errorStatus === "NOT_FOUND") {
|
|
1525
|
+
logger.error(" Access config not found. Please contact support.");
|
|
1526
|
+
} else if (errorStatus === "PERMISSION_DENIED") {
|
|
1527
|
+
logger.error(" Invalid API token. Please check your token and try again.");
|
|
1528
|
+
}
|
|
1529
|
+
throw new Error(`Token validation failed: ${errorMessage} (${errorStatus})`);
|
|
1530
|
+
}
|
|
1531
|
+
logger.error(`\u274C Unexpected validation response format: ${JSON.stringify(responseData)}`);
|
|
1532
|
+
throw new Error("Unexpected validation response format");
|
|
1533
|
+
} catch (error) {
|
|
1534
|
+
if (error instanceof Error && error.message.startsWith("Token validation failed:")) {
|
|
1535
|
+
throw error;
|
|
1536
|
+
}
|
|
1537
|
+
logger.error("\u274C Failed to validate API token:", error instanceof Error ? error.message : String(error));
|
|
1538
|
+
if (error instanceof Error) {
|
|
1539
|
+
if (error.message.includes("fetch failed") || error.message.includes("ECONNREFUSED")) {
|
|
1540
|
+
logger.error(" Network error: Could not reach validation service.");
|
|
1541
|
+
logger.error(" Please check your internet connection and try again.");
|
|
1542
|
+
throw new Error("Network error: Could not reach validation service");
|
|
1543
|
+
} else if (error.message.includes("timeout")) {
|
|
1544
|
+
logger.error(" Request timeout: Validation service did not respond in time.");
|
|
1545
|
+
logger.error(" Please try again later.");
|
|
1546
|
+
throw new Error("Request timeout: Validation service did not respond in time");
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
throw new Error(`Token validation failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
2013
1550
|
}
|
|
2014
1551
|
}
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
1552
|
+
|
|
1553
|
+
// src/figma2frida/figma2frida.ts
|
|
1554
|
+
var ENVIRONMENT = "production";
|
|
1555
|
+
process.env.NODE_ENV = ENVIRONMENT;
|
|
1556
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
1557
|
+
var __dirname = dirname(__filename);
|
|
1558
|
+
var require2 = createRequire(import.meta.url);
|
|
1559
|
+
var packageJson = require2(join(__dirname, "../package.json"));
|
|
1560
|
+
var MCP_VERSION = packageJson.version;
|
|
1561
|
+
var SERVER_VERSION = "1.0.2";
|
|
1562
|
+
logger.info(`Figma2Frida MCP Server v${MCP_VERSION} (Server: ${SERVER_VERSION}) [${ENVIRONMENT}]`);
|
|
1563
|
+
logger.info(`Transport: ${process.env.HTTP_STREAM === "true" ? "HTTP Stream" : "stdio"}`);
|
|
1564
|
+
logger.info(`Node.js: ${process.version}`);
|
|
1565
|
+
logger.debug("Loading hardcoded configuration...");
|
|
1566
|
+
process.env.PINECONE_INDEX = "figma2frida";
|
|
1567
|
+
process.env.PINECONE_MIN_SCORE = "0.5";
|
|
1568
|
+
process.env.PINECONE_TOP_K = "10";
|
|
1569
|
+
process.env.FRIDA_PINECONE_PROXY_URL = "https://azf-frida-pinecone-proxy.azurewebsites.net";
|
|
1570
|
+
process.env.FRIDA_API_URL = "https://frida-llm-api.azurewebsites.net/v1/responses";
|
|
1571
|
+
process.env.FRIDA_EMBEDDING_URL = "https://frida-llm-api.azurewebsites.net/v1/embeddings";
|
|
1572
|
+
process.env.FRIDA_EMBEDDING_MODEL = "text-embedding-ada-002";
|
|
1573
|
+
process.env.FRIDA_MODEL = "gpt-5";
|
|
1574
|
+
process.env.FRIDA_MAX_TOKENS = "9000";
|
|
1575
|
+
process.env.HTTP_STREAM = "false";
|
|
1576
|
+
process.env.PORT = "8080";
|
|
1577
|
+
logger.debug(`PINECONE_INDEX: ${process.env.PINECONE_INDEX}`);
|
|
1578
|
+
logger.debug(`PINECONE_MIN_SCORE: ${process.env.PINECONE_MIN_SCORE}`);
|
|
1579
|
+
logger.debug(`PINECONE_TOP_K: ${process.env.PINECONE_TOP_K}`);
|
|
1580
|
+
logger.debug(`FRIDA_PINECONE_PROXY_URL: ${process.env.FRIDA_PINECONE_PROXY_URL}`);
|
|
1581
|
+
logger.debug(`FRIDA_API_URL: ${process.env.FRIDA_API_URL}`);
|
|
1582
|
+
logger.debug(`FRIDA_EMBEDDING_URL: ${process.env.FRIDA_EMBEDDING_URL}`);
|
|
1583
|
+
logger.debug(`FRIDA_EMBEDDING_MODEL: ${process.env.FRIDA_EMBEDDING_MODEL}`);
|
|
1584
|
+
logger.debug(`FRIDA_MODEL: ${process.env.FRIDA_MODEL}`);
|
|
1585
|
+
logger.debug(`FRIDA_MAX_TOKENS: ${process.env.FRIDA_MAX_TOKENS}`);
|
|
1586
|
+
logger.debug(`HTTP_STREAM: ${process.env.HTTP_STREAM}`);
|
|
1587
|
+
logger.debug(`PORT: ${process.env.PORT}`);
|
|
1588
|
+
logger.info("Configuration loaded");
|
|
1589
|
+
var argv = yargs(hideBin(process.argv)).option("project-id", {
|
|
1590
|
+
type: "string",
|
|
1591
|
+
description: "Pinecone namespace (required for component search)",
|
|
1592
|
+
demandOption: true
|
|
1593
|
+
}).option("frida-api-token", {
|
|
1594
|
+
type: "string",
|
|
1595
|
+
description: "Frida API token for authentication",
|
|
1596
|
+
demandOption: true
|
|
1597
|
+
}).parseSync();
|
|
1598
|
+
var PINECONE_NAMESPACE = argv.projectId?.trim();
|
|
1599
|
+
if (!PINECONE_NAMESPACE || PINECONE_NAMESPACE === "" || PINECONE_NAMESPACE === "your_namespace_here") {
|
|
1600
|
+
logger.error("\u274C SHUTDOWN: Invalid or missing --project-id. This argument is required for Pinecone search.");
|
|
1601
|
+
process.exit(1);
|
|
2026
1602
|
}
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
1603
|
+
logger.debug(`PINECONE_NAMESPACE: ${PINECONE_NAMESPACE} (from CLI)`);
|
|
1604
|
+
var FRIDA_TOKEN = argv.fridaApiToken?.trim();
|
|
1605
|
+
if (!FRIDA_TOKEN || FRIDA_TOKEN === "" || FRIDA_TOKEN === "your_api_token_here") {
|
|
1606
|
+
logger.error("\u274C SHUTDOWN: Invalid or missing --frida-api-token. This token is required for authentication.");
|
|
2031
1607
|
process.exit(1);
|
|
2032
1608
|
}
|
|
2033
|
-
|
|
2034
|
-
`);
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
if (PINECONE_NAMESPACE) {
|
|
2041
|
-
console.log(`[CONFIG] \u2713 PINECONE_NAMESPACE: ${PINECONE_NAMESPACE} (from CLI)
|
|
2042
|
-
`);
|
|
1609
|
+
process.env.FRIDA_BEARER_TOKEN = FRIDA_TOKEN;
|
|
1610
|
+
logger.debug(`FRIDA_API_TOKEN: ${FRIDA_TOKEN.substring(0, 8)}... (hidden)`);
|
|
1611
|
+
try {
|
|
1612
|
+
await validateFridaApiToken(FRIDA_TOKEN);
|
|
1613
|
+
} catch (error) {
|
|
1614
|
+
logger.error("\u274C SHUTDOWN: Authentication failed. MCP server cannot start without valid token.");
|
|
1615
|
+
process.exit(1);
|
|
2043
1616
|
}
|
|
2044
1617
|
var HTTP_STREAM = process.env.HTTP_STREAM === "true" || process.env.HTTP_STREAM === "1";
|
|
2045
1618
|
var PORT = parseInt(process.env.PORT || "8080", 10);
|
|
@@ -2048,98 +1621,72 @@ var CACHE_TTL_MS = 6e4;
|
|
|
2048
1621
|
var server = new FastMCP({
|
|
2049
1622
|
instructions: `You are a Figma design assistant optimized for fast code generation using your organization's design system components.
|
|
2050
1623
|
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
-
|
|
2057
|
-
-
|
|
2058
|
-
|
|
2059
|
-
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
- From the component suggestions provided
|
|
2076
|
-
- NEVER use generic component libraries unless they appear in search results
|
|
2077
|
-
- Framework/language agnostic - use whatever framework type is specified in component metadata (Web Components, React, Angular, Vue, etc.)
|
|
1624
|
+
**PRIMARY WORKFLOW (Most Common):**
|
|
1625
|
+
1. User asks for code from Figma design
|
|
1626
|
+
2. Call \`figma_get_design_context\` with:
|
|
1627
|
+
- \`forceCode: true\`
|
|
1628
|
+
- \`transformToDesignSystem: true\` (default)
|
|
1629
|
+
- \`autoSearchComponents: true\` (default)
|
|
1630
|
+
- \`autoImproveLayout: false\` (default - set to true if visual check is needed)
|
|
1631
|
+
3. This tool automatically:
|
|
1632
|
+
- Fetches design from Figma
|
|
1633
|
+
- Searches for matching design system components
|
|
1634
|
+
- Returns code with component documentation
|
|
1635
|
+
- Can include a visual screenshot & layout improvement hints (if requested)
|
|
1636
|
+
4. Use the transformed code and component docs provided to generate final code.
|
|
1637
|
+
|
|
1638
|
+
**ALTERNATIVE WORKFLOWS:**
|
|
1639
|
+
|
|
1640
|
+
**Option A: Component Discovery (before code generation)**
|
|
1641
|
+
- Call \`figma_suggest_components\` to explore available components.
|
|
1642
|
+
- Then use results to inform code generation.
|
|
1643
|
+
|
|
1644
|
+
**Option B: Query-Only Component Search**
|
|
1645
|
+
- Call \`figma_suggest_components\` with the \`query\` parameter (omit \`nodeId\`).
|
|
1646
|
+
- **Figma is OPTIONAL** in this mode - it works even if Figma is closed.
|
|
1647
|
+
- Useful for browsing design system components without a design context.
|
|
2078
1648
|
|
|
2079
1649
|
**Available tools:**
|
|
2080
|
-
-
|
|
2081
|
-
-
|
|
2082
|
-
-
|
|
1650
|
+
- \`figma_get_design_context\`: Main tool for code generation (use this for primary workflow).
|
|
1651
|
+
- \`figma_suggest_components\`: Component discovery/exploration. Works without Figma if \`query\` is provided.
|
|
1652
|
+
- \`figma_get_screenshot\`: Quick visual reference (fast, ~0.5-1s).
|
|
1653
|
+
- \`figma_improve_layout\`: Layout refinement (call manually only if you need a fresh screenshot without regenerating code).
|
|
2083
1654
|
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
- Transformed code preserving Figma design structure
|
|
2091
|
-
- Component metadata including framework type, import statements, and usage examples
|
|
2092
|
-
4. **USE THE TRANSFORMED CODE AS PROVIDED** - Do not modify the structure or layout
|
|
2093
|
-
5. Show the element analysis and mapping clearly to the user
|
|
2094
|
-
6. Present the transformed code maintaining all Figma design elements
|
|
2095
|
-
7. **Use the component's framework type and import statements** as specified in the component metadata
|
|
2096
|
-
|
|
2097
|
-
**IMPORTANT:** The transformed code from Figma is the source of truth. Do not create new layouts or modify the structure. Use only the design system components as mapped in the transformation. The framework/language depends on what's configured in your component library.
|
|
2098
|
-
|
|
2099
|
-
Connection is established on startup. Always use design system components from the configured library.`,
|
|
1655
|
+
**\u{1F6A8} CRITICAL: Follow Figma Design Exactly**
|
|
1656
|
+
When generating code, you MUST:
|
|
1657
|
+
1. **USE THE TRANSFORMED CODE AS YOUR BASE** - It preserves the EXACT structure and layout from Figma.
|
|
1658
|
+
2. **USE ONLY DESIGN SYSTEM COMPONENTS** - Use only components found in search results or suggestions.
|
|
1659
|
+
3. **PRESERVE DESIGN STRUCTURE** - Maintain all container divs, spacing, and positioning.
|
|
1660
|
+
4. **SHOW ELEMENT ANALYSIS** - Display mapping of HTML elements to design system components.`,
|
|
2100
1661
|
name: "figma-proxy",
|
|
2101
1662
|
version: "1.0.0"
|
|
2102
1663
|
});
|
|
2103
|
-
|
|
2104
|
-
console.log("[FIGMA2FRIDA] Initializing Figma Client...");
|
|
1664
|
+
logger.debug("Initializing Figma Client...");
|
|
2105
1665
|
var figmaClient = new FigmaClient("http://127.0.0.1:3845/sse");
|
|
2106
|
-
|
|
2107
|
-
console.log("[FIGMA2FRIDA] ========================================\n");
|
|
1666
|
+
logger.debug("Figma Client object created");
|
|
2108
1667
|
var figmaConnected = false;
|
|
2109
|
-
|
|
2110
|
-
console.log("[FIGMA2FRIDA] This is a non-blocking async operation - server will continue starting\n");
|
|
1668
|
+
logger.debug("Starting initial connection attempt to Figma MCP (non-blocking)...");
|
|
2111
1669
|
var connectionStartTime = Date.now();
|
|
2112
1670
|
figmaClient.connect().then(() => {
|
|
2113
1671
|
const connectionTime = Date.now() - connectionStartTime;
|
|
2114
1672
|
figmaConnected = true;
|
|
2115
|
-
|
|
2116
|
-
console.log("[FIGMA2FRIDA] \u2705 STARTUP CONNECTION SUCCESS!");
|
|
2117
|
-
console.log(`[FIGMA2FRIDA] Connection time: ${connectionTime}ms`);
|
|
2118
|
-
console.log(`[FIGMA2FRIDA] Status: figmaConnected = ${figmaConnected}`);
|
|
2119
|
-
console.log("[FIGMA2FRIDA] ========================================\n");
|
|
1673
|
+
logger.debug(`Connected to Figma MCP (${connectionTime}ms)`);
|
|
2120
1674
|
}).catch((error) => {
|
|
2121
1675
|
const connectionTime = Date.now() - connectionStartTime;
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
console.log(`[FIGMA2FRIDA] Status: figmaConnected = ${figmaConnected}`);
|
|
2126
|
-
console.log("[FIGMA2FRIDA] \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
2127
|
-
console.error("[FIGMA2FRIDA] Error type:", error?.constructor?.name || typeof error);
|
|
2128
|
-
console.error("[FIGMA2FRIDA] Error message:", error instanceof Error ? error.message : String(error));
|
|
1676
|
+
logger.warn(`Failed to connect to Figma MCP (${connectionTime}ms)`);
|
|
1677
|
+
logger.debug("Error type:", error?.constructor?.name || typeof error);
|
|
1678
|
+
logger.debug("Error message:", error instanceof Error ? error.message : String(error));
|
|
2129
1679
|
if (error && typeof error === "object") {
|
|
2130
1680
|
const errorObj = error;
|
|
2131
1681
|
if ("code" in errorObj) {
|
|
2132
|
-
|
|
1682
|
+
logger.debug("Error code:", errorObj.code);
|
|
2133
1683
|
}
|
|
2134
1684
|
}
|
|
2135
1685
|
if (error instanceof Error && error.stack) {
|
|
2136
|
-
|
|
2137
|
-
console.error(error.stack);
|
|
1686
|
+
logger.debug("Error stack:", error.stack);
|
|
2138
1687
|
}
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
console.log("[FIGMA2FRIDA] Note: Tools will attempt to reconnect when called");
|
|
2142
|
-
console.log("[FIGMA2FRIDA] ========================================\n");
|
|
1688
|
+
logger.warn("Make sure Figma Desktop is running with MCP enabled at http://127.0.0.1:3845/sse");
|
|
1689
|
+
logger.debug("Tools will attempt to reconnect when called");
|
|
2143
1690
|
});
|
|
2144
1691
|
server.addTool({
|
|
2145
1692
|
name: "figma_get_screenshot",
|
|
@@ -2170,10 +1717,10 @@ server.addTool({
|
|
|
2170
1717
|
}),
|
|
2171
1718
|
execute: async (args, _context) => {
|
|
2172
1719
|
try {
|
|
2173
|
-
|
|
2174
|
-
|
|
1720
|
+
logger.debug(`Tool called with nodeId: ${args.nodeId || "current selection"}`);
|
|
1721
|
+
logger.debug(`figmaConnected flag: ${figmaConnected}`);
|
|
2175
1722
|
if (!figmaConnected) {
|
|
2176
|
-
|
|
1723
|
+
logger.warn("Early return: figmaConnected is false");
|
|
2177
1724
|
const result = {
|
|
2178
1725
|
content: [{
|
|
2179
1726
|
type: "text",
|
|
@@ -2182,7 +1729,7 @@ server.addTool({
|
|
|
2182
1729
|
};
|
|
2183
1730
|
return result;
|
|
2184
1731
|
}
|
|
2185
|
-
|
|
1732
|
+
logger.debug("Calling figmaClient.getScreenshot()...");
|
|
2186
1733
|
const screenshot = await figmaClient.getScreenshot(args.nodeId);
|
|
2187
1734
|
if (!screenshot.content || screenshot.content.length === 0) {
|
|
2188
1735
|
const result = {
|
|
@@ -2192,6 +1739,7 @@ server.addTool({
|
|
|
2192
1739
|
}
|
|
2193
1740
|
return { content: screenshot.content };
|
|
2194
1741
|
} catch (error) {
|
|
1742
|
+
logger.error("Failed to get screenshot:", error instanceof Error ? error.message : String(error));
|
|
2195
1743
|
const result = {
|
|
2196
1744
|
content: [{
|
|
2197
1745
|
type: "text",
|
|
@@ -2204,12 +1752,18 @@ server.addTool({
|
|
|
2204
1752
|
});
|
|
2205
1753
|
server.addTool({
|
|
2206
1754
|
name: "figma_get_design_context",
|
|
2207
|
-
description: `
|
|
1755
|
+
description: `The ALL-IN-ONE tool for generating code from Figma Desktop.
|
|
1756
|
+
|
|
1757
|
+
**Primary Workflow:**
|
|
1758
|
+
- Call this tool when the user wants to generate code from a Figma design.
|
|
1759
|
+
- It returns design data, generated code, component suggestions, and visual references in a single turn.
|
|
2208
1760
|
|
|
2209
1761
|
**What this does:**
|
|
2210
1762
|
- Calls Figma's get_design_context tool
|
|
2211
1763
|
- Returns design data and optionally generated code (React, HTML, etc.)
|
|
2212
|
-
- Automatically
|
|
1764
|
+
- **Discovery (Semantic)**: Automatically analyzes design to suggest components (Pinecone search)
|
|
1765
|
+
- **Validation (Exact)**: If code is generated, verifies that used components exist in the library
|
|
1766
|
+
- Automatically includes a screenshot for layout refinement (if requested)
|
|
2213
1767
|
- Results are cached for 60 seconds for instant subsequent access
|
|
2214
1768
|
- Streams progress updates for better responsiveness
|
|
2215
1769
|
|
|
@@ -2266,19 +1820,18 @@ server.addTool({
|
|
|
2266
1820
|
nodeId: z.string().optional().describe('Figma node ID (e.g., "315-2920" or "315:2920"). Leave empty for current selection.'),
|
|
2267
1821
|
forceCode: z.boolean().optional().describe("Force code generation even for large outputs (default: false). Set to true only when you need the actual React/HTML code."),
|
|
2268
1822
|
autoSearchComponents: z.boolean().optional().describe("Automatically search for design system components in the generated code (default: true). Only works when forceCode is true."),
|
|
2269
|
-
transformToDesignSystem: z.boolean().optional().describe("Transform Figma-generated code to use design system components (default: true). Only works when forceCode is true. Replaces generic HTML elements (button, input, etc.) with components from your design system library. Framework and language are determined by component metadata.")
|
|
1823
|
+
transformToDesignSystem: z.boolean().optional().describe("Transform Figma-generated code to use design system components (default: true). Only works when forceCode is true. Replaces generic HTML elements (button, input, etc.) with components from your design system library. Framework and language are determined by component metadata."),
|
|
1824
|
+
autoImproveLayout: z.boolean().optional().describe("Automatically include a screenshot and layout improvement reference (default: false). Set to true for visual verification.")
|
|
2270
1825
|
}),
|
|
2271
1826
|
execute: async (args, context) => {
|
|
2272
1827
|
try {
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
console.log(`[FIGMA_GET_DESIGN_CONTEXT] figmaConnected flag: ${figmaConnected}`);
|
|
2279
|
-
console.log(`[FIGMA_GET_DESIGN_CONTEXT] ========================================`);
|
|
1828
|
+
logger.debug("Tool called");
|
|
1829
|
+
logger.debug(`nodeId: ${args.nodeId || "current selection"}`);
|
|
1830
|
+
logger.debug(`forceCode: ${args.forceCode ?? false}`);
|
|
1831
|
+
logger.debug(`autoImproveLayout: ${args.autoImproveLayout ?? false}`);
|
|
1832
|
+
logger.debug(`figmaConnected flag: ${figmaConnected}`);
|
|
2280
1833
|
if (!figmaConnected) {
|
|
2281
|
-
|
|
1834
|
+
logger.warn("Early return: figmaConnected is false");
|
|
2282
1835
|
return {
|
|
2283
1836
|
content: [
|
|
2284
1837
|
{
|
|
@@ -2288,22 +1841,22 @@ server.addTool({
|
|
|
2288
1841
|
]
|
|
2289
1842
|
};
|
|
2290
1843
|
}
|
|
2291
|
-
|
|
2292
|
-
console.log(`
|
|
2293
|
-
[DESIGN CONTEXT] Starting...`);
|
|
1844
|
+
logger.debug("Calling figmaClient.getDesignContext()...");
|
|
2294
1845
|
const nodeId = args.nodeId;
|
|
2295
1846
|
const forceCode = args.forceCode ?? false;
|
|
2296
1847
|
const autoSearchComponents = args.autoSearchComponents ?? true;
|
|
2297
1848
|
const transformToDesignSystem = args.transformToDesignSystem ?? true;
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
1849
|
+
const autoImproveLayout = args.autoImproveLayout ?? false;
|
|
1850
|
+
logger.debug(`NodeId: ${nodeId || "current selection"}`);
|
|
1851
|
+
logger.debug(`ForceCode: ${forceCode}`);
|
|
1852
|
+
logger.debug(`AutoSearchComponents: ${autoSearchComponents}`);
|
|
1853
|
+
logger.debug(`TransformToDesignSystem: ${transformToDesignSystem}`);
|
|
1854
|
+
logger.debug(`autoImproveLayout: ${autoImproveLayout}`);
|
|
1855
|
+
const cacheKey = `${nodeId || "current"}:${forceCode}:${autoSearchComponents}:${transformToDesignSystem}:${autoImproveLayout}`;
|
|
2303
1856
|
const now = Date.now();
|
|
2304
1857
|
const cached = designContextCache.get(cacheKey);
|
|
2305
1858
|
if (cached && now - cached.timestamp < CACHE_TTL_MS) {
|
|
2306
|
-
|
|
1859
|
+
logger.debug(`Using cached result (age: ${(now - cached.timestamp).toFixed(0)}ms)`);
|
|
2307
1860
|
await context.streamContent({
|
|
2308
1861
|
type: "text",
|
|
2309
1862
|
text: `\u26A1 Using cached design context (${((now - cached.timestamp) / 1e3).toFixed(1)}s old)`
|
|
@@ -2331,7 +1884,7 @@ server.addTool({
|
|
|
2331
1884
|
type: "text",
|
|
2332
1885
|
text: `\u{1F504} Fetching design from Figma${forceCode ? " (including code generation)" : ""}...`
|
|
2333
1886
|
});
|
|
2334
|
-
|
|
1887
|
+
logger.debug("Calling Figma MCP get_design_context...");
|
|
2335
1888
|
const startTime = performance.now();
|
|
2336
1889
|
const designResult = await figmaClient.getDesignContext(nodeId, {
|
|
2337
1890
|
clientLanguages: "typescript",
|
|
@@ -2344,9 +1897,9 @@ server.addTool({
|
|
|
2344
1897
|
timestamp: now,
|
|
2345
1898
|
forceCode
|
|
2346
1899
|
});
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
1900
|
+
logger.debug(`Cached result for key: ${cacheKey}`);
|
|
1901
|
+
logger.debug(`Response received in ${(endTime - startTime).toFixed(0)}ms`);
|
|
1902
|
+
logger.debug(`Content items: ${designResult.content?.length || 0}`);
|
|
2350
1903
|
if (!designResult.content || designResult.content.length === 0) {
|
|
2351
1904
|
return {
|
|
2352
1905
|
content: [
|
|
@@ -2365,7 +1918,7 @@ server.addTool({
|
|
|
2365
1918
|
};
|
|
2366
1919
|
}
|
|
2367
1920
|
designResult.content.forEach((item, idx) => {
|
|
2368
|
-
|
|
1921
|
+
logger.debug(`Content[${idx}]:`, {
|
|
2369
1922
|
type: item.type,
|
|
2370
1923
|
hasText: item.type === "text" && "text" in item,
|
|
2371
1924
|
textLength: item.type === "text" && "text" in item ? item.text?.length : 0,
|
|
@@ -2387,15 +1940,11 @@ ${forceCode ? "" : "\u{1F4A1} **Tip:** If you need the actual React/HTML code, c
|
|
|
2387
1940
|
|
|
2388
1941
|
`
|
|
2389
1942
|
};
|
|
2390
|
-
|
|
1943
|
+
logger.debug("Design context retrieved successfully");
|
|
2391
1944
|
let transformedCodeSection = null;
|
|
2392
1945
|
let componentSuggestions = [];
|
|
2393
|
-
if (
|
|
1946
|
+
if (transformToDesignSystem && designResult.content) {
|
|
2394
1947
|
try {
|
|
2395
|
-
await context.streamContent({
|
|
2396
|
-
type: "text",
|
|
2397
|
-
text: "\u{1F504} Transforming code to use design system components..."
|
|
2398
|
-
});
|
|
2399
1948
|
if (autoSearchComponents) {
|
|
2400
1949
|
await context.streamContent({
|
|
2401
1950
|
type: "text",
|
|
@@ -2404,7 +1953,7 @@ ${forceCode ? "" : "\u{1F4A1} **Tip:** If you need the actual React/HTML code, c
|
|
|
2404
1953
|
try {
|
|
2405
1954
|
const queryExtractor2 = new DesignQueryExtractor();
|
|
2406
1955
|
const searchQuery = queryExtractor2.extractQuery(designResult);
|
|
2407
|
-
|
|
1956
|
+
logger.debug(`Extracted query: "${searchQuery}"`);
|
|
2408
1957
|
await context.streamContent({
|
|
2409
1958
|
type: "text",
|
|
2410
1959
|
text: `\u{1F50E} Searching Pinecone for: "${searchQuery}"...`
|
|
@@ -2417,8 +1966,8 @@ ${forceCode ? "" : "\u{1F4A1} **Tip:** If you need the actual React/HTML code, c
|
|
|
2417
1966
|
});
|
|
2418
1967
|
componentSuggestions = searchResults.matches;
|
|
2419
1968
|
const componentNames = componentSuggestions.map((comp) => comp.tag).join(", ");
|
|
2420
|
-
|
|
2421
|
-
`
|
|
1969
|
+
logger.debug(
|
|
1970
|
+
`Found ${componentSuggestions.length} components from Pinecone: ${componentNames}`
|
|
2422
1971
|
);
|
|
2423
1972
|
if (componentSuggestions.length > 0) {
|
|
2424
1973
|
await context.streamContent({
|
|
@@ -2433,8 +1982,8 @@ ${componentSuggestions.map((c) => ` - ${c.tag} (${c.category})`).join("\n")}`
|
|
|
2433
1982
|
});
|
|
2434
1983
|
}
|
|
2435
1984
|
} catch (error) {
|
|
2436
|
-
|
|
2437
|
-
|
|
1985
|
+
logger.warn(
|
|
1986
|
+
"Could not get component suggestions:",
|
|
2438
1987
|
error
|
|
2439
1988
|
);
|
|
2440
1989
|
await context.streamContent({
|
|
@@ -2445,10 +1994,6 @@ Falling back to default mappings.`
|
|
|
2445
1994
|
}
|
|
2446
1995
|
}
|
|
2447
1996
|
if (componentSuggestions.length > 0) {
|
|
2448
|
-
await context.streamContent({
|
|
2449
|
-
type: "text",
|
|
2450
|
-
text: `\u2705 Found ${componentSuggestions.length} design system components from Pinecone. Returning component documentation to agent.`
|
|
2451
|
-
});
|
|
2452
1997
|
const queryExtractor2 = new DesignQueryExtractor();
|
|
2453
1998
|
const searchQuery = queryExtractor2.extractQuery(designResult);
|
|
2454
1999
|
const minScore = parseFloat(process.env.PINECONE_MIN_SCORE);
|
|
@@ -2486,14 +2031,14 @@ Generate code that uses these components with the correct props and structure.
|
|
|
2486
2031
|
|
|
2487
2032
|
`
|
|
2488
2033
|
};
|
|
2489
|
-
} else {
|
|
2034
|
+
} else if (autoSearchComponents) {
|
|
2490
2035
|
await context.streamContent({
|
|
2491
2036
|
type: "text",
|
|
2492
2037
|
text: `\u26A0\uFE0F No components found in Pinecone for this design.`
|
|
2493
2038
|
});
|
|
2494
2039
|
}
|
|
2495
2040
|
} catch (error) {
|
|
2496
|
-
|
|
2041
|
+
logger.error("Code transform error:", error);
|
|
2497
2042
|
await context.streamContent({
|
|
2498
2043
|
type: "text",
|
|
2499
2044
|
text: `\u26A0\uFE0F Error transforming code: ${error instanceof Error ? error.message : String(error)}
|
|
@@ -2532,7 +2077,8 @@ Generate code that uses these components with the correct props and structure.
|
|
|
2532
2077
|
await context.streamContent({
|
|
2533
2078
|
type: "text",
|
|
2534
2079
|
text: `\u2705 **${comp.tag}** - ${comp.category}
|
|
2535
|
-
${comp.
|
|
2080
|
+
${comp.name || comp.tag}
|
|
2081
|
+
${comp.documentation ? comp.documentation.substring(0, 200) + "..." : "No documentation available"}
|
|
2536
2082
|
`
|
|
2537
2083
|
});
|
|
2538
2084
|
} else {
|
|
@@ -2558,8 +2104,8 @@ ${comp.description || "No description available"}
|
|
|
2558
2104
|
summary.foundComponents.forEach((comp) => {
|
|
2559
2105
|
summaryText += `- **${comp.tag}** (${comp.category}) - Score: ${comp.score.toFixed(4)}
|
|
2560
2106
|
`;
|
|
2561
|
-
if (comp.
|
|
2562
|
-
summaryText += ` - ${comp.
|
|
2107
|
+
if (comp.documentation) {
|
|
2108
|
+
summaryText += ` - ${comp.documentation.substring(0, 100)}${comp.documentation.length > 100 ? "..." : ""}
|
|
2563
2109
|
`;
|
|
2564
2110
|
}
|
|
2565
2111
|
});
|
|
@@ -2594,7 +2140,7 @@ ${comp.description || "No description available"}
|
|
|
2594
2140
|
});
|
|
2595
2141
|
}
|
|
2596
2142
|
} catch (error) {
|
|
2597
|
-
|
|
2143
|
+
logger.error("Component search error:", error);
|
|
2598
2144
|
await context.streamContent({
|
|
2599
2145
|
type: "text",
|
|
2600
2146
|
text: `\u26A0\uFE0F Error searching for components: ${error instanceof Error ? error.message : String(error)}
|
|
@@ -2610,11 +2156,48 @@ ${comp.description || "No description available"}
|
|
|
2610
2156
|
if (componentSection) {
|
|
2611
2157
|
finalContent.push(componentSection);
|
|
2612
2158
|
}
|
|
2159
|
+
if (autoImproveLayout) {
|
|
2160
|
+
try {
|
|
2161
|
+
logger.debug("Fetching integrated screenshot for layout improvement...");
|
|
2162
|
+
await context.streamContent({
|
|
2163
|
+
type: "text",
|
|
2164
|
+
text: "\u{1F4F8} Fetching Figma screenshot for layout refinement..."
|
|
2165
|
+
});
|
|
2166
|
+
const screenshot = await figmaClient.getScreenshot(nodeId);
|
|
2167
|
+
if (screenshot.content && screenshot.content.length > 0) {
|
|
2168
|
+
finalContent.push({
|
|
2169
|
+
type: "text",
|
|
2170
|
+
text: `
|
|
2171
|
+
## \u{1F4D0} **Layout Improvement Reference**
|
|
2172
|
+
|
|
2173
|
+
Use the screenshot below to verify your generated code matches the Figma design exactly.
|
|
2174
|
+
|
|
2175
|
+
**Key Areas to Verify:**
|
|
2176
|
+
- \u2705 **Element spacing and margins**
|
|
2177
|
+
- \u2705 **Alignment and positioning**
|
|
2178
|
+
- \u2705 **Colors and typography**
|
|
2179
|
+
- \u2705 **Border radius and shadows**
|
|
2180
|
+
|
|
2181
|
+
---
|
|
2182
|
+
|
|
2183
|
+
`
|
|
2184
|
+
});
|
|
2185
|
+
finalContent.push(...screenshot.content);
|
|
2186
|
+
}
|
|
2187
|
+
} catch (error) {
|
|
2188
|
+
logger.error("Screenshot fetch failed:", error);
|
|
2189
|
+
finalContent.push({
|
|
2190
|
+
type: "text",
|
|
2191
|
+
text: `
|
|
2192
|
+
\u26A0\uFE0F **Note:** Automated screenshot for layout improvement failed: ${error instanceof Error ? error.message : String(error)}`
|
|
2193
|
+
});
|
|
2194
|
+
}
|
|
2195
|
+
}
|
|
2613
2196
|
return {
|
|
2614
2197
|
content: finalContent
|
|
2615
2198
|
};
|
|
2616
2199
|
} catch (error) {
|
|
2617
|
-
|
|
2200
|
+
logger.error("Design context error:", error);
|
|
2618
2201
|
return {
|
|
2619
2202
|
content: [
|
|
2620
2203
|
{
|
|
@@ -2640,50 +2223,18 @@ ${error.stack}` : ""}`
|
|
|
2640
2223
|
});
|
|
2641
2224
|
server.addTool({
|
|
2642
2225
|
name: "figma_suggest_components",
|
|
2643
|
-
description:
|
|
2644
|
-
|
|
2645
|
-
Get component suggestions from your organization's design system based on a Figma design. This tool searches the component library configured in your environment and returns specific components that match your design.
|
|
2226
|
+
description: `Search and explore design system components.
|
|
2646
2227
|
|
|
2647
|
-
**
|
|
2648
|
-
-
|
|
2649
|
-
-
|
|
2650
|
-
- Always call this tool BEFORE generating code from Figma designs
|
|
2651
|
-
- Use the recommended components from the response in your code generation
|
|
2228
|
+
**Figma is OPTIONAL:**
|
|
2229
|
+
- If you provide a \`query\` and omit \`nodeId\`, the tool works even if Figma is closed.
|
|
2230
|
+
- If you omit \`query\`, it extracts the context from the current Figma selection.
|
|
2652
2231
|
|
|
2653
|
-
**
|
|
2654
|
-
-
|
|
2655
|
-
-
|
|
2656
|
-
- Searches your component library for relevant components
|
|
2657
|
-
- Processes results with Frida AI for intelligent recommendations
|
|
2658
|
-
- Returns component suggestions with code examples and usage guidelines
|
|
2659
|
-
|
|
2660
|
-
**Performance:**
|
|
2661
|
-
- Typically completes in 3-10 seconds (depending on Frida processing)
|
|
2662
|
-
- Uses cache to avoid redundant Figma calls
|
|
2663
|
-
- Component search is fast (~1-2s)
|
|
2664
|
-
- Frida AI processing adds 2-5s for intelligent suggestions
|
|
2232
|
+
**WHEN TO USE:**
|
|
2233
|
+
- Use this tool for **DISCOVERY** and **EXPLORATION** of the component library.
|
|
2234
|
+
- Use this when you want to know "What components are available for X?" without generating full code.
|
|
2665
2235
|
|
|
2666
|
-
|
|
2667
|
-
-
|
|
2668
|
-
- Optionally provide a custom query to override automatic extraction
|
|
2669
|
-
- Configure minScore and topK to control search results
|
|
2670
|
-
- Set useFrida to false to skip AI processing (faster, less intelligent)
|
|
2671
|
-
|
|
2672
|
-
**Example:**
|
|
2673
|
-
\`\`\`json
|
|
2674
|
-
{
|
|
2675
|
-
"nodeId": "315-2920",
|
|
2676
|
-
"minScore": 0.1,
|
|
2677
|
-
"topK": 5,
|
|
2678
|
-
"useFrida": true
|
|
2679
|
-
}
|
|
2680
|
-
\`\`\`
|
|
2681
|
-
|
|
2682
|
-
**Returns:**
|
|
2683
|
-
- Recommended components from Frida AI (if enabled)
|
|
2684
|
-
- Search results with components from your design system
|
|
2685
|
-
- Component details (props, events, methods, slots, usage examples)
|
|
2686
|
-
- Generated code examples with framework-specific imports`,
|
|
2236
|
+
**\u26A0\uFE0F DO NOT USE FOR CODE GENERATION:**
|
|
2237
|
+
- If your goal is to generate code from a Figma design, use \`figma_get_design_context\` instead. It is faster and integrates component search automatically.`,
|
|
2687
2238
|
annotations: {
|
|
2688
2239
|
title: "Suggest Design System Components",
|
|
2689
2240
|
readOnlyHint: true,
|
|
@@ -2694,62 +2245,64 @@ Get component suggestions from your organization's design system based on a Figm
|
|
|
2694
2245
|
minScore: z.number().optional().describe("Minimum Pinecone relevance score (default: 0.05). Higher values return more relevant results."),
|
|
2695
2246
|
topK: z.number().optional().describe("Maximum number of components to retrieve (default: 10)."),
|
|
2696
2247
|
useFrida: z.boolean().optional().describe("Enable Frida AI processing for intelligent recommendations (default: true)."),
|
|
2697
|
-
query: z.string().optional().describe("Custom search query. If
|
|
2248
|
+
query: z.string().optional().describe("Custom search query. If provided, Figma connection is optional unless nodeId is also provided.")
|
|
2698
2249
|
}),
|
|
2699
2250
|
execute: async (args, context) => {
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
console.log(`[FIGMA_SUGGEST_COMPONENTS] \u26A0\uFE0F Early return: figmaConnected is false`);
|
|
2251
|
+
logger.debug("Tool called");
|
|
2252
|
+
logger.debug(`nodeId: ${args.nodeId || "current selection"}`);
|
|
2253
|
+
logger.debug(`query: ${args.query || "none"}`);
|
|
2254
|
+
logger.debug(`figmaConnected flag: ${figmaConnected}`);
|
|
2255
|
+
const requiresFigma = !args.query || args.nodeId;
|
|
2256
|
+
if (!figmaConnected && requiresFigma) {
|
|
2257
|
+
logger.warn("Early return: figmaConnected is false and Figma is required");
|
|
2708
2258
|
return {
|
|
2709
2259
|
content: [
|
|
2710
2260
|
{
|
|
2711
2261
|
type: "text",
|
|
2712
|
-
text: "\u274C Not connected to Figma MCP. Check logs for connection details."
|
|
2262
|
+
text: args.query ? "\u274C Figma connection required when using nodeId. For query-only mode, omit nodeId." : "\u274C Not connected to Figma MCP. Check logs for connection details."
|
|
2713
2263
|
}
|
|
2714
2264
|
]
|
|
2715
2265
|
};
|
|
2716
2266
|
}
|
|
2717
|
-
|
|
2267
|
+
logger.debug("Proceeding with component suggestion...");
|
|
2718
2268
|
const nodeId = args.nodeId;
|
|
2719
2269
|
const minScore = args.minScore ?? 0.05;
|
|
2720
2270
|
const topK = args.topK ?? 10;
|
|
2721
2271
|
const useFrida = args.useFrida ?? true;
|
|
2722
2272
|
const customQuery = args.query;
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
console.log(`[COMPONENT SUGGEST] TopK:`, topK);
|
|
2728
|
-
console.log(`[COMPONENT SUGGEST] UseFrida:`, useFrida);
|
|
2729
|
-
await context.streamContent({
|
|
2730
|
-
type: "text",
|
|
2731
|
-
text: "\u{1F3A8} Fetching design context from Figma..."
|
|
2732
|
-
});
|
|
2733
|
-
const designCacheKey = `${nodeId || "current"}:false`;
|
|
2734
|
-
const now = Date.now();
|
|
2273
|
+
logger.debug(`NodeId: ${nodeId || "current selection"}`);
|
|
2274
|
+
logger.debug(`MinScore: ${minScore}`);
|
|
2275
|
+
logger.debug(`TopK: ${topK}`);
|
|
2276
|
+
logger.debug(`UseFrida: ${useFrida}`);
|
|
2735
2277
|
let designResult;
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
console.log(`[COMPONENT SUGGEST] Using cached design context`);
|
|
2740
|
-
designResult = cachedDesign.data;
|
|
2278
|
+
if (customQuery && !nodeId) {
|
|
2279
|
+
logger.debug("Query-only mode: skipping Figma fetch");
|
|
2280
|
+
designResult = { content: [] };
|
|
2741
2281
|
} else {
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
forceCode: false
|
|
2746
|
-
// Use metadata mode for faster response
|
|
2747
|
-
});
|
|
2748
|
-
designContextCache.set(designCacheKey, {
|
|
2749
|
-
data: designResult,
|
|
2750
|
-
timestamp: now,
|
|
2751
|
-
forceCode: false
|
|
2282
|
+
await context.streamContent({
|
|
2283
|
+
type: "text",
|
|
2284
|
+
text: "\u{1F3A8} Fetching design context from Figma..."
|
|
2752
2285
|
});
|
|
2286
|
+
const designCacheKey = `${nodeId || "current"}:false`;
|
|
2287
|
+
const now = Date.now();
|
|
2288
|
+
const cachedDesign = designContextCache.get(designCacheKey);
|
|
2289
|
+
const designCacheAge = cachedDesign ? now - cachedDesign.timestamp : CACHE_TTL_MS + 1;
|
|
2290
|
+
if (cachedDesign && designCacheAge < CACHE_TTL_MS) {
|
|
2291
|
+
logger.debug("Using cached design context");
|
|
2292
|
+
designResult = cachedDesign.data;
|
|
2293
|
+
} else {
|
|
2294
|
+
designResult = await figmaClient.getDesignContext(nodeId, {
|
|
2295
|
+
clientLanguages: "typescript",
|
|
2296
|
+
clientFrameworks: "react",
|
|
2297
|
+
forceCode: false
|
|
2298
|
+
// Use metadata mode for faster response
|
|
2299
|
+
});
|
|
2300
|
+
designContextCache.set(designCacheKey, {
|
|
2301
|
+
data: designResult,
|
|
2302
|
+
timestamp: now,
|
|
2303
|
+
forceCode: false
|
|
2304
|
+
});
|
|
2305
|
+
}
|
|
2753
2306
|
}
|
|
2754
2307
|
return handleComponentSuggestion({
|
|
2755
2308
|
nodeId,
|
|
@@ -2759,7 +2312,6 @@ Get component suggestions from your organization's design system based on a Figm
|
|
|
2759
2312
|
query: customQuery,
|
|
2760
2313
|
namespace: PINECONE_NAMESPACE,
|
|
2761
2314
|
designContext: designResult,
|
|
2762
|
-
designContextCache,
|
|
2763
2315
|
getDesignContext: figmaClient.getDesignContext.bind(figmaClient),
|
|
2764
2316
|
streamContent: async (content) => {
|
|
2765
2317
|
if (Array.isArray(content)) {
|
|
@@ -2775,36 +2327,120 @@ Get component suggestions from your organization's design system based on a Figm
|
|
|
2775
2327
|
});
|
|
2776
2328
|
server.addTool({
|
|
2777
2329
|
name: "figma_improve_layout",
|
|
2778
|
-
description: `
|
|
2330
|
+
description: `Improve the generated layout to visually match the Figma frame reference.
|
|
2331
|
+
|
|
2332
|
+
**WHEN TO USE:**
|
|
2333
|
+
- Use this tool when the user asks to fix spacing, alignment, or visual issues AFTER code generation.
|
|
2334
|
+
- It provides a fresh screenshot and specific layout improvement instructions.
|
|
2335
|
+
- This is a refinement tool, typically used in a loop to polish the UI.
|
|
2779
2336
|
|
|
2780
2337
|
**What this does:**
|
|
2781
|
-
-
|
|
2782
|
-
-
|
|
2783
|
-
-
|
|
2338
|
+
- Fetches the Figma screenshot (visual reference) for the specified node
|
|
2339
|
+
- Provides the screenshot as a visual reference for layout improvement
|
|
2340
|
+
- Use this tool after generating code from Figma to refine the layout
|
|
2341
|
+
- Compare your generated code output with the screenshot to adjust spacing, positioning, and styling
|
|
2784
2342
|
|
|
2785
2343
|
**Usage:**
|
|
2786
2344
|
- Call this tool after generating code from Figma
|
|
2787
|
-
-
|
|
2345
|
+
- Provide a nodeId (e.g., "315-2920") or leave empty for current selection
|
|
2346
|
+
- The tool will fetch the screenshot and provide it as a visual reference
|
|
2788
2347
|
|
|
2789
2348
|
**Example:**
|
|
2790
2349
|
\`\`\`json
|
|
2791
|
-
{
|
|
2350
|
+
{
|
|
2351
|
+
"nodeId": "315-2920"
|
|
2352
|
+
}
|
|
2792
2353
|
\`\`\``,
|
|
2793
2354
|
annotations: {
|
|
2794
2355
|
title: "Improve Layout to Match Figma",
|
|
2795
2356
|
readOnlyHint: false,
|
|
2796
2357
|
openWorldHint: false
|
|
2797
2358
|
},
|
|
2798
|
-
parameters: z.object({
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2359
|
+
parameters: z.object({
|
|
2360
|
+
nodeId: z.string().optional().describe('Figma node ID (e.g., "315-2920" or "315:2920"). Leave empty for current selection.')
|
|
2361
|
+
}),
|
|
2362
|
+
execute: async (args, _context) => {
|
|
2363
|
+
try {
|
|
2364
|
+
logger.debug(`Tool called with nodeId: ${args.nodeId || "current selection"}`);
|
|
2365
|
+
logger.debug(`figmaConnected flag: ${figmaConnected}`);
|
|
2366
|
+
if (!figmaConnected) {
|
|
2367
|
+
logger.warn("Early return: figmaConnected is false");
|
|
2368
|
+
return {
|
|
2369
|
+
content: [
|
|
2370
|
+
{
|
|
2371
|
+
type: "text",
|
|
2372
|
+
text: "\u274C Not connected to Figma MCP. Check logs for connection details."
|
|
2373
|
+
}
|
|
2374
|
+
]
|
|
2375
|
+
};
|
|
2376
|
+
}
|
|
2377
|
+
logger.debug("Calling figmaClient.getScreenshot()...");
|
|
2378
|
+
const screenshot = await figmaClient.getScreenshot(args.nodeId);
|
|
2379
|
+
if (!screenshot.content || screenshot.content.length === 0) {
|
|
2380
|
+
return {
|
|
2381
|
+
content: [
|
|
2382
|
+
{
|
|
2383
|
+
type: "text",
|
|
2384
|
+
text: "\u274C No screenshot returned from Figma MCP."
|
|
2385
|
+
}
|
|
2386
|
+
]
|
|
2387
|
+
};
|
|
2388
|
+
}
|
|
2389
|
+
return {
|
|
2390
|
+
content: [
|
|
2391
|
+
{
|
|
2392
|
+
type: "text",
|
|
2393
|
+
text: `\u2705 **Visual Reference from Figma**
|
|
2394
|
+
|
|
2395
|
+
**Node ID:** ${args.nodeId || "current selection"}
|
|
2396
|
+
|
|
2397
|
+
**Instructions for Layout Improvement:**
|
|
2398
|
+
|
|
2399
|
+
Compare the screenshot below with your generated code output. Adjust the following to match the Figma design exactly:
|
|
2400
|
+
|
|
2401
|
+
**Key Areas to Verify:**
|
|
2402
|
+
- \u2705 **Element spacing and margins** - Ensure spacing matches the design
|
|
2403
|
+
- \u2705 **Alignment and positioning** - Check vertical and horizontal alignment
|
|
2404
|
+
- \u2705 **Colors and typography** - Match colors, font sizes, and font weights
|
|
2405
|
+
- \u2705 **Border radius and shadows** - Verify rounded corners and shadow effects
|
|
2406
|
+
- \u2705 **Responsive behavior** - Ensure layout works at different screen sizes
|
|
2407
|
+
- \u2705 **Component sizing** - Check width, height, and aspect ratios
|
|
2408
|
+
|
|
2409
|
+
**Visual Reference:**
|
|
2410
|
+
The screenshot below shows the exact Figma design you should match.
|
|
2411
|
+
|
|
2412
|
+
---
|
|
2413
|
+
|
|
2414
|
+
`
|
|
2415
|
+
},
|
|
2416
|
+
...screenshot.content
|
|
2417
|
+
]
|
|
2418
|
+
};
|
|
2419
|
+
} catch (error) {
|
|
2420
|
+
logger.error("Improve layout error:", error);
|
|
2421
|
+
return {
|
|
2422
|
+
content: [
|
|
2423
|
+
{
|
|
2424
|
+
type: "text",
|
|
2425
|
+
text: `\u274C **Failed to Get Screenshot for Layout Improvement**
|
|
2426
|
+
|
|
2427
|
+
**Error:** ${error instanceof Error ? error.message : String(error)}
|
|
2428
|
+
|
|
2429
|
+
**Troubleshooting:**
|
|
2430
|
+
1. Is Figma Desktop running?
|
|
2431
|
+
2. Is the node ID valid?
|
|
2432
|
+
3. Is Figma MCP enabled? (Figma \u2192 Preferences \u2192 Enable MCP)
|
|
2433
|
+
4. Try restarting Figma Desktop
|
|
2434
|
+
|
|
2435
|
+
${error instanceof Error && error.stack ? `
|
|
2436
|
+
**Stack:**
|
|
2437
|
+
\`\`\`
|
|
2438
|
+
${error.stack}
|
|
2439
|
+
\`\`\`` : ""}`
|
|
2440
|
+
}
|
|
2441
|
+
]
|
|
2442
|
+
};
|
|
2443
|
+
}
|
|
2808
2444
|
}
|
|
2809
2445
|
});
|
|
2810
2446
|
if (HTTP_STREAM) {
|
|
@@ -2814,20 +2450,16 @@ if (HTTP_STREAM) {
|
|
|
2814
2450
|
},
|
|
2815
2451
|
transportType: "httpStream"
|
|
2816
2452
|
});
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2453
|
+
logger.info("Figma Proxy MCP Server running on HTTP Stream!");
|
|
2454
|
+
logger.info(`Endpoint: http://localhost:${PORT}/mcp`);
|
|
2455
|
+
logger.debug(`Pinecone Namespace: ${PINECONE_NAMESPACE || "not set (use --project-id)"}`);
|
|
2456
|
+
logger.debug(`
|
|
2822
2457
|
Available Tools:
|
|
2823
2458
|
- figma_get_screenshot: Get visual screenshot (fast)
|
|
2824
2459
|
- figma_get_design_context: Get design data and generated code (with caching & streaming)
|
|
2825
2460
|
- figma_suggest_components: Suggest design system components based on Figma design (with Pinecone & Frida AI)
|
|
2826
2461
|
- figma_improve_layout: Trigger layout improvement to match Figma frame reference
|
|
2827
2462
|
|
|
2828
|
-
Configuration:
|
|
2829
|
-
- Pinecone Namespace: ${PINECONE_NAMESPACE || "not set (use --pinecone-namespace)"}
|
|
2830
|
-
|
|
2831
2463
|
Test with curl:
|
|
2832
2464
|
curl -X POST http://localhost:${PORT}/mcp \\
|
|
2833
2465
|
-H "Content-Type: application/json" \\
|
|
@@ -2835,6 +2467,6 @@ curl -X POST http://localhost:${PORT}/mcp \\
|
|
|
2835
2467
|
`);
|
|
2836
2468
|
} else {
|
|
2837
2469
|
server.start({ transportType: "stdio" });
|
|
2838
|
-
|
|
2470
|
+
logger.info("Started with stdio transport (for MCP clients)");
|
|
2839
2471
|
}
|
|
2840
2472
|
//# sourceMappingURL=figma2frida.js.map
|