figma-console-mcp 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +328 -0
  3. package/dist/browser/base.d.ts +50 -0
  4. package/dist/browser/base.d.ts.map +1 -0
  5. package/dist/browser/base.js +6 -0
  6. package/dist/browser/base.js.map +1 -0
  7. package/dist/browser/local.d.ts +66 -0
  8. package/dist/browser/local.d.ts.map +1 -0
  9. package/dist/browser/local.js +223 -0
  10. package/dist/browser/local.js.map +1 -0
  11. package/dist/cloudflare/browser/base.js +5 -0
  12. package/dist/cloudflare/browser/cloudflare.js +156 -0
  13. package/dist/cloudflare/browser-manager.js +157 -0
  14. package/dist/cloudflare/core/config.js +161 -0
  15. package/dist/cloudflare/core/console-monitor.js +382 -0
  16. package/dist/cloudflare/core/enrichment/enrichment-service.js +272 -0
  17. package/dist/cloudflare/core/enrichment/index.js +7 -0
  18. package/dist/cloudflare/core/enrichment/relationship-mapper.js +351 -0
  19. package/dist/cloudflare/core/enrichment/style-resolver.js +326 -0
  20. package/dist/cloudflare/core/figma-api.js +273 -0
  21. package/dist/cloudflare/core/figma-desktop-connector.js +383 -0
  22. package/dist/cloudflare/core/figma-style-extractor.js +311 -0
  23. package/dist/cloudflare/core/figma-tools.js +2299 -0
  24. package/dist/cloudflare/core/logger.js +53 -0
  25. package/dist/cloudflare/core/snippet-injector.js +96 -0
  26. package/dist/cloudflare/core/types/enriched.js +5 -0
  27. package/dist/cloudflare/core/types/index.js +4 -0
  28. package/dist/cloudflare/index.js +1059 -0
  29. package/dist/cloudflare/test-browser.js +88 -0
  30. package/dist/config.d.ts +17 -0
  31. package/dist/config.d.ts.map +1 -0
  32. package/dist/config.js +141 -0
  33. package/dist/config.js.map +1 -0
  34. package/dist/core/config.d.ts +17 -0
  35. package/dist/core/config.d.ts.map +1 -0
  36. package/dist/core/config.js +162 -0
  37. package/dist/core/config.js.map +1 -0
  38. package/dist/core/console-monitor.d.ts +81 -0
  39. package/dist/core/console-monitor.d.ts.map +1 -0
  40. package/dist/core/console-monitor.js +383 -0
  41. package/dist/core/console-monitor.js.map +1 -0
  42. package/dist/core/enrichment/enrichment-service.d.ts +52 -0
  43. package/dist/core/enrichment/enrichment-service.d.ts.map +1 -0
  44. package/dist/core/enrichment/enrichment-service.js +273 -0
  45. package/dist/core/enrichment/enrichment-service.js.map +1 -0
  46. package/dist/core/enrichment/index.d.ts +8 -0
  47. package/dist/core/enrichment/index.d.ts.map +1 -0
  48. package/dist/core/enrichment/index.js +8 -0
  49. package/dist/core/enrichment/index.js.map +1 -0
  50. package/dist/core/enrichment/relationship-mapper.d.ts +106 -0
  51. package/dist/core/enrichment/relationship-mapper.d.ts.map +1 -0
  52. package/dist/core/enrichment/relationship-mapper.js +352 -0
  53. package/dist/core/enrichment/relationship-mapper.js.map +1 -0
  54. package/dist/core/enrichment/style-resolver.d.ts +80 -0
  55. package/dist/core/enrichment/style-resolver.d.ts.map +1 -0
  56. package/dist/core/enrichment/style-resolver.js +327 -0
  57. package/dist/core/enrichment/style-resolver.js.map +1 -0
  58. package/dist/core/figma-api.d.ts +137 -0
  59. package/dist/core/figma-api.d.ts.map +1 -0
  60. package/dist/core/figma-api.js +274 -0
  61. package/dist/core/figma-api.js.map +1 -0
  62. package/dist/core/figma-desktop-connector.d.ts +52 -0
  63. package/dist/core/figma-desktop-connector.d.ts.map +1 -0
  64. package/dist/core/figma-desktop-connector.js +384 -0
  65. package/dist/core/figma-desktop-connector.js.map +1 -0
  66. package/dist/core/figma-style-extractor.d.ts +76 -0
  67. package/dist/core/figma-style-extractor.d.ts.map +1 -0
  68. package/dist/core/figma-style-extractor.js +312 -0
  69. package/dist/core/figma-style-extractor.js.map +1 -0
  70. package/dist/core/figma-tools.d.ts +15 -0
  71. package/dist/core/figma-tools.d.ts.map +1 -0
  72. package/dist/core/figma-tools.js +2300 -0
  73. package/dist/core/figma-tools.js.map +1 -0
  74. package/dist/core/logger.d.ts +22 -0
  75. package/dist/core/logger.d.ts.map +1 -0
  76. package/dist/core/logger.js +54 -0
  77. package/dist/core/logger.js.map +1 -0
  78. package/dist/core/snippet-injector.d.ts +24 -0
  79. package/dist/core/snippet-injector.d.ts.map +1 -0
  80. package/dist/core/snippet-injector.js +97 -0
  81. package/dist/core/snippet-injector.js.map +1 -0
  82. package/dist/core/types/enriched.d.ts +213 -0
  83. package/dist/core/types/enriched.d.ts.map +1 -0
  84. package/dist/core/types/enriched.js +6 -0
  85. package/dist/core/types/enriched.js.map +1 -0
  86. package/dist/core/types/index.d.ts +112 -0
  87. package/dist/core/types/index.d.ts.map +1 -0
  88. package/dist/core/types/index.js +5 -0
  89. package/dist/core/types/index.js.map +1 -0
  90. package/dist/index.d.ts +8 -0
  91. package/dist/index.d.ts.map +1 -0
  92. package/dist/index.js +72 -0
  93. package/dist/index.js.map +1 -0
  94. package/dist/local.d.ts +57 -0
  95. package/dist/local.d.ts.map +1 -0
  96. package/dist/local.js +668 -0
  97. package/dist/local.js.map +1 -0
  98. package/dist/logger.d.ts +22 -0
  99. package/dist/logger.d.ts.map +1 -0
  100. package/dist/logger.js +45 -0
  101. package/dist/logger.js.map +1 -0
  102. package/dist/server.d.ts +40 -0
  103. package/dist/server.d.ts.map +1 -0
  104. package/dist/server.js +99 -0
  105. package/dist/server.js.map +1 -0
  106. package/dist/tools/index.d.ts +15 -0
  107. package/dist/tools/index.d.ts.map +1 -0
  108. package/dist/tools/index.js +184 -0
  109. package/dist/tools/index.js.map +1 -0
  110. package/dist/types/index.d.ts +102 -0
  111. package/dist/types/index.d.ts.map +1 -0
  112. package/dist/types/index.js +6 -0
  113. package/dist/types/index.js.map +1 -0
  114. package/figma-desktop-bridge/README.md +232 -0
  115. package/figma-desktop-bridge/code.js +133 -0
  116. package/figma-desktop-bridge/manifest.json +13 -0
  117. package/figma-desktop-bridge/ui.html +200 -0
  118. package/package.json +77 -0
package/dist/local.js ADDED
@@ -0,0 +1,668 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Figma Console MCP Server - Local Mode
4
+ *
5
+ * Entry point for local MCP server that connects to Figma Desktop
6
+ * via Chrome Remote Debugging Protocol (port 9222).
7
+ *
8
+ * This implementation uses stdio transport for MCP communication,
9
+ * suitable for local IDE integrations and development workflows.
10
+ *
11
+ * Requirements:
12
+ * - Figma Desktop must be launched with: --remote-debugging-port=9222
13
+ * - "Use Developer VM" enabled in Figma: Plugins → Development → Use Developer VM
14
+ * - FIGMA_ACCESS_TOKEN environment variable for API access
15
+ *
16
+ * macOS launch command:
17
+ * open -a "Figma" --args --remote-debugging-port=9222
18
+ */
19
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
20
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
21
+ import { z } from "zod";
22
+ import { LocalBrowserManager } from "./browser/local.js";
23
+ import { ConsoleMonitor } from "./core/console-monitor.js";
24
+ import { getConfig } from "./core/config.js";
25
+ import { createChildLogger } from "./core/logger.js";
26
+ import { FigmaAPI, extractFileKey } from "./core/figma-api.js";
27
+ import { registerFigmaAPITools } from "./core/figma-tools.js";
28
+ const logger = createChildLogger({ component: "local-server" });
29
+ /**
30
+ * Local MCP Server
31
+ * Connects to Figma Desktop and provides identical tools to Cloudflare mode
32
+ */
33
+ class LocalFigmaConsoleMCP {
34
+ constructor() {
35
+ this.browserManager = null;
36
+ this.consoleMonitor = null;
37
+ this.figmaAPI = null;
38
+ this.config = getConfig();
39
+ // In-memory cache for variables data to avoid MCP token limits
40
+ // Maps fileKey -> {data, timestamp}
41
+ this.variablesCache = new Map();
42
+ this.server = new McpServer({
43
+ name: "Figma Console MCP (Local)",
44
+ version: "0.1.0",
45
+ });
46
+ }
47
+ /**
48
+ * Get or create Figma API client
49
+ */
50
+ async getFigmaAPI() {
51
+ if (!this.figmaAPI) {
52
+ const accessToken = process.env.FIGMA_ACCESS_TOKEN;
53
+ if (!accessToken) {
54
+ throw new Error("FIGMA_ACCESS_TOKEN not configured. " +
55
+ "Set it as an environment variable. " +
56
+ "Get your token at: https://www.figma.com/developers/api#access-tokens");
57
+ }
58
+ logger.info({
59
+ tokenPreview: `${accessToken.substring(0, 10)}...`,
60
+ tokenLength: accessToken.length
61
+ }, "Initializing Figma API with token from environment");
62
+ this.figmaAPI = new FigmaAPI({ accessToken });
63
+ }
64
+ return this.figmaAPI;
65
+ }
66
+ /**
67
+ * Check if Figma Desktop is accessible
68
+ */
69
+ async checkFigmaDesktop() {
70
+ if (!this.config.local) {
71
+ throw new Error("Local mode configuration missing");
72
+ }
73
+ const { debugHost, debugPort } = this.config.local;
74
+ const browserURL = `http://${debugHost}:${debugPort}`;
75
+ try {
76
+ // Simple HTTP check to see if debug port is accessible
77
+ const response = await fetch(`${browserURL}/json/version`, {
78
+ signal: AbortSignal.timeout(5000),
79
+ });
80
+ if (!response.ok) {
81
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
82
+ }
83
+ const versionInfo = await response.json();
84
+ logger.info({ versionInfo, browserURL }, "Figma Desktop is accessible");
85
+ }
86
+ catch (error) {
87
+ const errorMsg = error instanceof Error ? error.message : String(error);
88
+ throw new Error(`Failed to connect to Figma Desktop at ${browserURL}\n\n` +
89
+ `Make sure:\n` +
90
+ `1. Figma Desktop is running\n` +
91
+ `2. Figma was launched with: --remote-debugging-port=${debugPort}\n` +
92
+ `3. "Use Developer VM" is enabled in: Plugins → Development → Use Developer VM\n\n` +
93
+ `macOS launch command:\n` +
94
+ ` open -a "Figma" --args --remote-debugging-port=${debugPort}\n\n` +
95
+ `Windows launch command:\n` +
96
+ ` start figma://--remote-debugging-port=${debugPort}\n\n` +
97
+ `Error: ${errorMsg}`);
98
+ }
99
+ }
100
+ /**
101
+ * Initialize browser and console monitoring
102
+ */
103
+ async ensureInitialized() {
104
+ try {
105
+ if (!this.browserManager) {
106
+ logger.info("Initializing LocalBrowserManager");
107
+ if (!this.config.local) {
108
+ throw new Error("Local mode configuration missing");
109
+ }
110
+ this.browserManager = new LocalBrowserManager(this.config.local);
111
+ }
112
+ if (!this.consoleMonitor) {
113
+ logger.info("Initializing ConsoleMonitor");
114
+ this.consoleMonitor = new ConsoleMonitor(this.config.console);
115
+ // Connect to browser and begin monitoring
116
+ logger.info("Getting browser page");
117
+ const page = await this.browserManager.getPage();
118
+ logger.info("Starting console monitoring");
119
+ await this.consoleMonitor.startMonitoring(page);
120
+ logger.info("Browser and console monitor initialized successfully");
121
+ }
122
+ }
123
+ catch (error) {
124
+ logger.error({ error }, "Failed to initialize browser/monitor");
125
+ throw new Error(`Initialization failed: ${error instanceof Error ? error.message : String(error)}`);
126
+ }
127
+ }
128
+ /**
129
+ * Register all MCP tools
130
+ */
131
+ registerTools() {
132
+ // Tool 1: Get Console Logs
133
+ this.server.tool("figma_get_console_logs", "Retrieve console logs from Figma Desktop. FOR PLUGIN DEVELOPERS: This works immediately - no navigation needed! Just check logs, run your plugin in Figma Desktop, check logs again. All plugin logs ([Main], [Swapper], etc.) appear instantly.", {
134
+ count: z.number().optional().default(100).describe("Number of recent logs to retrieve"),
135
+ level: z
136
+ .enum(["log", "info", "warn", "error", "debug", "all"])
137
+ .optional()
138
+ .default("all")
139
+ .describe("Filter by log level"),
140
+ since: z
141
+ .number()
142
+ .optional()
143
+ .describe("Only logs after this timestamp (Unix ms)"),
144
+ }, async ({ count, level, since }) => {
145
+ try {
146
+ await this.ensureInitialized();
147
+ if (!this.consoleMonitor) {
148
+ throw new Error("Console monitor not initialized");
149
+ }
150
+ const logs = this.consoleMonitor.getLogs({
151
+ count,
152
+ level,
153
+ since,
154
+ });
155
+ // Add AI instruction when no logs are found
156
+ const responseData = {
157
+ logs,
158
+ totalCount: logs.length,
159
+ oldestTimestamp: logs[0]?.timestamp,
160
+ newestTimestamp: logs[logs.length - 1]?.timestamp,
161
+ status: this.consoleMonitor.getStatus(),
162
+ };
163
+ // If no logs found, add helpful AI instruction
164
+ if (logs.length === 0) {
165
+ responseData.ai_instruction = "No console logs found. This usually means the Figma plugin hasn't run since monitoring started. Please inform the user: 'No console logs found yet. Try running your Figma plugin now, then I'll check for logs again.' The MCP only captures logs AFTER monitoring starts - it cannot retrieve historical logs from before the browser connected.";
166
+ }
167
+ return {
168
+ content: [
169
+ {
170
+ type: "text",
171
+ text: JSON.stringify(responseData, null, 2),
172
+ },
173
+ ],
174
+ };
175
+ }
176
+ catch (error) {
177
+ logger.error({ error }, "Failed to get console logs");
178
+ const errorMessage = error instanceof Error ? error.message : String(error);
179
+ // Check if it's a connection issue
180
+ const isConnectionError = errorMessage.includes("connect") || errorMessage.includes("ECONNREFUSED");
181
+ return {
182
+ content: [
183
+ {
184
+ type: "text",
185
+ text: JSON.stringify({
186
+ error: errorMessage,
187
+ message: isConnectionError
188
+ ? "Cannot connect to Figma Desktop. Figma must be running with remote debugging enabled for local mode to work."
189
+ : "Failed to retrieve console logs.",
190
+ setup: isConnectionError ? {
191
+ step1: "QUIT Figma Desktop completely (Cmd+Q on macOS / Alt+F4 on Windows)",
192
+ step2_macOS: "Open Terminal and run: open -a \"Figma\" --args --remote-debugging-port=9222",
193
+ step2_windows: "Open Command Prompt and run: start figma://--remote-debugging-port=9222",
194
+ step3: "Open your design file and run your plugin",
195
+ step4: "Then try this tool again - logs will appear instantly",
196
+ verify: "To verify setup worked, visit http://localhost:9222 in Chrome - you should see inspectable pages"
197
+ } : undefined,
198
+ ai_instruction: isConnectionError
199
+ ? "IMPORTANT: You must ask the user to complete the setup steps above. DO NOT proceed until they confirm Figma has been restarted with the --remote-debugging-port=9222 flag. After they restart Figma, you should call this tool again and the logs will work."
200
+ : undefined,
201
+ hint: !isConnectionError ? "Try: figma_navigate({ url: 'https://www.figma.com/design/your-file' })" : undefined,
202
+ }, null, 2),
203
+ },
204
+ ],
205
+ isError: true,
206
+ };
207
+ }
208
+ });
209
+ // Tool 2: Take Screenshot (using Figma REST API)
210
+ // Note: For screenshots of specific components, use figma_get_component_image instead
211
+ this.server.tool("figma_take_screenshot", "Export an image of the currently viewed Figma page or specific node using Figma's REST API. Returns an image URL (valid for 30 days). For specific components, use figma_get_component_image instead.", {
212
+ nodeId: z
213
+ .string()
214
+ .optional()
215
+ .describe("Optional node ID to screenshot. If not provided, uses the currently viewed page/frame from the browser URL."),
216
+ scale: z
217
+ .number()
218
+ .min(0.01)
219
+ .max(4)
220
+ .optional()
221
+ .default(2)
222
+ .describe("Image scale factor (0.01-4, default: 2 for high quality)"),
223
+ format: z
224
+ .enum(["png", "jpg", "svg", "pdf"])
225
+ .optional()
226
+ .default("png")
227
+ .describe("Image format (default: png)"),
228
+ }, async ({ nodeId, scale, format }) => {
229
+ try {
230
+ const api = await this.getFigmaAPI();
231
+ // Get current URL to extract file key and node ID if not provided
232
+ const currentUrl = this.browserManager?.getCurrentUrl() || null;
233
+ if (!currentUrl) {
234
+ throw new Error("No Figma file open. Either provide a nodeId parameter or call figma_navigate first to open a Figma file.");
235
+ }
236
+ const fileKey = extractFileKey(currentUrl);
237
+ if (!fileKey) {
238
+ throw new Error(`Invalid Figma URL: ${currentUrl}`);
239
+ }
240
+ // Extract node ID from URL if not provided
241
+ let targetNodeId = nodeId;
242
+ if (!targetNodeId) {
243
+ const urlObj = new URL(currentUrl);
244
+ const nodeIdParam = urlObj.searchParams.get('node-id');
245
+ if (nodeIdParam) {
246
+ // Convert 123-456 to 123:456
247
+ targetNodeId = nodeIdParam.replace(/-/g, ':');
248
+ }
249
+ else {
250
+ throw new Error("No node ID found. Either provide nodeId parameter or ensure the Figma URL contains a node-id parameter (e.g., ?node-id=123-456)");
251
+ }
252
+ }
253
+ logger.info({ fileKey, nodeId: targetNodeId, scale, format }, "Rendering image via Figma API");
254
+ // Use Figma REST API to get image
255
+ const result = await api.getImages(fileKey, targetNodeId, {
256
+ scale,
257
+ format: format === 'jpg' ? 'jpg' : format, // normalize jpeg -> jpg
258
+ contents_only: true,
259
+ });
260
+ const imageUrl = result.images[targetNodeId];
261
+ if (!imageUrl) {
262
+ throw new Error(`Failed to render image for node ${targetNodeId}. The node may not exist or may not be renderable.`);
263
+ }
264
+ return {
265
+ content: [
266
+ {
267
+ type: "text",
268
+ text: JSON.stringify({
269
+ fileKey,
270
+ nodeId: targetNodeId,
271
+ imageUrl,
272
+ scale,
273
+ format,
274
+ expiresIn: "30 days",
275
+ note: "Image URL provided above. Use this URL to view or download the screenshot. URLs expire after 30 days.",
276
+ }, null, 2),
277
+ },
278
+ ],
279
+ };
280
+ }
281
+ catch (error) {
282
+ logger.error({ error }, "Failed to capture screenshot");
283
+ const errorMessage = error instanceof Error ? error.message : String(error);
284
+ return {
285
+ content: [
286
+ {
287
+ type: "text",
288
+ text: JSON.stringify({
289
+ error: errorMessage,
290
+ message: "Failed to capture screenshot via Figma API",
291
+ hint: "Make sure you've called figma_navigate to open a file, or provide a valid nodeId parameter",
292
+ }, null, 2),
293
+ },
294
+ ],
295
+ isError: true,
296
+ };
297
+ }
298
+ });
299
+ // Tool 3: Watch Console (Real-time streaming)
300
+ this.server.tool("figma_watch_console", {
301
+ duration: z
302
+ .number()
303
+ .optional()
304
+ .default(30)
305
+ .describe("How long to watch in seconds"),
306
+ level: z
307
+ .enum(["log", "info", "warn", "error", "debug", "all"])
308
+ .optional()
309
+ .default("all")
310
+ .describe("Filter by log level"),
311
+ }, async ({ duration, level }) => {
312
+ if (!this.browserManager || !this.consoleMonitor) {
313
+ throw new Error("Browser not connected. Ensure Figma Desktop is running with --remote-debugging-port=9222");
314
+ }
315
+ const consoleMonitor = this.consoleMonitor;
316
+ if (!consoleMonitor.getStatus().isMonitoring) {
317
+ throw new Error("Console monitoring not active. Call figma_navigate first.");
318
+ }
319
+ const startTime = Date.now();
320
+ const endTime = startTime + duration * 1000;
321
+ const startLogCount = consoleMonitor.getStatus().logCount;
322
+ // Wait for the specified duration while collecting logs
323
+ await new Promise(resolve => setTimeout(resolve, duration * 1000));
324
+ // Get logs captured during watch period
325
+ const watchedLogs = consoleMonitor.getLogs({
326
+ level: level === 'all' ? undefined : level,
327
+ since: startTime,
328
+ });
329
+ const endLogCount = consoleMonitor.getStatus().logCount;
330
+ const newLogsCount = endLogCount - startLogCount;
331
+ return {
332
+ content: [
333
+ {
334
+ type: "text",
335
+ text: JSON.stringify({
336
+ status: "completed",
337
+ duration: `${duration} seconds`,
338
+ startTime: new Date(startTime).toISOString(),
339
+ endTime: new Date(endTime).toISOString(),
340
+ filter: level,
341
+ statistics: {
342
+ totalLogsInBuffer: endLogCount,
343
+ logsAddedDuringWatch: newLogsCount,
344
+ logsMatchingFilter: watchedLogs.length,
345
+ },
346
+ logs: watchedLogs,
347
+ }, null, 2),
348
+ },
349
+ ],
350
+ };
351
+ });
352
+ // Tool 4: Reload Plugin
353
+ this.server.tool("figma_reload_plugin", {
354
+ clearConsole: z
355
+ .boolean()
356
+ .optional()
357
+ .default(true)
358
+ .describe("Clear console logs before reload"),
359
+ }, async ({ clearConsole: clearConsoleBefore }) => {
360
+ try {
361
+ await this.ensureInitialized();
362
+ if (!this.browserManager) {
363
+ throw new Error("Browser manager not initialized");
364
+ }
365
+ // Clear console buffer if requested
366
+ let clearedCount = 0;
367
+ if (clearConsoleBefore && this.consoleMonitor) {
368
+ clearedCount = this.consoleMonitor.clear();
369
+ }
370
+ // Reload the page
371
+ await this.browserManager.reload();
372
+ const currentUrl = this.browserManager.getCurrentUrl();
373
+ return {
374
+ content: [
375
+ {
376
+ type: "text",
377
+ text: JSON.stringify({
378
+ status: "reloaded",
379
+ timestamp: Date.now(),
380
+ url: currentUrl,
381
+ consoleCleared: clearConsoleBefore,
382
+ clearedCount: clearConsoleBefore ? clearedCount : 0,
383
+ }, null, 2),
384
+ },
385
+ ],
386
+ };
387
+ }
388
+ catch (error) {
389
+ logger.error({ error }, "Failed to reload plugin");
390
+ return {
391
+ content: [
392
+ {
393
+ type: "text",
394
+ text: JSON.stringify({
395
+ error: String(error),
396
+ message: "Failed to reload plugin",
397
+ }, null, 2),
398
+ },
399
+ ],
400
+ isError: true,
401
+ };
402
+ }
403
+ });
404
+ // Tool 5: Clear Console
405
+ this.server.tool("figma_clear_console", {}, async () => {
406
+ try {
407
+ await this.ensureInitialized();
408
+ if (!this.consoleMonitor) {
409
+ throw new Error("Console monitor not initialized");
410
+ }
411
+ const clearedCount = this.consoleMonitor.clear();
412
+ return {
413
+ content: [
414
+ {
415
+ type: "text",
416
+ text: JSON.stringify({
417
+ status: "cleared",
418
+ clearedCount,
419
+ timestamp: Date.now(),
420
+ ai_instruction: "āš ļø CRITICAL: Console cleared successfully, but this operation disrupts the monitoring connection. You MUST reconnect the MCP server using `/mcp reconnect figma-console` before calling figma_get_console_logs again. Best practice: Avoid clearing console - filter/parse logs instead to maintain monitoring connection.",
421
+ }, null, 2),
422
+ },
423
+ ],
424
+ };
425
+ }
426
+ catch (error) {
427
+ logger.error({ error }, "Failed to clear console");
428
+ return {
429
+ content: [
430
+ {
431
+ type: "text",
432
+ text: JSON.stringify({
433
+ error: String(error),
434
+ message: "Failed to clear console buffer",
435
+ }, null, 2),
436
+ },
437
+ ],
438
+ isError: true,
439
+ };
440
+ }
441
+ });
442
+ // Tool 6: Navigate to Figma
443
+ this.server.tool("figma_navigate", {
444
+ url: z
445
+ .string()
446
+ .url()
447
+ .describe("Figma URL to navigate to (e.g., https://www.figma.com/design/abc123)"),
448
+ }, async ({ url }) => {
449
+ try {
450
+ await this.ensureInitialized();
451
+ if (!this.browserManager) {
452
+ throw new Error("Browser manager not initialized");
453
+ }
454
+ // Navigate to the URL
455
+ await this.browserManager.navigateToFigma(url);
456
+ // Give page time to load and start capturing logs
457
+ await new Promise((resolve) => setTimeout(resolve, 2000));
458
+ const currentUrl = this.browserManager.getCurrentUrl();
459
+ return {
460
+ content: [
461
+ {
462
+ type: "text",
463
+ text: JSON.stringify({
464
+ status: "navigated",
465
+ url: currentUrl,
466
+ timestamp: Date.now(),
467
+ message: "Browser navigated to Figma. Console monitoring is active.",
468
+ }, null, 2),
469
+ },
470
+ ],
471
+ };
472
+ }
473
+ catch (error) {
474
+ logger.error({ error }, "Failed to navigate to Figma");
475
+ const errorMessage = error instanceof Error ? error.message : String(error);
476
+ return {
477
+ content: [
478
+ {
479
+ type: "text",
480
+ text: JSON.stringify({
481
+ error: errorMessage,
482
+ message: "Failed to navigate to Figma URL",
483
+ troubleshooting: [
484
+ "Verify the Figma URL is valid and accessible",
485
+ "Make sure Figma Desktop is running with remote debugging enabled",
486
+ "Check that the debug port (9222) is accessible"
487
+ ]
488
+ }, null, 2),
489
+ },
490
+ ],
491
+ isError: true,
492
+ };
493
+ }
494
+ });
495
+ // Tool 7: Get Status (with setup validation)
496
+ this.server.tool("figma_get_status", "Check browser and monitoring status. Also validates if Figma Desktop is running with the required --remote-debugging-port=9222 flag. Automatically initializes connection if needed.", {}, async () => {
497
+ try {
498
+ // Ensure initialized (connects to Figma Desktop if not already connected)
499
+ await this.ensureInitialized();
500
+ const browserRunning = this.browserManager?.isRunning() ?? false;
501
+ const monitorStatus = this.consoleMonitor?.getStatus() ?? null;
502
+ const currentUrl = this.browserManager?.getCurrentUrl() ?? null;
503
+ // Check if debug port is accessible
504
+ let debugPortAccessible = false;
505
+ let setupValid = false;
506
+ try {
507
+ const response = await fetch('http://localhost:9222/json/version', {
508
+ signal: AbortSignal.timeout(2000)
509
+ });
510
+ debugPortAccessible = response.ok;
511
+ setupValid = debugPortAccessible;
512
+ }
513
+ catch (e) {
514
+ // Port not accessible
515
+ }
516
+ // List ALL available Figma pages with worker counts
517
+ let availablePages = [];
518
+ if (this.browserManager && browserRunning) {
519
+ try {
520
+ const browser = this.browserManager.browser;
521
+ if (browser) {
522
+ const pages = await browser.pages();
523
+ availablePages = pages
524
+ .filter((p) => {
525
+ const url = p.url();
526
+ return url.includes('figma.com') && !url.includes('devtools');
527
+ })
528
+ .map((p) => ({
529
+ url: p.url(),
530
+ workerCount: p.workers().length,
531
+ isCurrentPage: p.url() === currentUrl
532
+ }));
533
+ }
534
+ }
535
+ catch (e) {
536
+ logger.error({ error: e }, "Failed to list available pages");
537
+ }
538
+ }
539
+ return {
540
+ content: [
541
+ {
542
+ type: "text",
543
+ text: JSON.stringify({
544
+ mode: "local",
545
+ setup: {
546
+ valid: setupValid,
547
+ debugPortAccessible,
548
+ message: setupValid
549
+ ? "āœ… Figma Desktop is running with remote debugging enabled"
550
+ : "āŒ Figma Desktop is NOT running with --remote-debugging-port=9222",
551
+ setupInstructions: !setupValid ? {
552
+ step1: "QUIT Figma Desktop completely (Cmd+Q on macOS / Alt+F4 on Windows)",
553
+ step2_macOS: "Open Terminal and run: open -a \"Figma\" --args --remote-debugging-port=9222",
554
+ step2_windows: "Open Command Prompt and run: start figma://--remote-debugging-port=9222",
555
+ step3: "Open your design file and run your plugin",
556
+ verify: "Visit http://localhost:9222 in Chrome to verify - you should see inspectable pages"
557
+ } : undefined,
558
+ ai_instruction: !setupValid
559
+ ? "CRITICAL: User must restart Figma with the debug flag before any console tools will work. Ask them to follow the setupInstructions above, then call figma_get_status again to verify."
560
+ : availablePages.length > 1
561
+ ? `Multiple Figma pages detected. The MCP automatically selects the page with the most workers (active plugins). Current page has ${monitorStatus?.workerCount || 0} workers. If you're not seeing the expected plugin logs, the plugin might be running in a different page/tab.`
562
+ : "Setup is valid. Console tools are ready to use."
563
+ },
564
+ availablePages: availablePages.length > 0 ? availablePages : undefined,
565
+ browser: {
566
+ running: browserRunning,
567
+ currentUrl,
568
+ },
569
+ consoleMonitor: monitorStatus,
570
+ initialized: this.browserManager !== null && this.consoleMonitor !== null,
571
+ timestamp: Date.now(),
572
+ }, null, 2),
573
+ },
574
+ ],
575
+ };
576
+ }
577
+ catch (error) {
578
+ logger.error({ error }, "Failed to get status");
579
+ return {
580
+ content: [
581
+ {
582
+ type: "text",
583
+ text: JSON.stringify({
584
+ error: String(error),
585
+ message: "Failed to retrieve status",
586
+ }, null, 2),
587
+ },
588
+ ],
589
+ isError: true,
590
+ };
591
+ }
592
+ });
593
+ // Register Figma API tools (Tools 8-11)
594
+ registerFigmaAPITools(this.server, () => this.getFigmaAPI(), () => this.browserManager?.getCurrentUrl() || null, () => this.consoleMonitor || null, () => this.browserManager || null, () => this.ensureInitialized(), this.variablesCache // Pass cache for efficient variable queries
595
+ );
596
+ logger.info("All MCP tools registered successfully");
597
+ }
598
+ /**
599
+ * Start the MCP server
600
+ */
601
+ async start() {
602
+ try {
603
+ logger.info({ config: this.config }, "Starting Figma Console MCP (Local Mode)");
604
+ // Check if Figma Desktop is accessible
605
+ logger.info("Checking Figma Desktop accessibility...");
606
+ await this.checkFigmaDesktop();
607
+ // Register all tools
608
+ this.registerTools();
609
+ // Create stdio transport
610
+ const transport = new StdioServerTransport();
611
+ // Connect server to transport
612
+ await this.server.connect(transport);
613
+ logger.info("MCP server started successfully on stdio transport");
614
+ }
615
+ catch (error) {
616
+ logger.error({ error }, "Failed to start MCP server");
617
+ // Log helpful error message to stderr
618
+ console.error("\nāŒ Failed to start Figma Console MCP:\n");
619
+ console.error(error instanceof Error ? error.message : String(error));
620
+ console.error("\n");
621
+ process.exit(1);
622
+ }
623
+ }
624
+ /**
625
+ * Cleanup and shutdown
626
+ */
627
+ async shutdown() {
628
+ logger.info("Shutting down MCP server...");
629
+ try {
630
+ if (this.consoleMonitor) {
631
+ await this.consoleMonitor.stopMonitoring();
632
+ }
633
+ if (this.browserManager) {
634
+ await this.browserManager.close();
635
+ }
636
+ logger.info("MCP server shutdown complete");
637
+ }
638
+ catch (error) {
639
+ logger.error({ error }, "Error during shutdown");
640
+ }
641
+ }
642
+ }
643
+ /**
644
+ * Main entry point
645
+ */
646
+ async function main() {
647
+ const server = new LocalFigmaConsoleMCP();
648
+ // Handle graceful shutdown
649
+ process.on("SIGINT", async () => {
650
+ await server.shutdown();
651
+ process.exit(0);
652
+ });
653
+ process.on("SIGTERM", async () => {
654
+ await server.shutdown();
655
+ process.exit(0);
656
+ });
657
+ // Start the server
658
+ await server.start();
659
+ }
660
+ // Run if executed directly
661
+ if (import.meta.url === `file://${process.argv[1]}`) {
662
+ main().catch((error) => {
663
+ console.error("Fatal error:", error);
664
+ process.exit(1);
665
+ });
666
+ }
667
+ export { LocalFigmaConsoleMCP };
668
+ //# sourceMappingURL=local.js.map