figma-console-mcp 1.7.0 → 1.9.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 (49) hide show
  1. package/README.md +81 -52
  2. package/dist/cloudflare/core/comment-tools.js +292 -0
  3. package/dist/cloudflare/core/design-code-tools.js +2 -2
  4. package/dist/cloudflare/core/figma-api.js +34 -0
  5. package/dist/cloudflare/core/figma-connector.js +8 -0
  6. package/dist/cloudflare/core/figma-desktop-connector.js +69 -2
  7. package/dist/cloudflare/core/figma-tools.js +118 -98
  8. package/dist/cloudflare/core/websocket-connector.js +235 -0
  9. package/dist/cloudflare/core/websocket-server.js +603 -0
  10. package/dist/cloudflare/index.js +9 -6
  11. package/dist/core/comment-tools.d.ts +11 -0
  12. package/dist/core/comment-tools.d.ts.map +1 -0
  13. package/dist/core/comment-tools.js +293 -0
  14. package/dist/core/comment-tools.js.map +1 -0
  15. package/dist/core/design-code-tools.js +2 -2
  16. package/dist/core/design-code-tools.js.map +1 -1
  17. package/dist/core/figma-api.d.ts +23 -0
  18. package/dist/core/figma-api.d.ts.map +1 -1
  19. package/dist/core/figma-api.js +34 -0
  20. package/dist/core/figma-api.js.map +1 -1
  21. package/dist/core/figma-connector.d.ts +47 -0
  22. package/dist/core/figma-connector.d.ts.map +1 -0
  23. package/dist/core/figma-connector.js +9 -0
  24. package/dist/core/figma-connector.js.map +1 -0
  25. package/dist/core/figma-desktop-connector.d.ts +14 -1
  26. package/dist/core/figma-desktop-connector.d.ts.map +1 -1
  27. package/dist/core/figma-desktop-connector.js +69 -2
  28. package/dist/core/figma-desktop-connector.js.map +1 -1
  29. package/dist/core/figma-tools.d.ts +1 -1
  30. package/dist/core/figma-tools.d.ts.map +1 -1
  31. package/dist/core/figma-tools.js +118 -98
  32. package/dist/core/figma-tools.js.map +1 -1
  33. package/dist/core/websocket-connector.d.ts +53 -0
  34. package/dist/core/websocket-connector.d.ts.map +1 -0
  35. package/dist/core/websocket-connector.js +236 -0
  36. package/dist/core/websocket-connector.js.map +1 -0
  37. package/dist/core/websocket-server.d.ts +183 -0
  38. package/dist/core/websocket-server.d.ts.map +1 -0
  39. package/dist/core/websocket-server.js +604 -0
  40. package/dist/core/websocket-server.js.map +1 -0
  41. package/dist/local.d.ts +11 -3
  42. package/dist/local.d.ts.map +1 -1
  43. package/dist/local.js +847 -276
  44. package/dist/local.js.map +1 -1
  45. package/figma-desktop-bridge/README.md +30 -16
  46. package/figma-desktop-bridge/code.js +195 -3
  47. package/figma-desktop-bridge/manifest.json +4 -1
  48. package/figma-desktop-bridge/ui.html +229 -0
  49. package/package.json +6 -4
@@ -45,6 +45,9 @@ export class FigmaDesktopConnector {
45
45
  async initialize() {
46
46
  logger.info('Figma Desktop connector initialized (using Puppeteer Worker API)');
47
47
  }
48
+ getTransportType() {
49
+ return 'cdp';
50
+ }
48
51
  /**
49
52
  * Execute code in Figma's plugin context where the figma API is available
50
53
  * Uses Puppeteer's direct worker access instead of CDP context enumeration
@@ -707,7 +710,18 @@ export class FigmaDesktopConnector {
707
710
  logger.info({ variableId, newName }, 'Renaming variable via plugin UI');
708
711
  const frame = await this.findPluginUIFrame();
709
712
  try {
710
- const result = await frame.evaluate(`window.renameVariable(${JSON.stringify(variableId)}, ${JSON.stringify(newName)})`);
713
+ // Look up old name from cached variables data, then rename — single CDP roundtrip
714
+ const result = await frame.evaluate(`(async () => {
715
+ var oldName;
716
+ var vars = window.__figmaVariablesData;
717
+ if (vars && vars.variables) {
718
+ var v = vars.variables.find(function(v) { return v.id === ${JSON.stringify(variableId)}; });
719
+ if (v) oldName = v.name;
720
+ }
721
+ var result = await window.renameVariable(${JSON.stringify(variableId)}, ${JSON.stringify(newName)});
722
+ if (oldName) result.oldName = oldName;
723
+ return result;
724
+ })()`);
711
725
  logger.info({ success: result.success, oldName: result.oldName, newName: result.variable?.name }, 'Variable renamed');
712
726
  await this.logToFigmaConsole((oldN, newN) => {
713
727
  console.log(`[DESKTOP_CONNECTOR] ✅ Variable renamed from "${oldN}" to "${newN}"`);
@@ -773,7 +787,25 @@ export class FigmaDesktopConnector {
773
787
  logger.info({ collectionId, modeId, newName }, 'Renaming mode via plugin UI');
774
788
  const frame = await this.findPluginUIFrame();
775
789
  try {
776
- const result = await frame.evaluate(`window.renameMode(${JSON.stringify(collectionId)}, ${JSON.stringify(modeId)}, ${JSON.stringify(newName)})`);
790
+ // Look up old mode name from cached variables data, then rename — single CDP roundtrip
791
+ const result = await frame.evaluate(`(async () => {
792
+ var oldName;
793
+ var vars = window.__figmaVariablesData;
794
+ var colls = vars && (vars.variableCollections || vars.collections);
795
+ if (colls) {
796
+ for (var i = 0; i < colls.length; i++) {
797
+ var c = colls[i];
798
+ if (c.id === ${JSON.stringify(collectionId)}) {
799
+ var mode = c.modes.find(function(m) { return m.modeId === ${JSON.stringify(modeId)}; });
800
+ if (mode) oldName = mode.name;
801
+ break;
802
+ }
803
+ }
804
+ }
805
+ var result = await window.renameMode(${JSON.stringify(collectionId)}, ${JSON.stringify(modeId)}, ${JSON.stringify(newName)});
806
+ if (oldName) result.oldName = oldName;
807
+ return result;
808
+ })()`);
777
809
  logger.info({ success: result.success, oldName: result.oldName, newName }, 'Mode renamed');
778
810
  await this.logToFigmaConsole((oldN, newN) => {
779
811
  console.log(`[DESKTOP_CONNECTOR] ✅ Mode renamed from "${oldN}" to "${newN}"`);
@@ -1080,5 +1112,40 @@ export class FigmaDesktopConnector {
1080
1112
  throw error;
1081
1113
  }
1082
1114
  }
1115
+ // ============================================================================
1116
+ // SCREENSHOT & INSTANCE PROPERTIES (via plugin UI)
1117
+ // ============================================================================
1118
+ /**
1119
+ * Capture screenshot via plugin's exportAsync
1120
+ */
1121
+ async captureScreenshot(nodeId, options) {
1122
+ logger.info({ nodeId, options }, 'Capturing screenshot via plugin UI');
1123
+ const frame = await this.findPluginUIFrame();
1124
+ try {
1125
+ const result = await frame.evaluate(`window.captureScreenshot(${JSON.stringify(nodeId)}, ${JSON.stringify(options || {})})`);
1126
+ logger.info({ success: result.success }, 'Screenshot captured');
1127
+ return result;
1128
+ }
1129
+ catch (error) {
1130
+ logger.error({ error, nodeId }, 'Capture screenshot failed');
1131
+ throw error;
1132
+ }
1133
+ }
1134
+ /**
1135
+ * Set component instance properties
1136
+ */
1137
+ async setInstanceProperties(nodeId, properties) {
1138
+ logger.info({ nodeId, properties: Object.keys(properties || {}) }, 'Setting instance properties via plugin UI');
1139
+ const frame = await this.findPluginUIFrame();
1140
+ try {
1141
+ const result = await frame.evaluate(`window.setInstanceProperties(${JSON.stringify(nodeId)}, ${JSON.stringify(properties)})`);
1142
+ logger.info({ success: result.success }, 'Instance properties set');
1143
+ return result;
1144
+ }
1145
+ catch (error) {
1146
+ logger.error({ error, nodeId }, 'Set instance properties failed');
1147
+ throw error;
1148
+ }
1149
+ }
1083
1150
  }
1084
1151
  FigmaDesktopConnector.DEBUG = process.env.DEBUG === '1' || process.env.DEBUG === 'true';
@@ -574,7 +574,7 @@ function resolveVariableAliases(variables, allVariablesMap, collectionsMap) {
574
574
  /**
575
575
  * Register Figma API tools with the MCP server
576
576
  */
577
- export function registerFigmaAPITools(server, getFigmaAPI, getCurrentUrl, getConsoleMonitor, getBrowserManager, ensureInitialized, variablesCache, options) {
577
+ export function registerFigmaAPITools(server, getFigmaAPI, getCurrentUrl, getConsoleMonitor, getBrowserManager, ensureInitialized, variablesCache, options, getDesktopConnector) {
578
578
  const isRemoteMode = options?.isRemoteMode ?? false;
579
579
  // Tool 8: Get File Data (General Purpose)
580
580
  // NOTE: For specific use cases, consider using specialized tools:
@@ -585,7 +585,7 @@ export function registerFigmaAPITools(server, getFigmaAPI, getCurrentUrl, getCon
585
585
  .string()
586
586
  .url()
587
587
  .optional()
588
- .describe("Figma file URL (e.g., https://figma.com/design/abc123). REQUIRED unless figma_navigate was already called. If not provided, ask the user to share their Figma file URL (they can copy it from Figma Desktop via right-click → 'Copy link')."),
588
+ .describe("Figma file URL (e.g., https://figma.com/design/abc123). Auto-detected from CDP browser or WebSocket Desktop Bridge connection. Only required if neither is connected."),
589
589
  depth: z
590
590
  .number()
591
591
  .min(0)
@@ -626,7 +626,7 @@ export function registerFigmaAPITools(server, getFigmaAPI, getCurrentUrl, getCon
626
626
  // Use provided URL or current URL from browser
627
627
  const url = fileUrl || getCurrentUrl();
628
628
  if (!url) {
629
- throw new Error("No Figma file URL provided. Either pass fileUrl parameter or call figma_navigate first.");
629
+ throw new Error("No Figma file URL available. Pass the fileUrl parameter, call figma_navigate (CDP mode), or ensure the Desktop Bridge plugin is connected (WebSocket mode).");
630
630
  }
631
631
  const fileKey = extractFileKey(url);
632
632
  if (!fileKey) {
@@ -786,7 +786,7 @@ export function registerFigmaAPITools(server, getFigmaAPI, getCurrentUrl, getCon
786
786
  .string()
787
787
  .url()
788
788
  .optional()
789
- .describe("Figma file URL (e.g., https://figma.com/design/abc123). REQUIRED unless figma_navigate was already called. If not provided, ask the user to share their Figma file URL (they can copy it from Figma Desktop via right-click → 'Copy link')."),
789
+ .describe("Figma file URL (e.g., https://figma.com/design/abc123). Auto-detected from CDP browser or WebSocket Desktop Bridge connection. Only required if neither is connected."),
790
790
  includePublished: z
791
791
  .boolean()
792
792
  .optional()
@@ -895,8 +895,8 @@ export function registerFigmaAPITools(server, getFigmaAPI, getCurrentUrl, getCon
895
895
  {
896
896
  type: "text",
897
897
  text: JSON.stringify({
898
- error: "No Figma file URL provided",
899
- message: "Either pass fileUrl parameter or call figma_navigate first."
898
+ error: "No Figma file URL available",
899
+ message: "Pass the fileUrl parameter, call figma_navigate (CDP mode), or ensure the Desktop Bridge plugin is connected (WebSocket mode)."
900
900
  }),
901
901
  },
902
902
  ],
@@ -1447,16 +1447,17 @@ export function registerFigmaAPITools(server, getFigmaAPI, getCurrentUrl, getCon
1447
1447
  }
1448
1448
  }
1449
1449
  // FALLBACK: Try Desktop connection (when no token available OR as secondary fallback)
1450
- // Ensure browser manager is initialized
1451
- if (ensureInitialized && !parseFromConsole && (!hasToken || !restApiSucceeded)) {
1452
- logger.info("Calling ensureInitialized to initialize browser manager");
1450
+ // Only call ensureInitialized for CDP path — skip when transport-agnostic connector exists
1451
+ if (ensureInitialized && !getDesktopConnector && !parseFromConsole && (!hasToken || !restApiSucceeded)) {
1452
+ logger.info("Calling ensureInitialized to initialize browser manager (CDP path)");
1453
1453
  await ensureInitialized();
1454
1454
  }
1455
1455
  const browserManager = getBrowserManager?.();
1456
- logger.info({ hasBrowserManager: !!browserManager, parseFromConsole, hasToken, restApiSucceeded }, "Desktop connection check");
1456
+ const hasDesktopConnection = !!getDesktopConnector || !!browserManager;
1457
+ logger.info({ hasBrowserManager: !!browserManager, hasDesktopConnector: !!getDesktopConnector, parseFromConsole, hasToken, restApiSucceeded }, "Desktop connection check");
1457
1458
  // Debug: Log why Desktop connection might be skipped
1458
- if (!browserManager) {
1459
- logger.error("Desktop connection skipped: browserManager is not available");
1459
+ if (!hasDesktopConnection) {
1460
+ logger.error("Desktop connection skipped: neither connector nor browserManager available");
1460
1461
  }
1461
1462
  else if (parseFromConsole) {
1462
1463
  logger.info("Desktop connection skipped: parseFromConsole is true");
@@ -1464,26 +1465,21 @@ export function registerFigmaAPITools(server, getFigmaAPI, getCurrentUrl, getCon
1464
1465
  else if (restApiSucceeded) {
1465
1466
  logger.info("Desktop connection skipped: REST API already succeeded");
1466
1467
  }
1467
- if (browserManager && !parseFromConsole && (!hasToken || !restApiSucceeded)) {
1468
+ if (hasDesktopConnection && !parseFromConsole && (!hasToken || !restApiSucceeded)) {
1468
1469
  try {
1469
1470
  logger.info({ fileKey }, "Attempting to get variables via Desktop connection");
1470
- // Import and use the Desktop connector
1471
- const { FigmaDesktopConnector } = await import('./figma-desktop-connector.js');
1472
- const page = await browserManager.getPage();
1473
- logger.info("Got page from browser manager");
1474
- // Log to browser console for MCP capture
1475
- await page.evaluate(() => {
1476
- console.log('[FIGMA_TOOLS] 🚀 Got page from browser manager, creating Desktop connector...');
1477
- });
1478
- const connector = new FigmaDesktopConnector(page);
1479
- await page.evaluate(() => {
1480
- console.log('[FIGMA_TOOLS] ✅ Desktop connector created, initializing...');
1481
- });
1482
- await connector.initialize();
1483
- logger.info("Desktop connector initialized, calling getVariablesFromPluginUI...");
1484
- await page.evaluate(() => {
1485
- console.log('[FIGMA_TOOLS] ✅ Desktop connector initialized, calling getVariablesFromPluginUI...');
1486
- });
1471
+ let connector;
1472
+ if (getDesktopConnector) {
1473
+ connector = await getDesktopConnector();
1474
+ }
1475
+ else {
1476
+ // Fallback: direct CDP connector (legacy path)
1477
+ const { FigmaDesktopConnector } = await import('./figma-desktop-connector.js');
1478
+ const page = await browserManager.getPage();
1479
+ connector = new FigmaDesktopConnector(page);
1480
+ await connector.initialize();
1481
+ }
1482
+ logger.info({ transport: connector.getTransportType?.() || 'unknown' }, "Desktop connector ready");
1487
1483
  const desktopResult = await connector.getVariablesFromPluginUI(fileKey);
1488
1484
  if (desktopResult.success && desktopResult.variables) {
1489
1485
  logger.info({
@@ -1841,7 +1837,7 @@ export function registerFigmaAPITools(server, getFigmaAPI, getCurrentUrl, getCon
1841
1837
  .string()
1842
1838
  .url()
1843
1839
  .optional()
1844
- .describe("Figma file URL (e.g., https://figma.com/design/abc123). REQUIRED unless figma_navigate was already called. If not provided, ask the user to share their Figma file URL (they can copy it from Figma Desktop via right-click → 'Copy link')."),
1840
+ .describe("Figma file URL (e.g., https://figma.com/design/abc123). Auto-detected from CDP browser or WebSocket Desktop Bridge connection. Only required if neither is connected."),
1845
1841
  nodeId: z
1846
1842
  .string()
1847
1843
  .describe("Component node ID (e.g., '123:456')"),
@@ -1858,7 +1854,7 @@ export function registerFigmaAPITools(server, getFigmaAPI, getCurrentUrl, getCon
1858
1854
  try {
1859
1855
  const url = fileUrl || getCurrentUrl();
1860
1856
  if (!url) {
1861
- throw new Error("No Figma file URL provided. Either pass fileUrl parameter or call figma_navigate first.");
1857
+ throw new Error("No Figma file URL available. Pass the fileUrl parameter, call figma_navigate (CDP mode), or ensure the Desktop Bridge plugin is connected (WebSocket mode).");
1862
1858
  }
1863
1859
  const fileKey = extractFileKey(url);
1864
1860
  if (!fileKey) {
@@ -1866,18 +1862,26 @@ export function registerFigmaAPITools(server, getFigmaAPI, getCurrentUrl, getCon
1866
1862
  }
1867
1863
  logger.info({ fileKey, nodeId, format, enrich }, "Fetching component data");
1868
1864
  // PRIORITY 1: Try Desktop Bridge plugin UI first (has reliable description field!)
1869
- if (getBrowserManager && ensureInitialized) {
1865
+ if (getDesktopConnector || (getBrowserManager && ensureInitialized)) {
1870
1866
  try {
1871
1867
  logger.info({ nodeId }, "Attempting to get component via Desktop Bridge plugin UI");
1872
- await ensureInitialized();
1873
- const browserManager = getBrowserManager();
1874
- if (!browserManager) {
1875
- throw new Error("Browser manager not available after initialization");
1868
+ let connector;
1869
+ if (getDesktopConnector) {
1870
+ connector = await getDesktopConnector();
1871
+ }
1872
+ else {
1873
+ // Fallback: direct CDP connector (legacy path)
1874
+ if (ensureInitialized)
1875
+ await ensureInitialized();
1876
+ const browserManager = getBrowserManager?.();
1877
+ if (!browserManager) {
1878
+ throw new Error("Browser manager not available after initialization");
1879
+ }
1880
+ const { FigmaDesktopConnector } = await import('./figma-desktop-connector.js');
1881
+ const page = await browserManager.getPage();
1882
+ connector = new FigmaDesktopConnector(page);
1883
+ await connector.initialize();
1876
1884
  }
1877
- const { FigmaDesktopConnector } = await import('./figma-desktop-connector.js');
1878
- const page = await browserManager.getPage();
1879
- const connector = new FigmaDesktopConnector(page);
1880
- await connector.initialize();
1881
1885
  const desktopResult = await connector.getComponentFromPluginUI(nodeId);
1882
1886
  if (desktopResult.success && desktopResult.component) {
1883
1887
  logger.info({
@@ -1969,7 +1973,7 @@ export function registerFigmaAPITools(server, getFigmaAPI, getCurrentUrl, getCon
1969
1973
  catch (apiError) {
1970
1974
  const errorMessage = apiError instanceof Error ? apiError.message : String(apiError);
1971
1975
  throw new Error(`Cannot retrieve component data. Both Desktop Bridge and REST API are unavailable.\n` +
1972
- `Desktop Bridge: ${getBrowserManager && ensureInitialized ? 'Failed (see logs above)' : 'Not available (local mode only)'}\n` +
1976
+ `Desktop Bridge: ${getDesktopConnector || (getBrowserManager && ensureInitialized) ? 'Failed (see logs above)' : 'Not available (local mode only)'}\n` +
1973
1977
  `REST API: ${errorMessage}\n\n` +
1974
1978
  `To fix:\n` +
1975
1979
  `1. Local mode: Set FIGMA_ACCESS_TOKEN environment variable, OR ensure Figma Desktop Bridge plugin is running\n` +
@@ -2073,7 +2077,7 @@ export function registerFigmaAPITools(server, getFigmaAPI, getCurrentUrl, getCon
2073
2077
  .string()
2074
2078
  .url()
2075
2079
  .optional()
2076
- .describe("Figma file URL (e.g., https://figma.com/design/abc123). REQUIRED unless figma_navigate was already called. If not provided, ask the user to share their Figma file URL (they can copy it from Figma Desktop via right-click → 'Copy link')."),
2080
+ .describe("Figma file URL (e.g., https://figma.com/design/abc123). Auto-detected from CDP browser or WebSocket Desktop Bridge connection. Only required if neither is connected."),
2077
2081
  verbosity: z
2078
2082
  .enum(["summary", "standard", "full"])
2079
2083
  .optional()
@@ -2111,7 +2115,7 @@ export function registerFigmaAPITools(server, getFigmaAPI, getCurrentUrl, getCon
2111
2115
  }
2112
2116
  const url = fileUrl || getCurrentUrl();
2113
2117
  if (!url) {
2114
- throw new Error("No Figma file URL provided. Either pass fileUrl parameter or call figma_navigate first.");
2118
+ throw new Error("No Figma file URL available. Pass the fileUrl parameter, call figma_navigate (CDP mode), or ensure the Desktop Bridge plugin is connected (WebSocket mode).");
2115
2119
  }
2116
2120
  const fileKey = extractFileKey(url);
2117
2121
  if (!fileKey) {
@@ -2218,7 +2222,7 @@ export function registerFigmaAPITools(server, getFigmaAPI, getCurrentUrl, getCon
2218
2222
  .string()
2219
2223
  .url()
2220
2224
  .optional()
2221
- .describe("Figma file URL (e.g., https://figma.com/design/abc123). REQUIRED unless figma_navigate was already called. If not provided, ask the user to share their Figma file URL (they can copy it from Figma Desktop via right-click → 'Copy link')."),
2225
+ .describe("Figma file URL (e.g., https://figma.com/design/abc123). Auto-detected from CDP browser or WebSocket Desktop Bridge connection. Only required if neither is connected."),
2222
2226
  nodeId: z
2223
2227
  .string()
2224
2228
  .describe("Component node ID to render as image (e.g., '695:313')"),
@@ -2252,7 +2256,7 @@ export function registerFigmaAPITools(server, getFigmaAPI, getCurrentUrl, getCon
2252
2256
  }
2253
2257
  const url = fileUrl || getCurrentUrl();
2254
2258
  if (!url) {
2255
- throw new Error("No Figma file URL provided. Either pass fileUrl parameter or call figma_navigate first.");
2259
+ throw new Error("No Figma file URL available. Pass the fileUrl parameter, call figma_navigate (CDP mode), or ensure the Desktop Bridge plugin is connected (WebSocket mode).");
2256
2260
  }
2257
2261
  const fileKey = extractFileKey(url);
2258
2262
  if (!fileKey) {
@@ -2260,8 +2264,8 @@ export function registerFigmaAPITools(server, getFigmaAPI, getCurrentUrl, getCon
2260
2264
  }
2261
2265
  logger.info({ fileKey, nodeId, scale, format }, "Rendering component image");
2262
2266
  // First, fetch the node to check if it's a COMPONENT_SET
2263
- const fileData = await api.getFile(fileKey, { ids: [nodeId] });
2264
- const node = fileData.nodes[nodeId]?.document;
2267
+ const fileData = await api.getNodes(fileKey, [nodeId]);
2268
+ const node = fileData.nodes?.[nodeId]?.document;
2265
2269
  if (!node) {
2266
2270
  throw new Error(`Node ${nodeId} not found in file ${fileKey}. Please verify the node ID is correct.`);
2267
2271
  }
@@ -2367,7 +2371,7 @@ export function registerFigmaAPITools(server, getFigmaAPI, getCurrentUrl, getCon
2367
2371
  }
2368
2372
  const url = fileUrl || getCurrentUrl();
2369
2373
  if (!url) {
2370
- throw new Error("No Figma file URL provided. Either pass fileUrl parameter or call figma_navigate first.");
2374
+ throw new Error("No Figma file URL available. Pass the fileUrl parameter, call figma_navigate (CDP mode), or ensure the Desktop Bridge plugin is connected (WebSocket mode).");
2371
2375
  }
2372
2376
  const fileKey = extractFileKey(url);
2373
2377
  if (!fileKey) {
@@ -2577,7 +2581,7 @@ export function registerFigmaAPITools(server, getFigmaAPI, getCurrentUrl, getCon
2577
2581
  }
2578
2582
  const url = fileUrl || getCurrentUrl();
2579
2583
  if (!url) {
2580
- throw new Error("No Figma file URL provided. Either pass fileUrl parameter or call figma_navigate first.");
2584
+ throw new Error("No Figma file URL available. Pass the fileUrl parameter, call figma_navigate (CDP mode), or ensure the Desktop Bridge plugin is connected (WebSocket mode).");
2581
2585
  }
2582
2586
  const fileKey = extractFileKey(url);
2583
2587
  if (!fileKey) {
@@ -2752,33 +2756,43 @@ export function registerFigmaAPITools(server, getFigmaAPI, getCurrentUrl, getCon
2752
2756
  .describe("Scale factor (default: 2 for 2x resolution)"),
2753
2757
  }, async ({ nodeId, format, scale }) => {
2754
2758
  try {
2755
- // Check for Desktop Bridge connection
2756
- const browserManager = getBrowserManager?.();
2757
- if (!browserManager) {
2758
- throw new Error("Desktop Bridge not available. To capture screenshots:\n" +
2759
- "1. Open your Figma file in Figma Desktop\n" +
2760
- "2. Install and run the 'Figma Console MCP' plugin\n" +
2761
- "3. Ensure the plugin shows 'MCP ready' status");
2762
- }
2763
- // Ensure browser is initialized
2764
- if (ensureInitialized) {
2765
- await ensureInitialized();
2766
- }
2767
- const page = await browserManager.getPage();
2768
2759
  logger.info({ nodeId, format, scale }, "Capturing screenshot via Desktop Bridge");
2769
- // Find the plugin UI frame and call the captureScreenshot function
2770
- const frames = page.frames();
2771
2760
  let result = null;
2772
- for (const frame of frames) {
2773
- try {
2774
- const hasFunction = await frame.evaluate('typeof window.captureScreenshot === "function"');
2775
- if (hasFunction) {
2776
- result = await frame.evaluate(`window.captureScreenshot(${JSON.stringify(nodeId)}, ${JSON.stringify({ format, scale })})`);
2777
- break;
2778
- }
2761
+ // Use the connector abstraction (supports both CDP and WebSocket)
2762
+ if (getDesktopConnector) {
2763
+ const connector = await getDesktopConnector();
2764
+ logger.info({ transport: connector.getTransportType?.() || 'unknown' }, "Screenshot via connector");
2765
+ result = await connector.captureScreenshot(nodeId || '', { format, scale });
2766
+ // Wrap in expected format only if connector returns raw data without a success flag
2767
+ if (result && typeof result.success === 'undefined' && result.image) {
2768
+ result = { success: true, image: result };
2769
+ }
2770
+ }
2771
+ // Legacy CDP fallback (only when no connector factory is available)
2772
+ if (!result && !getDesktopConnector) {
2773
+ const browserManager = getBrowserManager?.();
2774
+ if (!browserManager) {
2775
+ throw new Error("Desktop Bridge not available. To capture screenshots:\n" +
2776
+ "1. Open your Figma file in Figma Desktop\n" +
2777
+ "2. Install and run the 'Figma Console MCP' plugin\n" +
2778
+ "3. Ensure the plugin shows 'MCP ready' status");
2779
2779
  }
2780
- catch {
2781
- continue;
2780
+ if (ensureInitialized) {
2781
+ await ensureInitialized();
2782
+ }
2783
+ const page = await browserManager.getPage();
2784
+ const frames = page.frames();
2785
+ for (const frame of frames) {
2786
+ try {
2787
+ const hasFunction = await frame.evaluate('typeof window.captureScreenshot === "function"');
2788
+ if (hasFunction) {
2789
+ result = await frame.evaluate(`window.captureScreenshot(${JSON.stringify(nodeId || '')}, ${JSON.stringify({ format, scale })})`);
2790
+ break;
2791
+ }
2792
+ }
2793
+ catch {
2794
+ continue;
2795
+ }
2782
2796
  }
2783
2797
  }
2784
2798
  if (!result) {
@@ -2850,33 +2864,39 @@ export function registerFigmaAPITools(server, getFigmaAPI, getCurrentUrl, getCon
2850
2864
  "The tool automatically handles the #nodeId suffix for TEXT/BOOLEAN/INSTANCE_SWAP properties."),
2851
2865
  }, async ({ nodeId, properties }) => {
2852
2866
  try {
2853
- // Check for Desktop Bridge connection
2854
- const browserManager = getBrowserManager?.();
2855
- if (!browserManager) {
2856
- throw new Error("Desktop Bridge not available. To set instance properties:\n" +
2857
- "1. Open your Figma file in Figma Desktop\n" +
2858
- "2. Install and run the 'Figma Console MCP' plugin\n" +
2859
- "3. Ensure the plugin shows 'MCP ready' status");
2860
- }
2861
- // Ensure browser is initialized
2862
- if (ensureInitialized) {
2863
- await ensureInitialized();
2864
- }
2865
- const page = await browserManager.getPage();
2866
2867
  logger.info({ nodeId, properties: Object.keys(properties) }, "Setting instance properties via Desktop Bridge");
2867
- // Find the plugin UI frame and call the setInstanceProperties function
2868
- const frames = page.frames();
2869
2868
  let result = null;
2870
- for (const frame of frames) {
2871
- try {
2872
- const hasFunction = await frame.evaluate('typeof window.setInstanceProperties === "function"');
2873
- if (hasFunction) {
2874
- result = await frame.evaluate(`window.setInstanceProperties(${JSON.stringify(nodeId)}, ${JSON.stringify(properties)})`);
2875
- break;
2876
- }
2869
+ // Use the connector abstraction (supports both CDP and WebSocket)
2870
+ if (getDesktopConnector) {
2871
+ const connector = await getDesktopConnector();
2872
+ logger.info({ transport: connector.getTransportType?.() || 'unknown' }, "Instance properties via connector");
2873
+ result = await connector.setInstanceProperties(nodeId, properties);
2874
+ }
2875
+ // Legacy CDP fallback (only when no connector factory is available)
2876
+ if (!result && !getDesktopConnector) {
2877
+ const browserManager = getBrowserManager?.();
2878
+ if (!browserManager) {
2879
+ throw new Error("Desktop Bridge not available. To set instance properties:\n" +
2880
+ "1. Open your Figma file in Figma Desktop\n" +
2881
+ "2. Install and run the 'Figma Console MCP' plugin\n" +
2882
+ "3. Ensure the plugin shows 'MCP ready' status");
2883
+ }
2884
+ if (ensureInitialized) {
2885
+ await ensureInitialized();
2877
2886
  }
2878
- catch {
2879
- continue;
2887
+ const page = await browserManager.getPage();
2888
+ const frames = page.frames();
2889
+ for (const frame of frames) {
2890
+ try {
2891
+ const hasFunction = await frame.evaluate('typeof window.setInstanceProperties === "function"');
2892
+ if (hasFunction) {
2893
+ result = await frame.evaluate(`window.setInstanceProperties(${JSON.stringify(nodeId)}, ${JSON.stringify(properties)})`);
2894
+ break;
2895
+ }
2896
+ }
2897
+ catch {
2898
+ continue;
2899
+ }
2880
2900
  }
2881
2901
  }
2882
2902
  if (!result) {