@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.
@@ -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
- console.log("[FIGMA CLIENT] Already connected, skipping connection attempt");
69
+ logger.debug("Already connected, skipping connection attempt");
26
70
  return;
27
71
  }
28
- console.log(`[FIGMA CLIENT] ========================================`);
29
- console.log(`[FIGMA CLIENT] \u{1F504} Starting connection attempt`);
30
- console.log(`[FIGMA CLIENT] URL: ${this.figmaUrl}`);
31
- console.log(`[FIGMA CLIENT] Max retries: ${retries}`);
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
- console.log(`[FIGMA CLIENT] \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`);
37
- console.log(`[FIGMA CLIENT] Attempt ${attempt + 1}/${retries} starting...`);
78
+ logger.debug(`Attempt ${attempt + 1}/${retries} starting...`);
38
79
  try {
39
- console.log(`[FIGMA CLIENT] Creating MCP Client object...`);
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
- console.log(`[FIGMA CLIENT] \u2713 Client object created`);
52
- console.log(`[FIGMA CLIENT] Creating SSE Transport for: ${this.figmaUrl}`);
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
- console.log(`[FIGMA CLIENT] \u2713 Transport object created`);
55
- console.log(`[FIGMA CLIENT] Attempting to connect...`);
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
- console.log(`[FIGMA CLIENT] \u2713 Connection established in ${connectTime}ms`);
100
+ logger.debug(`Connection established in ${connectTime}ms`);
60
101
  this.connected = true;
61
102
  const totalTime = Date.now() - attemptStartTime;
62
- console.log(`[FIGMA CLIENT] ========================================`);
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
- console.log(`[FIGMA CLIENT] \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`);
72
- console.log(`[FIGMA CLIENT] \u274C Connection attempt ${attempt + 1}/${retries} FAILED`);
73
- console.log(`[FIGMA CLIENT] Time elapsed: ${attemptTime}ms`);
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
- console.log(`[FIGMA CLIENT] Error code: ${errorObj.code}`);
113
+ logger.debug(`Error code: ${errorObj.code}`);
80
114
  }
81
115
  if ("cause" in errorObj) {
82
- console.log(`[FIGMA CLIENT] Error cause:`, errorObj.cause);
116
+ logger.debug("Error cause:", errorObj.cause);
83
117
  }
84
118
  if ("status" in errorObj) {
85
- console.log(`[FIGMA CLIENT] HTTP status: ${errorObj.status}`);
119
+ logger.debug(`HTTP status: ${errorObj.status}`);
86
120
  }
87
121
  if ("statusText" in errorObj) {
88
- console.log(`[FIGMA CLIENT] HTTP status text: ${errorObj.statusText}`);
122
+ logger.debug(`HTTP status text: ${errorObj.statusText}`);
89
123
  }
90
124
  }
91
125
  if (error instanceof Error && error.stack) {
92
- console.log(`[FIGMA CLIENT] Error stack:`);
93
- console.log(error.stack);
126
+ logger.debug("Error stack:", error.stack);
94
127
  }
95
128
  if (attempt === retries - 1) {
96
- console.log(`[FIGMA CLIENT] ========================================`);
97
- console.log(`[FIGMA CLIENT] \u274C ALL CONNECTION ATTEMPTS FAILED`);
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
- console.log(`[FIGMA CLIENT] Waiting ${waitTime}ms before retry...`);
136
+ logger.debug(`Waiting ${waitTime}ms before retry...`);
107
137
  await new Promise(
108
138
  (resolve) => setTimeout(resolve, waitTime)
109
139
  );
110
- console.log(`[FIGMA CLIENT] Wait complete, retrying...`);
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
- console.log("[FIGMA CLIENT] ensureConnected() called - not connected, attempting connection...");
149
+ logger.debug("ensureConnected() called - not connected, attempting connection...");
120
150
  await this.connect();
121
151
  } else {
122
- console.log("[FIGMA CLIENT] ensureConnected() called - already connected, skipping");
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
- console.log(`[FIGMA CLIENT] Calling get_design_context with args:`, args);
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
- console.log(`[FIGMA CLIENT] get_design_context response type:`, typeof result);
143
- console.log(
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
- console.warn("Error calling get_design_context, attempting reconnect...");
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
- console.log(`[FIGMA CLIENT] Calling get_metadata with args:`, args);
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
- console.log(`[FIGMA CLIENT] get_metadata response type:`, typeof result);
181
- console.log(
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
- console.warn("Error calling get_metadata, attempting reconnect...");
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
- console.warn("Error calling get_screenshot, attempting reconnect...");
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
- console.log("Disconnected from Figma MCP");
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
- pinecone = null;
255
- index = null;
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 = options.fridaEmbeddingUrl || process.env.FRIDA_EMBEDDING_URL || (() => {
267
- throw new Error(
268
- "FRIDA_EMBEDDING_URL is required. Set it in .env or pass it in options."
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
- console.log(`[PINECONE] Calling Frida Embedding API...`);
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: "text-embedding-ada-002",
306
+ model: this.fridaEmbeddingModel,
309
307
  user_id: "pinecone-search",
310
308
  email: "design_system@example.com"
311
309
  })
312
310
  });
313
- console.log(`[PINECONE] API response status: ${response.status}`);
311
+ logger.debug(`API response status: ${response.status}`);
314
312
  if (!response.ok) {
315
313
  const errorText = await response.text();
316
- console.error(`[PINECONE] API Error response:`, errorText);
314
+ logger.error("API Error response:", errorText);
317
315
  throw new Error(`API Error: ${response.status} ${response.statusText}`);
318
316
  }
319
- console.log(`[PINECONE] Parsing API response...`);
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
- console.error(`[PINECONE] Invalid response format:`, JSON.stringify(data).substring(0, 200));
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
- console.log(`[PINECONE] \u2713 Embedding generated (${embedding.length} dims) in ${elapsed}ms`);
325
+ logger.debug(`\u2713 Embedding generated (${embedding.length} dims) in ${elapsed}ms`);
328
326
  return embedding;
329
327
  } catch (error) {
330
- console.error(`[PINECONE] \u2717 Error generating embedding:`, error instanceof Error ? error.message : String(error));
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
- * Supports new schema with properties, events, methods, slots, etc.
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
- tag,
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
- description: metadata.short_description || "",
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
- * Searches for components in Pinecone
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
- console.log(`[PINECONE] Starting search - Query: "${query}", topK: ${topK}, minScore: ${minScore}${this.namespace ? `, namespace: "${this.namespace}"` : ""}`);
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
- console.log(`[PINECONE] Querying Pinecone index "${this.indexName}"${this.namespace ? ` namespace "${this.namespace}"` : ""}...`);
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.index.query({
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
- console.log(`[PINECONE] Query completed in ${queryTime}ms - Found ${queryResponse.matches?.length || 0} matches`);
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
- console.log(`[PINECONE] Filtered results: ${relevantMatches.length} relevant (score >= ${minScore}) out of ${allMatches.length} total`);
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.selector || metadata.id || metadata.tag || "unknown";
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: this.namespace
426
+ namespace
518
427
  }
519
428
  };
520
429
  }
521
430
  /**
522
- * Gets details of a specific component by tag, selector, or id
523
- * @param tag - Component tag, selector, or id (e.g.: 'my-badge', 'Button', 'custom-table')
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.selector === tag || m.id === tag
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
- console.log(`
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
- console.log(`[FRIDA] API URL: ${this.apiUrl}`);
861
- console.log(`[FRIDA] Model: ${this.model}`);
862
- console.log(`[FRIDA] Question length: ${question.length} chars`);
863
- console.log(`[FRIDA] Context length: ${context.length} chars`);
864
- console.log(`[FRIDA] Max tokens: ${this.maxTokens}`);
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: "design_system@example.com"
648
+ email: "figma_2frida_mcp@softtek.com"
879
649
  })
880
650
  });
881
651
  const fetchTime = Date.now() - startTime;
882
- console.log(`[FRIDA] API request completed in ${fetchTime}ms - Status: ${response.status}`);
652
+ logger.debug(`API request completed in ${fetchTime}ms - Status: ${response.status}`);
883
653
  if (!response.ok) {
884
- console.error(`[FRIDA] API Error: ${response.status} ${response.statusText}`);
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
- console.log(`[FRIDA] Response received successfully`);
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
- console.log(`[FRIDA] ========================================`);
893
- console.log(`[FRIDA] \u2705 FRIDA AI RESPONSE RECEIVED`);
894
- console.log(`[FRIDA] ========================================`);
895
- console.log(`[FRIDA] \u{1F4CA} TOKENS USED: ${tokensUsed || "N/A"}`);
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
- console.error(`[FRIDA] Error calling Frida AI:`, error instanceof Error ? error.message : String(error));
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
- description: match.description,
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
- **Description:**
971
- ${match.description || "No description available"}
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
- readme: match.description,
768
+ name: match.name,
769
+ framework: match.framework,
1144
770
  category: match.category,
1145
- keywords: match.keywords,
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
- console.log(
1438
- `[COMPONENT SUGGEST] Using cached result (age: ${((now - cached.timestamp) / 1e3).toFixed(1)}s)`
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
- console.log(`[COMPONENT SUGGEST] Extracted query: "${searchQuery}"`);
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
- console.log(`[COMPONENT SUGGEST] Calling Pinecone search service...`);
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
- console.log(
1486
- `[COMPONENT SUGGEST] Pinecone search completed in ${searchTime}ms - Found ${searchResults.relevantMatches} relevant components`
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 \`--pinecone-namespace\` CLI argument if not set
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]?.framework_type || "Web Components";
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 import statements and usage examples provided
1570
- - \u2705 Respect the component's props, events, and methods as documented
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 \`framework_type\` and \`import_statement\` fields for each component
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
- console.log(`[COMPONENT SUGGEST] Calling Frida AI with ${searchResults.matches.length} component matches...`);
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
- console.log(`[COMPONENT SUGGEST] Frida AI completed in ${fridaTime}ms - Success: ${fridaResponse.success}`);
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
- console.log(`[COMPONENT SUGGEST] Completed successfully`);
1230
+ logger.debug("Completed successfully");
1718
1231
  return { content };
1719
1232
  } catch (error) {
1720
- console.error(`[COMPONENT SUGGEST ERROR]`, error);
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/figma2frida.ts
1989
- console.log("\n[CONFIG] Validating environment variables...\n");
1990
- var missingVars = [];
1991
- if (!process.env.PINECONE_API_KEY) {
1992
- console.error(`[CONFIG] \u274C ERROR: PINECONE_API_KEY is missing from .env file`);
1993
- missingVars.push("PINECONE_API_KEY");
1994
- } else {
1995
- console.log(`[CONFIG] \u2713 PINECONE_API_KEY: ${process.env.PINECONE_API_KEY.substring(0, 8)}... (hidden)`);
1996
- }
1997
- if (!process.env.PINECONE_INDEX) {
1998
- console.error(`[CONFIG] \u274C ERROR: PINECONE_INDEX is missing from .env file`);
1999
- missingVars.push("PINECONE_INDEX");
2000
- } else {
2001
- console.log(`[CONFIG] \u2713 PINECONE_INDEX: ${process.env.PINECONE_INDEX}`);
2002
- }
2003
- if (!process.env.PINECONE_MIN_SCORE) {
2004
- console.error(`[CONFIG] \u274C ERROR: PINECONE_MIN_SCORE is missing from .env file`);
2005
- missingVars.push("PINECONE_MIN_SCORE");
2006
- } else {
2007
- const parsed = parseFloat(process.env.PINECONE_MIN_SCORE);
2008
- if (isNaN(parsed)) {
2009
- console.error(`[CONFIG] \u274C ERROR: PINECONE_MIN_SCORE is not a valid number: "${process.env.PINECONE_MIN_SCORE}"`);
2010
- missingVars.push("PINECONE_MIN_SCORE");
2011
- } else {
2012
- console.log(`[CONFIG] \u2713 PINECONE_MIN_SCORE: ${parsed}`);
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
- if (!process.env.PINECONE_TOP_K) {
2016
- console.error(`[CONFIG] \u274C ERROR: PINECONE_TOP_K is missing from .env file`);
2017
- missingVars.push("PINECONE_TOP_K");
2018
- } else {
2019
- const parsed = parseInt(process.env.PINECONE_TOP_K, 10);
2020
- if (isNaN(parsed)) {
2021
- console.error(`[CONFIG] \u274C ERROR: PINECONE_TOP_K is not a valid integer: "${process.env.PINECONE_TOP_K}"`);
2022
- missingVars.push("PINECONE_TOP_K");
2023
- } else {
2024
- console.log(`[CONFIG] \u2713 PINECONE_TOP_K: ${parsed}`);
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
- if (missingVars.length > 0) {
2028
- console.error(`
2029
- [CONFIG] \u274C Missing required environment variables: ${missingVars.join(", ")}
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
- console.log(`[CONFIG] \u2713 All required Pinecone environment variables are set
2034
- `);
2035
- var argv = yargs(hideBin(process.argv)).option("pinecone-namespace", {
2036
- type: "string",
2037
- description: "Pinecone namespace (passed via options, not from .env)"
2038
- }).parseSync();
2039
- var PINECONE_NAMESPACE = argv.pineconeNamespace || void 0;
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
- **\u{1F6A8} CRITICAL: Follow Figma Design Exactly**
2052
-
2053
- When generating code from Figma designs, you MUST:
2054
-
2055
- 1. **USE THE TRANSFORMED CODE AS YOUR BASE** - The \`figma_get_design_context\` tool with \`transformToDesignSystem: true\` returns code that:
2056
- - Preserves the EXACT structure, layout, and styling from Figma
2057
- - Transforms generic HTML elements to design system components
2058
- - Maintains all divs, containers, classes, and CSS from the original design
2059
- - **DO NOT invent new layouts or structures** - Use the transformed code exactly as provided
2060
-
2061
- 2. **SHOW ELEMENT ANALYSIS** - Before showing code, clearly display:
2062
- - What HTML elements were found in the Figma design
2063
- - Which design system components will be used for each element
2064
- - The mapping: HTML element \u2192 Design system component
2065
- - Any elements that couldn't be transformed (and why)
2066
-
2067
- 3. **PRESERVE DESIGN STRUCTURE** - The transformed code maintains:
2068
- - All container divs and layout structure
2069
- - All CSS classes and inline styles
2070
- - All spacing, positioning, and visual hierarchy
2071
- - The exact visual appearance from Figma
2072
-
2073
- 4. **USE ONLY DESIGN SYSTEM COMPONENTS** - Components must be:
2074
- - From your organization's design system library
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
- - figma_get_screenshot: Get visual screenshot (fast, ~0.5-1s)
2081
- - figma_get_design_context: Get design context and transformed code with design system components (main tool)
2082
- - figma_suggest_components: Suggest design system components based on Figma design
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
- **Workflow for code generation:**
2085
- 1. User asks for code from Figma design
2086
- 2. Call \`figma_get_design_context\` with \`forceCode: true\` and \`transformToDesignSystem: true\`
2087
- 3. The tool will return:
2088
- - Analysis of HTML elements found
2089
- - Mapping to design system components
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
- console.log("\n[FIGMA2FRIDA] ========================================");
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
- console.log("[FIGMA2FRIDA] \u2713 Figma Client object created");
2107
- console.log("[FIGMA2FRIDA] ========================================\n");
1666
+ logger.debug("Figma Client object created");
2108
1667
  var figmaConnected = false;
2109
- console.log("[FIGMA2FRIDA] Starting initial connection attempt to Figma MCP...");
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
- console.log("\n[FIGMA2FRIDA] ========================================");
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
- console.log("\n[FIGMA2FRIDA] ========================================");
2123
- console.log("[FIGMA2FRIDA] \u274C STARTUP CONNECTION FAILED");
2124
- console.log(`[FIGMA2FRIDA] Time elapsed: ${connectionTime}ms`);
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
- console.error("[FIGMA2FRIDA] Error code:", errorObj.code);
1682
+ logger.debug("Error code:", errorObj.code);
2133
1683
  }
2134
1684
  }
2135
1685
  if (error instanceof Error && error.stack) {
2136
- console.error("[FIGMA2FRIDA] Error stack:");
2137
- console.error(error.stack);
1686
+ logger.debug("Error stack:", error.stack);
2138
1687
  }
2139
- 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");
2140
- console.error("[FIGMA2FRIDA] Make sure Figma Desktop is running with MCP enabled at http://127.0.0.1:3845/sse");
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
- console.log(`[FIGMA_GET_SCREENSHOT] Tool called with nodeId: ${args.nodeId || "current selection"}`);
2174
- console.log(`[FIGMA_GET_SCREENSHOT] figmaConnected flag: ${figmaConnected}`);
1720
+ logger.debug(`Tool called with nodeId: ${args.nodeId || "current selection"}`);
1721
+ logger.debug(`figmaConnected flag: ${figmaConnected}`);
2175
1722
  if (!figmaConnected) {
2176
- console.log(`[FIGMA_GET_SCREENSHOT] \u26A0\uFE0F Early return: figmaConnected is false`);
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
- console.log(`[FIGMA_GET_SCREENSHOT] Calling figmaClient.getScreenshot()...`);
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: `Get design context and generated code directly from Figma Desktop MCP.
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 searches for design system components in generated code
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
- console.log(`
2274
- [FIGMA_GET_DESIGN_CONTEXT] ========================================`);
2275
- console.log(`[FIGMA_GET_DESIGN_CONTEXT] Tool called`);
2276
- console.log(`[FIGMA_GET_DESIGN_CONTEXT] nodeId: ${args.nodeId || "current selection"}`);
2277
- console.log(`[FIGMA_GET_DESIGN_CONTEXT] forceCode: ${args.forceCode ?? false}`);
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
- console.log(`[FIGMA_GET_DESIGN_CONTEXT] \u26A0\uFE0F Early return: figmaConnected is false`);
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
- console.log(`[FIGMA_GET_DESIGN_CONTEXT] Calling figmaClient.getDesignContext()...`);
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
- console.log(`[DESIGN CONTEXT] NodeId:`, nodeId || "current selection");
2299
- console.log(`[DESIGN CONTEXT] ForceCode:`, forceCode);
2300
- console.log(`[DESIGN CONTEXT] AutoSearchComponents:`, autoSearchComponents);
2301
- console.log(`[DESIGN CONTEXT] TransformToDesignSystem:`, transformToDesignSystem);
2302
- const cacheKey = `${nodeId || "current"}:${forceCode}`;
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
- console.log(`[DESIGN CONTEXT] Using cached result (age: ${(now - cached.timestamp).toFixed(0)}ms)`);
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
- console.log(`[DESIGN CONTEXT] Calling Figma MCP get_design_context...`);
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
- console.log(`[DESIGN CONTEXT] Cached result for key: ${cacheKey}`);
2348
- console.log(`[DESIGN CONTEXT] Response received in ${(endTime - startTime).toFixed(0)}ms`);
2349
- console.log(`[DESIGN CONTEXT] Content items:`, designResult.content?.length || 0);
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
- console.log(`[DESIGN CONTEXT] Content[${idx}]:`, {
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
- console.log(`\u2705 Design context retrieved successfully`);
1943
+ logger.debug("Design context retrieved successfully");
2391
1944
  let transformedCodeSection = null;
2392
1945
  let componentSuggestions = [];
2393
- if (forceCode && transformToDesignSystem && designResult.content) {
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
- console.log(`[TRANSFORM] Extracted query: "${searchQuery}"`);
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
- console.log(
2421
- `[TRANSFORM] Found ${componentSuggestions.length} components from Pinecone: ${componentNames}`
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
- console.warn(
2437
- `[TRANSFORM] Could not get component suggestions:`,
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
- console.error(`[CODE TRANSFORM ERROR]`, error);
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.description || "No description available"}
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.description) {
2562
- summaryText += ` - ${comp.description.substring(0, 100)}${comp.description.length > 100 ? "..." : ""}
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
- console.error(`[COMPONENT SEARCH ERROR]`, error);
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
- console.error(`[DESIGN CONTEXT ERROR]`, error);
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: `**USE THIS TOOL FIRST** when generating code from Figma designs.
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
- **CRITICAL:**
2648
- - This tool returns ONLY components from your organization's design system
2649
- - NEVER use generic component libraries unless explicitly found in the search results
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
- **What this does:**
2654
- - Fetches design context from Figma (uses cache when available)
2655
- - Extracts semantic query from the design
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
- **Usage:**
2667
- - Provide a nodeId (e.g., "315-2920") or leave empty for current selection
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 not provided, will be extracted automatically from the Figma design context.")
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
- console.log(`
2701
- [FIGMA_SUGGEST_COMPONENTS] ========================================`);
2702
- console.log(`[FIGMA_SUGGEST_COMPONENTS] Tool called`);
2703
- console.log(`[FIGMA_SUGGEST_COMPONENTS] nodeId: ${args.nodeId || "current selection"}`);
2704
- console.log(`[FIGMA_SUGGEST_COMPONENTS] figmaConnected flag: ${figmaConnected}`);
2705
- console.log(`[FIGMA_SUGGEST_COMPONENTS] ========================================`);
2706
- if (!figmaConnected) {
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
- console.log(`[FIGMA_SUGGEST_COMPONENTS] Proceeding with component suggestion...`);
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
- console.log(`
2724
- [COMPONENT SUGGEST] Starting...`);
2725
- console.log(`[COMPONENT SUGGEST] NodeId:`, nodeId || "current selection");
2726
- console.log(`[COMPONENT SUGGEST] MinScore:`, minScore);
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
- const cachedDesign = designContextCache.get(designCacheKey);
2737
- const designCacheAge = cachedDesign ? now - cachedDesign.timestamp : CACHE_TTL_MS + 1;
2738
- if (cachedDesign && designCacheAge < CACHE_TTL_MS) {
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
- designResult = await figmaClient.getDesignContext(nodeId, {
2743
- clientLanguages: "typescript",
2744
- clientFrameworks: "react",
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: `Trigger layout improvement to match the Figma frame reference.
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
- - Returns an instruction to improve the generated layout to match the Figma design
2782
- - Use this tool after code generation to refine the layout
2783
- - The agent will use this instruction to adjust spacing, positioning, and styling
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
- - No parameters required - simply triggers the improvement instruction
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
- execute: async (_args, _context) => {
2800
- return {
2801
- content: [
2802
- {
2803
- type: "text",
2804
- text: "#figma2frida improve the layout to match the figma frame reference"
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
- console.log(`
2818
- \u2705 Figma Proxy MCP Server running on HTTP Stream!
2819
-
2820
- Endpoint: http://localhost:${PORT}/mcp
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
- console.log("Started with stdio transport (for MCP clients like Cursor)");
2470
+ logger.info("Started with stdio transport (for MCP clients)");
2839
2471
  }
2840
2472
  //# sourceMappingURL=figma2frida.js.map