figma-console-mcp 1.43.0 → 1.44.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/dist/local.d.ts CHANGED
@@ -29,6 +29,9 @@ declare class LocalFigmaConsoleMCP {
29
29
  private wsReaperStop;
30
30
  /** Heartbeat timer that refreshes port file to prove this server is active */
31
31
  private wsHeartbeatTimer;
32
+ /** Optional Streamable HTTP endpoint (FIGMA_MCP_HTTP_PORT), shared by local clients. */
33
+ private httpEndpoint;
34
+ private httpStartupError;
32
35
  private config;
33
36
  private variablesCache;
34
37
  private auditDataCache;
@@ -40,6 +43,11 @@ declare class LocalFigmaConsoleMCP {
40
43
  */
41
44
  private invalidateVariablesCache;
42
45
  constructor();
46
+ /**
47
+ * A configured McpServer with no tools yet. stdio uses one for the whole
48
+ * run; the optional HTTP endpoint builds one per request (see http-transport.ts).
49
+ */
50
+ private createMcpServer;
43
51
  /**
44
52
  * Get or create Figma API client
45
53
  */
@@ -87,6 +95,11 @@ declare class LocalFigmaConsoleMCP {
87
95
  * Start the MCP server
88
96
  */
89
97
  start(): Promise<void>;
98
+ /**
99
+ * Serve MCP over Streamable HTTP as well, when FIGMA_MCP_HTTP_PORT is set.
100
+ * A failure here (bad value, port taken) is reported but never stops stdio.
101
+ */
102
+ private startHttpEndpoint;
90
103
  /**
91
104
  * Cleanup and shutdown
92
105
  */
@@ -1 +1 @@
1
- {"version":3,"file":"local.d.ts","sourceRoot":"","sources":["../src/local.ts"],"names":[],"mappings":";AAEA;;;;;;;;;;;;GAYG;AAoGH;;;GAGG;AACH,cAAM,oBAAoB;IACzB,OAAO,CAAC,MAAM,CAAY;IAC1B,OAAO,CAAC,QAAQ,CAAyB;IACzC,OAAO,CAAC,QAAQ,CAAqC;IACrD,OAAO,CAAC,cAAc,CAA+C;IACrE,uGAAuG;IACvG,OAAO,CAAC,YAAY,CAAuB;IAC3C,6DAA6D;IAC7D,OAAO,CAAC,eAAe,CAA2B;IAClD,2EAA2E;IAC3E,OAAO,CAAC,YAAY,CAA6B;IACjD,8EAA8E;IAC9E,OAAO,CAAC,gBAAgB,CAA+C;IACvE,OAAO,CAAC,MAAM,CAAe;IAI7B,OAAO,CAAC,cAAc,CAMR;IAOd,OAAO,CAAC,cAAc,CAMR;IACd,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CAAiB;IAE3D;;;;OAIG;IACH,OAAO,CAAC,wBAAwB;;IAsFhC;;OAEG;YACW,WAAW;IAoBzB;;;OAGG;YACW,mBAAmB;IAyBjC;;;;;OAKG;IACH,OAAO,CAAC,eAAe;IAmBvB;;;;OAIG;IACH,OAAO,CAAC,oBAAoB;IAkD5B;;;;OAIG;IACH,OAAO,CAAC,iBAAiB;IAezB;;OAEG;YACW,iBAAiB;IAiB/B,wDAAwD;IACxD,OAAO,CAAC,gBAAgB,CAAuB;IAE/C;;;OAGG;IACH,OAAO,CAAC,aAAa;IAkBrB;;OAEG;IACH,OAAO,CAAC,aAAa;IA8yGrB;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAoP5B;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CA8B/B;AAyGD,OAAO,EAAE,oBAAoB,EAAE,CAAC"}
1
+ {"version":3,"file":"local.d.ts","sourceRoot":"","sources":["../src/local.ts"],"names":[],"mappings":";AAEA;;;;;;;;;;;;GAYG;AAsGH;;;GAGG;AACH,cAAM,oBAAoB;IACzB,OAAO,CAAC,MAAM,CAAY;IAC1B,OAAO,CAAC,QAAQ,CAAyB;IACzC,OAAO,CAAC,QAAQ,CAAqC;IACrD,OAAO,CAAC,cAAc,CAA+C;IACrE,uGAAuG;IACvG,OAAO,CAAC,YAAY,CAAuB;IAC3C,6DAA6D;IAC7D,OAAO,CAAC,eAAe,CAA2B;IAClD,2EAA2E;IAC3E,OAAO,CAAC,YAAY,CAA6B;IACjD,8EAA8E;IAC9E,OAAO,CAAC,gBAAgB,CAA+C;IACvE,wFAAwF;IACxF,OAAO,CAAC,YAAY,CAAgC;IACpD,OAAO,CAAC,gBAAgB,CAAuB;IAC/C,OAAO,CAAC,MAAM,CAAe;IAI7B,OAAO,CAAC,cAAc,CAMR;IAOd,OAAO,CAAC,cAAc,CAMR;IACd,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CAAiB;IAE3D;;;;OAIG;IACH,OAAO,CAAC,wBAAwB;;IAWhC;;;OAGG;IACH,OAAO,CAAC,eAAe;IAgFvB;;OAEG;YACW,WAAW;IAoBzB;;;OAGG;YACW,mBAAmB;IAyBjC;;;;;OAKG;IACH,OAAO,CAAC,eAAe;IAmBvB;;;;OAIG;IACH,OAAO,CAAC,oBAAoB;IAkD5B;;;;OAIG;IACH,OAAO,CAAC,iBAAiB;IAezB;;OAEG;YACW,iBAAiB;IAiB/B,wDAAwD;IACxD,OAAO,CAAC,gBAAgB,CAAuB;IAE/C;;;OAGG;IACH,OAAO,CAAC,aAAa;IAkBrB;;OAEG;IACH,OAAO,CAAC,aAAa;IAozGrB;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAsP5B;;;OAGG;YACW,iBAAiB;IAgC/B;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CAmC/B;AAyGD,OAAO,EAAE,oBAAoB,EAAE,CAAC"}
package/dist/local.js CHANGED
@@ -40,7 +40,8 @@ import { wrapServerForIdentity } from "./core/identity.js";
40
40
  import { PACKAGE_ROOT } from "./core/resolve-package-root.js";
41
41
  import { FigmaWebSocketServer, getBundledPluginVersion, getServerVersion } from "./core/websocket-server.js";
42
42
  import { WebSocketConnector } from "./core/websocket-connector.js";
43
- import { DEFAULT_WS_PORT, getPortRange, advertisePort, unadvertisePort, registerPortCleanup, startPeriodicReaper, discoverActiveInstances, cleanupStalePortFiles, cleanupOrphanedProcesses, evictOldestInstance, refreshPortAdvertisement, noteToolCall, HEARTBEAT_INTERVAL_MS, } from "./core/port-discovery.js";
43
+ import { DEFAULT_WS_PORT, getPortRange, advertisePort, unadvertisePort, registerPortCleanup, startPeriodicReaper, discoverActiveInstances, cleanupStalePortFiles, cleanupOrphanedProcesses, evictOldestInstance, refreshPortAdvertisement, noteToolCall, setAdvertisedMcpHttpUrl, HEARTBEAT_INTERVAL_MS, } from "./core/port-discovery.js";
44
+ import { parseHttpPort, startLocalHttpTransport } from "./core/http-transport.js";
44
45
  import { onStdinClosed } from "./core/stdin-lifecycle.js";
45
46
  import { registerTokenBrowserApp } from "./apps/token-browser/server.js";
46
47
  import { registerDesignSystemAuditTool, registerDesignSystemDashboardApp, } from "./apps/design-system-dashboard/server.js";
@@ -111,6 +112,9 @@ class LocalFigmaConsoleMCP {
111
112
  this.wsReaperStop = null;
112
113
  /** Heartbeat timer that refreshes port file to prove this server is active */
113
114
  this.wsHeartbeatTimer = null;
115
+ /** Optional Streamable HTTP endpoint (FIGMA_MCP_HTTP_PORT), shared by local clients. */
116
+ this.httpEndpoint = null;
117
+ this.httpStartupError = null;
114
118
  this.config = getConfig();
115
119
  // In-memory cache for variables data to avoid MCP token limits
116
120
  // Maps fileKey -> {data, timestamp}
@@ -123,7 +127,14 @@ class LocalFigmaConsoleMCP {
123
127
  this.auditDataCache = new Map();
124
128
  /** Stable plugin directory path (set during startup) */
125
129
  this.stablePluginPath = null;
126
- this.server = new McpServer({
130
+ this.server = this.createMcpServer();
131
+ }
132
+ /**
133
+ * A configured McpServer with no tools yet. stdio uses one for the whole
134
+ * run; the optional HTTP endpoint builds one per request (see http-transport.ts).
135
+ */
136
+ createMcpServer() {
137
+ const server = new McpServer({
127
138
  name: "Figma Console MCP (Local)",
128
139
  // Was hard-coded "0.1.0" — every client saw that regardless of the release
129
140
  version: getServerVersion(),
@@ -194,7 +205,8 @@ If Design Systems Assistant MCP is not available, install it from: https://githu
194
205
  // Stamp every tool response (and every thrown error) with our MCP identity
195
206
  // so LLMs can attribute output unambiguously when multiple Figma-related
196
207
  // MCPs are connected. Idempotent for already-tagged responses.
197
- wrapServerForIdentity(this.server, { onToolCall: noteToolCall });
208
+ wrapServerForIdentity(server, { onToolCall: noteToolCall });
209
+ return server;
198
210
  }
199
211
  /**
200
212
  * Get or create Figma API client
@@ -366,9 +378,9 @@ If Design Systems Assistant MCP is not available, install it from: https://githu
366
378
  /**
367
379
  * Register all MCP tools
368
380
  */
369
- registerTools() {
381
+ registerTools(server) {
370
382
  // Tool 1: Get Console Logs
371
- this.server.tool("figma_get_console_logs", "Retrieve console logs captured by the Desktop Bridge plugin: console output from code run through it (figma_execute and the other write tools) and from the bridge itself. It does NOT capture another plugin's console — Figma runs one plugin at a time in a file, each in its own sandbox, so the bridge cannot see a plugin you are developing. To debug your own plugin's logic, run that code through figma_execute and read the logs here, or use Figma's own developer console for the plugin.", {
383
+ server.tool("figma_get_console_logs", "Retrieve console logs captured by the Desktop Bridge plugin: console output from code run through it (figma_execute and the other write tools) and from the bridge itself. It does NOT capture another plugin's console — Figma runs one plugin at a time in a file, each in its own sandbox, so the bridge cannot see a plugin you are developing. To debug your own plugin's logic, run that code through figma_execute and read the logs here, or use Figma's own developer console for the plugin.", {
372
384
  count: z
373
385
  .number()
374
386
  .optional()
@@ -461,7 +473,7 @@ If Design Systems Assistant MCP is not available, install it from: https://githu
461
473
  // Bridge-first: the plugin's exportAsync works on any Figma plan with no REST
462
474
  // token, and reflects the current runtime state (no cloud-sync lag).
463
475
  // Note: For screenshots of specific components, use figma_get_component_image instead
464
- this.server.tool("figma_take_screenshot", `Export an image of the current Figma page or specific node. Uses the Desktop Bridge plugin (exportAsync) when connected — works on any plan, no REST token needed, reflects current runtime state. Falls back to the Figma REST API when the bridge is unavailable or for PDF format. Use for visual validation after design changes — check alignment, spacing, proportions. Pass nodeId to target specific elements. For components, prefer figma_get_component_image.`, {
476
+ server.tool("figma_take_screenshot", `Export an image of the current Figma page or specific node. Uses the Desktop Bridge plugin (exportAsync) when connected — works on any plan, no REST token needed, reflects current runtime state. Falls back to the Figma REST API when the bridge is unavailable or for PDF format. Use for visual validation after design changes — check alignment, spacing, proportions. Pass nodeId to target specific elements. For components, prefer figma_get_component_image.`, {
465
477
  nodeId: z
466
478
  .string()
467
479
  .optional()
@@ -646,7 +658,7 @@ If Design Systems Assistant MCP is not available, install it from: https://githu
646
658
  }
647
659
  });
648
660
  // Tool 3: Watch Console (Real-time streaming)
649
- this.server.tool("figma_watch_console", "Stream console logs captured by the Desktop Bridge plugin in real-time for a specified duration (max 5 minutes). Covers code run through the bridge (figma_execute and the write tools) — not other plugins, which run in their own sandboxes. Returns all logs captured during the watch period with summary statistics. NOT for retrieving past logs (use figma_get_console_logs). Best for: watching long-running figma_execute scripts, debugging race conditions, monitoring async operations.", {
661
+ server.tool("figma_watch_console", "Stream console logs captured by the Desktop Bridge plugin in real-time for a specified duration (max 5 minutes). Covers code run through the bridge (figma_execute and the write tools) — not other plugins, which run in their own sandboxes. Returns all logs captured during the watch period with summary statistics. NOT for retrieving past logs (use figma_get_console_logs). Best for: watching long-running figma_execute scripts, debugging race conditions, monitoring async operations.", {
650
662
  duration: z
651
663
  .number()
652
664
  .optional()
@@ -696,7 +708,7 @@ If Design Systems Assistant MCP is not available, install it from: https://githu
696
708
  };
697
709
  });
698
710
  // Tool 4: Reload Plugin
699
- this.server.tool("figma_reload_plugin", "Reload the current Figma page/plugin to test code changes. Optionally clears console logs before reload. Use when user says: 'reload plugin', 'refresh page', 'restart plugin', 'test my changes'. Returns reload confirmation and current URL. Best for rapid iteration during plugin development.", {
711
+ server.tool("figma_reload_plugin", "Reload the current Figma page/plugin to test code changes. Optionally clears console logs before reload. Use when user says: 'reload plugin', 'refresh page', 'restart plugin', 'test my changes'. Returns reload confirmation and current URL. Best for rapid iteration during plugin development.", {
700
712
  clearConsole: z
701
713
  .boolean()
702
714
  .optional()
@@ -764,7 +776,7 @@ If Design Systems Assistant MCP is not available, install it from: https://githu
764
776
  }
765
777
  });
766
778
  // Tool 5: Clear Console
767
- this.server.tool("figma_clear_console", "Clear the console log buffer. Safely clears the buffer without disrupting the connection. Returns number of logs cleared.", {}, async () => {
779
+ server.tool("figma_clear_console", "Clear the console log buffer. Safely clears the buffer without disrupting the connection. Returns number of logs cleared.", {}, async () => {
768
780
  try {
769
781
  let clearedCount = 0;
770
782
  let transport = "websocket";
@@ -816,7 +828,7 @@ If Design Systems Assistant MCP is not available, install it from: https://githu
816
828
  }
817
829
  });
818
830
  // Tool 6: Navigate / switch active file
819
- this.server.tool("figma_navigate", "Switch the active Figma file target among files that already have the Desktop Bridge plugin running. Local mode is WebSocket-only — this tool does NOT launch a browser or open files. If the requested URL is already the active file, it confirms the connection. If another connected plugin matches the URL, it switches the active target so subsequent tool calls hit that file. If no connected plugin matches, returns guidance for the user to open the Desktop Bridge plugin in the target file. Use figma_list_open_files to see all connected files.\n\nPass lock: true to PIN this file as the target — new connections, reconnects, and the user's own selection/page changes in other files will no longer move the target. Use this for parallel work (agent edits one file while the user works in another) so commands can't silently route to the wrong file. Switching to another file (or lock: false) releases the pin; it also releases automatically if the pinned file disconnects.", {
831
+ server.tool("figma_navigate", "Switch the active Figma file target among files that already have the Desktop Bridge plugin running. Local mode is WebSocket-only — this tool does NOT launch a browser or open files. If the requested URL is already the active file, it confirms the connection. If another connected plugin matches the URL, it switches the active target so subsequent tool calls hit that file. If no connected plugin matches, returns guidance for the user to open the Desktop Bridge plugin in the target file. Use figma_list_open_files to see all connected files.\n\nPass lock: true to PIN this file as the target — new connections, reconnects, and the user's own selection/page changes in other files will no longer move the target. Use this for parallel work (agent edits one file while the user works in another) so commands can't silently route to the wrong file. Switching to another file (or lock: false) releases the pin; it also releases automatically if the pinned file disconnects.", {
820
832
  url: z
821
833
  .string()
822
834
  .url()
@@ -953,7 +965,7 @@ If Design Systems Assistant MCP is not available, install it from: https://githu
953
965
  }
954
966
  });
955
967
  // Tool 7: Get Status (with setup validation)
956
- this.server.tool("figma_get_status", "Check connection status to Figma Desktop. Reports transport status and connection health via the Desktop Bridge plugin (WebSocket transport). Use probe:true for an active roundtrip verification that the plugin is actually responding.", {
968
+ server.tool("figma_get_status", "Check connection status to Figma Desktop. Reports transport status and connection health via the Desktop Bridge plugin (WebSocket transport). Use probe:true for an active roundtrip verification that the plugin is actually responding.", {
957
969
  probe: z.boolean().optional().describe("When true, sends a live roundtrip command to the plugin to verify the connection is actually responsive (not just TCP-open). Returns probeResult with success/latency. Recommended for health checks."),
958
970
  }, async ({ probe }) => {
959
971
  try {
@@ -1049,6 +1061,11 @@ If Design Systems Assistant MCP is not available, install it from: https://githu
1049
1061
  monitorWorkerCount: 0,
1050
1062
  transport: {
1051
1063
  active: activeTransport,
1064
+ mcpHttp: this.httpEndpoint
1065
+ ? { url: this.httpEndpoint.url, sessions: this.httpEndpoint.sessionCount(), auth: process.env.FIGMA_MCP_HTTP_TOKEN ? "bearer" : "none" }
1066
+ : this.httpStartupError
1067
+ ? { error: this.httpStartupError }
1068
+ : undefined,
1052
1069
  websocket: {
1053
1070
  available: wsConnected,
1054
1071
  serverRunning: this.wsServer?.isStarted() ?? false,
@@ -1075,6 +1092,7 @@ If Design Systems Assistant MCP is not available, install it from: https://githu
1075
1092
  port: i.port,
1076
1093
  pid: i.pid,
1077
1094
  startedAt: i.startedAt,
1095
+ mcpHttpUrl: i.mcpHttpUrl,
1078
1096
  }));
1079
1097
  }
1080
1098
  catch {
@@ -1180,7 +1198,7 @@ If Design Systems Assistant MCP is not available, install it from: https://githu
1180
1198
  // CONNECTION MANAGEMENT TOOLS
1181
1199
  // ============================================================================
1182
1200
  // Tool: Force reconnect to Figma Desktop
1183
- this.server.tool("figma_reconnect", "Force a complete reconnection to Figma Desktop. Use when connection seems stale or after switching files.", {}, async () => {
1201
+ server.tool("figma_reconnect", "Force a complete reconnection to Figma Desktop. Use when connection seems stale or after switching files.", {}, async () => {
1184
1202
  try {
1185
1203
  // figma_reconnect is informational in WebSocket-only mode — the
1186
1204
  // plugin handles its own reconnect logic. A TCP-open socket is not
@@ -1263,7 +1281,7 @@ If Design Systems Assistant MCP is not available, install it from: https://githu
1263
1281
  // REAL-TIME AWARENESS TOOLS (WebSocket-only)
1264
1282
  // ============================================================================
1265
1283
  // Tool: Get current user selection in Figma
1266
- this.server.tool("figma_get_selection", "Get the currently selected nodes in Figma. Returns node IDs, names, types, and dimensions. WebSocket-only — requires Desktop Bridge plugin. Use this to understand what the user is pointing at instead of asking them to describe it.", {
1284
+ server.tool("figma_get_selection", "Get the currently selected nodes in Figma. Returns node IDs, names, types, and dimensions. WebSocket-only — requires Desktop Bridge plugin. Use this to understand what the user is pointing at instead of asking them to describe it.", {
1267
1285
  verbose: z
1268
1286
  .boolean()
1269
1287
  .optional()
@@ -1356,7 +1374,7 @@ If Design Systems Assistant MCP is not available, install it from: https://githu
1356
1374
  }
1357
1375
  });
1358
1376
  // Tool: Get recent design changes
1359
- this.server.tool("figma_get_design_changes", "Get recent document changes detected in Figma. Returns buffered change events including which nodes changed, whether styles were modified, and change counts. WebSocket-only — events are captured via Desktop Bridge plugin. Use this to understand what changed since you last checked.", {
1377
+ server.tool("figma_get_design_changes", "Get recent document changes detected in Figma. Returns buffered change events including which nodes changed, whether styles were modified, and change counts. WebSocket-only — events are captured via Desktop Bridge plugin. Use this to understand what changed since you last checked.", {
1360
1378
  since: z
1361
1379
  .number()
1362
1380
  .optional()
@@ -1433,7 +1451,7 @@ If Design Systems Assistant MCP is not available, install it from: https://githu
1433
1451
  }
1434
1452
  });
1435
1453
  // Tool: List all open files connected via WebSocket
1436
- this.server.tool("figma_list_open_files", "List all Figma files currently connected via the Desktop Bridge plugin. Shows which files have the plugin open and which one is the active target for tool calls. Use figma_navigate to switch between files. WebSocket multi-client mode — each file with the Desktop Bridge plugin maintains its own connection.", {}, async () => {
1454
+ server.tool("figma_list_open_files", "List all Figma files currently connected via the Desktop Bridge plugin. Shows which files have the plugin open and which one is the active target for tool calls. Use figma_navigate to switch between files. WebSocket multi-client mode — each file with the Desktop Bridge plugin maintains its own connection.", {}, async () => {
1437
1455
  try {
1438
1456
  if (!this.wsServer?.isClientConnected()) {
1439
1457
  return {
@@ -1651,7 +1669,7 @@ If Design Systems Assistant MCP is not available, install it from: https://githu
1651
1669
  // (Previously interleaved with write tools in local.ts; restored after the
1652
1670
  // Phase-2 write-tools dedupe excised them along with the surrounding writes.)
1653
1671
  // ============================================================================
1654
- this.server.tool("figma_get_design_system_summary", "Get a compact overview of the design system. Returns categories, component counts, and token collection names WITHOUT full details. Use this first to understand what's available, then use figma_search_components to find specific components. This tool is optimized for minimal token usage.", {
1672
+ server.tool("figma_get_design_system_summary", "Get a compact overview of the design system. Returns categories, component counts, and token collection names WITHOUT full details. Use this first to understand what's available, then use figma_search_components to find specific components. This tool is optimized for minimal token usage.", {
1655
1673
  forceRefresh: z
1656
1674
  .boolean()
1657
1675
  .optional()
@@ -1855,7 +1873,7 @@ If Design Systems Assistant MCP is not available, install it from: https://githu
1855
1873
  }
1856
1874
  });
1857
1875
  // Tool 2: Search Components (~3000 tokens response max, paginated)
1858
- this.server.tool("figma_search_components", `Search for components by name, category, or description. Returns paginated results with component keys for instantiation. Automatically loads the design system cache if needed.
1876
+ server.tool("figma_search_components", `Search for components by name, category, or description. Returns paginated results with component keys for instantiation. Automatically loads the design system cache if needed.
1859
1877
 
1860
1878
  **NEW: Cross-file library search!** Pass a libraryFileKey or libraryFileUrl to search for components in a published shared library (different file). This uses the REST API and requires FIGMA_ACCESS_TOKEN.
1861
1879
 
@@ -2096,7 +2114,7 @@ Without libraryFileKey/libraryFileUrl, searches the currently open file (local c
2096
2114
  }
2097
2115
  });
2098
2116
  // Tool 3: Get Component Details (~500 tokens per component)
2099
- this.server.tool("figma_get_component_details", "Get full details for a specific component including all variants, properties, and keys needed for instantiation. Use the component key or name from figma_search_components.", {
2117
+ server.tool("figma_get_component_details", "Get full details for a specific component including all variants, properties, and keys needed for instantiation. Use the component key or name from figma_search_components.", {
2100
2118
  componentKey: z
2101
2119
  .string()
2102
2120
  .optional()
@@ -2206,7 +2224,7 @@ Without libraryFileKey/libraryFileUrl, searches the currently open file (local c
2206
2224
  }
2207
2225
  });
2208
2226
  // Tool 4: Get Token Values (~2000 tokens response max)
2209
- this.server.tool("figma_get_token_values", "Get actual values for design tokens (colors, spacing, etc). Use after figma_get_design_system_summary to get specific token values for implementation.", {
2227
+ server.tool("figma_get_token_values", "Get actual values for design tokens (colors, spacing, etc). Use after figma_get_design_system_summary to get specific token values for implementation.", {
2210
2228
  type: z
2211
2229
  .enum(["colors", "spacing", "all"])
2212
2230
  .optional()
@@ -2299,7 +2317,7 @@ Without libraryFileKey/libraryFileUrl, searches the currently open file (local c
2299
2317
  }
2300
2318
  });
2301
2319
  // Tool 5: Instantiate Component
2302
- this.server.tool("figma_get_library_components", `Discover published components from a shared/team library file.
2320
+ server.tool("figma_get_library_components", `Discover published components from a shared/team library file.
2303
2321
 
2304
2322
  **USE THIS when you need to use components from a published design system library** (a different file than the one currently open). This bridges the gap between library discovery and instantiation.
2305
2323
 
@@ -2569,18 +2587,18 @@ Without libraryFileKey/libraryFileUrl, searches the currently open file (local c
2569
2587
  // Register all write/manipulation tools (figma_execute, variable CRUD, node mutations,
2570
2588
  // design-token setup, accessibility audits, etc.). Sourced from src/core/write-tools.ts
2571
2589
  // so local mode and cloud mode share the same 30 implementations — no risk of drift.
2572
- registerWriteTools(this.server, () => this.getDesktopConnector());
2590
+ registerWriteTools(server, () => this.getDesktopConnector());
2573
2591
  // Register cross-file tools (figma_execute_across_files) — run the same
2574
2592
  // code against every (or a chosen subset of) currently connected files
2575
2593
  // concurrently. Local mode only, needs direct wsServer access for
2576
2594
  // getConnectedFiles(), so it isn't part of the connector abstraction.
2577
- registerMultiFileTools(this.server, () => this.wsServer, () => this.getDesktopConnector());
2595
+ registerMultiFileTools(server, () => this.wsServer, () => this.getDesktopConnector());
2578
2596
  // Register token sync tools — figma_export_tokens and figma_import_tokens.
2579
2597
  // Replace Style Dictionary and Tokens Studio's export pipeline for the
2580
2598
  // popular styling methods (DTCG canonical — legacy + 2025.10 dialects —
2581
2599
  // plus CSS/Tailwind/SCSS/TS/JSON/Style Dictionary/Tokens Studio, all
2582
2600
  // derived from a single internal token model).
2583
- registerTokensTools(this.server, () => this.getDesktopConnector(), {
2601
+ registerTokensTools(server, () => this.getDesktopConnector(), {
2584
2602
  // Lets figma_export_tokens report WHICH connected file it read from —
2585
2603
  // the bridge reads the active file, which may not be the intended one.
2586
2604
  resolveFileName: (fileKey) => this.wsServer
@@ -2593,18 +2611,18 @@ Without libraryFileKey/libraryFileUrl, searches the currently open file (local c
2593
2611
  // these read/write the local filesystem, which Cloudflare Workers
2594
2612
  // cannot (registerMultiFileTools precedent — never registered in
2595
2613
  // src/index.ts, so no Cloud Mode silent no-op is possible).
2596
- registerDesignSystemExtractionTools(this.server);
2614
+ registerDesignSystemExtractionTools(server);
2597
2615
  // Register Figma API tools (Tools 8-11)
2598
- registerFigmaAPITools(this.server, () => this.getFigmaAPI(), () => this.getCurrentFileUrl(), this.variablesCache, // Pass cache for efficient variable queries
2616
+ registerFigmaAPITools(server, () => this.getFigmaAPI(), () => this.getCurrentFileUrl(), this.variablesCache, // Pass cache for efficient variable queries
2599
2617
  undefined, // options (use default)
2600
2618
  () => this.getDesktopConnector());
2601
2619
  // Register Design-Code Parity & Documentation tools
2602
- registerDesignCodeTools(this.server, () => this.getFigmaAPI(), () => this.getCurrentFileUrl(), this.variablesCache, undefined, // options
2620
+ registerDesignCodeTools(server, () => this.getFigmaAPI(), () => this.getCurrentFileUrl(), this.variablesCache, undefined, // options
2603
2621
  () => this.getDesktopConnector());
2604
2622
  // Register Comment tools
2605
- registerCommentTools(this.server, () => this.getFigmaAPI(), () => this.getCurrentFileUrl());
2623
+ registerCommentTools(server, () => this.getFigmaAPI(), () => this.getCurrentFileUrl());
2606
2624
  // Register Version History tools
2607
- registerVersionTools(this.server, () => this.getFigmaAPI(), () => this.getCurrentFileUrl(), undefined, // options
2625
+ registerVersionTools(server, () => this.getFigmaAPI(), () => this.getCurrentFileUrl(), undefined, // options
2608
2626
  () => {
2609
2627
  // Selection fallback for blame/diff/changelog tools
2610
2628
  const sel = this.wsServer?.getCurrentSelection();
@@ -2619,20 +2637,20 @@ Without libraryFileKey/libraryFileUrl, searches the currently open file (local c
2619
2637
  return this.wsServer.getMetadataChanges(opts);
2620
2638
  });
2621
2639
  // Register Design System Kit tool
2622
- registerDesignSystemTools(this.server, () => this.getFigmaAPI(), () => this.getCurrentFileUrl(), this.variablesCache, undefined, // options (use default)
2640
+ registerDesignSystemTools(server, () => this.getFigmaAPI(), () => this.getCurrentFileUrl(), this.variablesCache, undefined, // options (use default)
2623
2641
  () => this.getDesktopConnector());
2624
2642
  // Register Library Tools (key-based component inspection across shared libraries)
2625
- registerLibraryTools(this.server, () => this.getFigmaAPI());
2643
+ registerLibraryTools(server, () => this.getFigmaAPI());
2626
2644
  // Register Library Variable Tools (Plugin-API based — list + import variables
2627
2645
  // from subscribed team libraries; works on every Figma plan, no Enterprise needed)
2628
- registerLibraryVariableTools(this.server, () => this.getDesktopConnector());
2646
+ registerLibraryVariableTools(server, () => this.getDesktopConnector());
2629
2647
  // Register code-side accessibility scanning (axe-core + JSDOM)
2630
- registerAccessibilityTools(this.server);
2648
+ registerAccessibilityTools(server);
2631
2649
  // Register figma_diagnose — designer-readable health check + cross-MCP disambiguator.
2632
2650
  // This is the first tool to point a confused user at: it self-identifies the server,
2633
2651
  // reports plugin/token state in plain language, and explicitly disclaims any
2634
2652
  // token/OAuth error that may have been emitted by a different Figma-related MCP.
2635
- registerDiagnoseTool(this.server, {
2653
+ registerDiagnoseTool(server, {
2636
2654
  mode: "local",
2637
2655
  getServerVersion: () => {
2638
2656
  try {
@@ -2666,17 +2684,17 @@ Without libraryFileKey/libraryFileUrl, searches the currently open file (local c
2666
2684
  },
2667
2685
  });
2668
2686
  // Register Annotation tools (read/write design annotations via Desktop Bridge)
2669
- registerAnnotationTools(this.server, () => this.getDesktopConnector());
2687
+ registerAnnotationTools(server, () => this.getDesktopConnector());
2670
2688
  // Register Deep Component tools (full Plugin API tree extraction for code generation)
2671
- registerDeepComponentTools(this.server, () => this.getDesktopConnector());
2689
+ registerDeepComponentTools(server, () => this.getDesktopConnector());
2672
2690
  // Register FigJam-specific tools (sticky notes, connectors, tables, etc.)
2673
- registerFigJamTools(this.server, () => this.getDesktopConnector());
2691
+ registerFigJamTools(server, () => this.getDesktopConnector());
2674
2692
  // Register Figma Slides tools (slide management, transitions, content)
2675
- registerSlidesTools(this.server, () => this.getDesktopConnector());
2676
- registerSlotTools(this.server, () => this.getDesktopConnector());
2693
+ registerSlidesTools(server, () => this.getDesktopConnector());
2694
+ registerSlotTools(server, () => this.getDesktopConnector());
2677
2695
  // MCP Apps - gated behind ENABLE_MCP_APPS env var
2678
2696
  if (process.env.ENABLE_MCP_APPS === "true") {
2679
- registerTokenBrowserApp(this.server, async (fileUrl) => {
2697
+ registerTokenBrowserApp(server, async (fileUrl) => {
2680
2698
  const url = fileUrl || this.getCurrentFileUrl();
2681
2699
  if (!url) {
2682
2700
  throw new Error("No Figma file URL available. Either pass a fileUrl, call figma_navigate, or ensure the Desktop Bridge plugin is connected.");
@@ -3135,10 +3153,10 @@ Without libraryFileKey/libraryFileUrl, searches the currently open file (local c
3135
3153
  };
3136
3154
  // Always available — any MCP client can run the audit, no MCP Apps
3137
3155
  // support required.
3138
- registerDesignSystemAuditTool(this.server, fetchDesignSystemAuditData, () => this.getCurrentFileUrl());
3156
+ registerDesignSystemAuditTool(server, fetchDesignSystemAuditData, () => this.getCurrentFileUrl());
3139
3157
  // Visual dashboard app (MCP-Apps-capable hosts only).
3140
3158
  if (process.env.ENABLE_MCP_APPS === "true") {
3141
- registerDesignSystemDashboardApp(this.server, fetchDesignSystemAuditData,
3159
+ registerDesignSystemDashboardApp(server, fetchDesignSystemAuditData,
3142
3160
  // Pass getCurrentUrl so dashboard can track which file was audited
3143
3161
  () => this.getCurrentFileUrl());
3144
3162
  }
@@ -3322,12 +3340,13 @@ Without libraryFileKey/libraryFileUrl, searches the currently open file (local c
3322
3340
  logger.info("Checking Figma Desktop accessibility...");
3323
3341
  await this.checkFigmaDesktop();
3324
3342
  // Register all tools
3325
- this.registerTools();
3343
+ this.registerTools(this.server);
3326
3344
  // Create stdio transport
3327
3345
  const transport = new StdioServerTransport();
3328
3346
  // Connect server to transport
3329
3347
  await this.server.connect(transport);
3330
3348
  logger.info("MCP server started successfully on stdio transport");
3349
+ await this.startHttpEndpoint();
3331
3350
  // 🆕 AUTO-CONNECT: Start monitoring immediately if Figma Desktop is available
3332
3351
  // This enables "get latest logs" workflow without requiring manual setup
3333
3352
  // In WS-only mode, no auto-connect is needed — the Desktop Bridge plugin
@@ -3342,12 +3361,53 @@ Without libraryFileKey/libraryFileUrl, searches the currently open file (local c
3342
3361
  process.exit(1);
3343
3362
  }
3344
3363
  }
3364
+ /**
3365
+ * Serve MCP over Streamable HTTP as well, when FIGMA_MCP_HTTP_PORT is set.
3366
+ * A failure here (bad value, port taken) is reported but never stops stdio.
3367
+ */
3368
+ async startHttpEndpoint() {
3369
+ let port;
3370
+ try {
3371
+ port = parseHttpPort(process.env.FIGMA_MCP_HTTP_PORT);
3372
+ }
3373
+ catch (error) {
3374
+ this.httpStartupError = error instanceof Error ? error.message : String(error);
3375
+ logger.warn(this.httpStartupError);
3376
+ return;
3377
+ }
3378
+ if (port === null)
3379
+ return;
3380
+ try {
3381
+ this.httpEndpoint = await startLocalHttpTransport({
3382
+ port,
3383
+ token: process.env.FIGMA_MCP_HTTP_TOKEN || undefined,
3384
+ createServer: () => {
3385
+ const server = this.createMcpServer();
3386
+ this.registerTools(server);
3387
+ return server;
3388
+ },
3389
+ });
3390
+ setAdvertisedMcpHttpUrl(this.httpEndpoint.url);
3391
+ console.error(`[figma-console-mcp] MCP over HTTP: ${this.httpEndpoint.url}`);
3392
+ }
3393
+ catch (error) {
3394
+ const code = error?.code;
3395
+ this.httpStartupError = code === "EADDRINUSE"
3396
+ ? `Port ${port} is already in use, so the HTTP endpoint is off (stdio still works). Another figma-console-mcp may already serve it: check its port file, or pick another FIGMA_MCP_HTTP_PORT.`
3397
+ : `HTTP endpoint failed to start on port ${port}: ${error instanceof Error ? error.message : String(error)}`;
3398
+ logger.warn({ port, code }, this.httpStartupError);
3399
+ }
3400
+ }
3345
3401
  /**
3346
3402
  * Cleanup and shutdown
3347
3403
  */
3348
3404
  async shutdown() {
3349
3405
  logger.info("Shutting down MCP server...");
3350
3406
  try {
3407
+ if (this.httpEndpoint) {
3408
+ await this.httpEndpoint.close();
3409
+ this.httpEndpoint = null;
3410
+ }
3351
3411
  // Stop heartbeat timer
3352
3412
  if (this.wsHeartbeatTimer) {
3353
3413
  clearInterval(this.wsHeartbeatTimer);