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.
- package/LICENSE +21 -0
- package/README.md +328 -0
- package/dist/browser/base.d.ts +50 -0
- package/dist/browser/base.d.ts.map +1 -0
- package/dist/browser/base.js +6 -0
- package/dist/browser/base.js.map +1 -0
- package/dist/browser/local.d.ts +66 -0
- package/dist/browser/local.d.ts.map +1 -0
- package/dist/browser/local.js +223 -0
- package/dist/browser/local.js.map +1 -0
- package/dist/cloudflare/browser/base.js +5 -0
- package/dist/cloudflare/browser/cloudflare.js +156 -0
- package/dist/cloudflare/browser-manager.js +157 -0
- package/dist/cloudflare/core/config.js +161 -0
- package/dist/cloudflare/core/console-monitor.js +382 -0
- package/dist/cloudflare/core/enrichment/enrichment-service.js +272 -0
- package/dist/cloudflare/core/enrichment/index.js +7 -0
- package/dist/cloudflare/core/enrichment/relationship-mapper.js +351 -0
- package/dist/cloudflare/core/enrichment/style-resolver.js +326 -0
- package/dist/cloudflare/core/figma-api.js +273 -0
- package/dist/cloudflare/core/figma-desktop-connector.js +383 -0
- package/dist/cloudflare/core/figma-style-extractor.js +311 -0
- package/dist/cloudflare/core/figma-tools.js +2299 -0
- package/dist/cloudflare/core/logger.js +53 -0
- package/dist/cloudflare/core/snippet-injector.js +96 -0
- package/dist/cloudflare/core/types/enriched.js +5 -0
- package/dist/cloudflare/core/types/index.js +4 -0
- package/dist/cloudflare/index.js +1059 -0
- package/dist/cloudflare/test-browser.js +88 -0
- package/dist/config.d.ts +17 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +141 -0
- package/dist/config.js.map +1 -0
- package/dist/core/config.d.ts +17 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +162 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/console-monitor.d.ts +81 -0
- package/dist/core/console-monitor.d.ts.map +1 -0
- package/dist/core/console-monitor.js +383 -0
- package/dist/core/console-monitor.js.map +1 -0
- package/dist/core/enrichment/enrichment-service.d.ts +52 -0
- package/dist/core/enrichment/enrichment-service.d.ts.map +1 -0
- package/dist/core/enrichment/enrichment-service.js +273 -0
- package/dist/core/enrichment/enrichment-service.js.map +1 -0
- package/dist/core/enrichment/index.d.ts +8 -0
- package/dist/core/enrichment/index.d.ts.map +1 -0
- package/dist/core/enrichment/index.js +8 -0
- package/dist/core/enrichment/index.js.map +1 -0
- package/dist/core/enrichment/relationship-mapper.d.ts +106 -0
- package/dist/core/enrichment/relationship-mapper.d.ts.map +1 -0
- package/dist/core/enrichment/relationship-mapper.js +352 -0
- package/dist/core/enrichment/relationship-mapper.js.map +1 -0
- package/dist/core/enrichment/style-resolver.d.ts +80 -0
- package/dist/core/enrichment/style-resolver.d.ts.map +1 -0
- package/dist/core/enrichment/style-resolver.js +327 -0
- package/dist/core/enrichment/style-resolver.js.map +1 -0
- package/dist/core/figma-api.d.ts +137 -0
- package/dist/core/figma-api.d.ts.map +1 -0
- package/dist/core/figma-api.js +274 -0
- package/dist/core/figma-api.js.map +1 -0
- package/dist/core/figma-desktop-connector.d.ts +52 -0
- package/dist/core/figma-desktop-connector.d.ts.map +1 -0
- package/dist/core/figma-desktop-connector.js +384 -0
- package/dist/core/figma-desktop-connector.js.map +1 -0
- package/dist/core/figma-style-extractor.d.ts +76 -0
- package/dist/core/figma-style-extractor.d.ts.map +1 -0
- package/dist/core/figma-style-extractor.js +312 -0
- package/dist/core/figma-style-extractor.js.map +1 -0
- package/dist/core/figma-tools.d.ts +15 -0
- package/dist/core/figma-tools.d.ts.map +1 -0
- package/dist/core/figma-tools.js +2300 -0
- package/dist/core/figma-tools.js.map +1 -0
- package/dist/core/logger.d.ts +22 -0
- package/dist/core/logger.d.ts.map +1 -0
- package/dist/core/logger.js +54 -0
- package/dist/core/logger.js.map +1 -0
- package/dist/core/snippet-injector.d.ts +24 -0
- package/dist/core/snippet-injector.d.ts.map +1 -0
- package/dist/core/snippet-injector.js +97 -0
- package/dist/core/snippet-injector.js.map +1 -0
- package/dist/core/types/enriched.d.ts +213 -0
- package/dist/core/types/enriched.d.ts.map +1 -0
- package/dist/core/types/enriched.js +6 -0
- package/dist/core/types/enriched.js.map +1 -0
- package/dist/core/types/index.d.ts +112 -0
- package/dist/core/types/index.d.ts.map +1 -0
- package/dist/core/types/index.js +5 -0
- package/dist/core/types/index.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +72 -0
- package/dist/index.js.map +1 -0
- package/dist/local.d.ts +57 -0
- package/dist/local.d.ts.map +1 -0
- package/dist/local.js +668 -0
- package/dist/local.js.map +1 -0
- package/dist/logger.d.ts +22 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +45 -0
- package/dist/logger.js.map +1 -0
- package/dist/server.d.ts +40 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +99 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/index.d.ts +15 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +184 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/types/index.d.ts +102 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +6 -0
- package/dist/types/index.js.map +1 -0
- package/figma-desktop-bridge/README.md +232 -0
- package/figma-desktop-bridge/code.js +133 -0
- package/figma-desktop-bridge/manifest.json +13 -0
- package/figma-desktop-bridge/ui.html +200 -0
- 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
|