@atezer/figma-mcp-bridge 1.2.0 → 1.2.2

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 (33) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/README.md +99 -9
  3. package/dist/cloudflare/cloud-cors.js +40 -0
  4. package/dist/cloudflare/cloud-mode-kv.js +86 -0
  5. package/dist/cloudflare/cloud-mode-routes.js +97 -0
  6. package/dist/cloudflare/cloud-relay-session.js +141 -0
  7. package/dist/cloudflare/core/config.js +1 -1
  8. package/dist/cloudflare/core/figma-url.js +48 -0
  9. package/dist/cloudflare/core/plugin-bridge-connector.js +52 -43
  10. package/dist/cloudflare/core/plugin-bridge-server.js +211 -87
  11. package/dist/cloudflare/index.js +243 -4
  12. package/dist/core/config.js +1 -1
  13. package/dist/core/config.js.map +1 -1
  14. package/dist/core/figma-url.d.ts +10 -0
  15. package/dist/core/figma-url.d.ts.map +1 -0
  16. package/dist/core/figma-url.js +49 -0
  17. package/dist/core/figma-url.js.map +1 -0
  18. package/dist/core/plugin-bridge-connector.d.ts +6 -1
  19. package/dist/core/plugin-bridge-connector.d.ts.map +1 -1
  20. package/dist/core/plugin-bridge-connector.js +52 -43
  21. package/dist/core/plugin-bridge-connector.js.map +1 -1
  22. package/dist/core/plugin-bridge-server.d.ts +47 -14
  23. package/dist/core/plugin-bridge-server.d.ts.map +1 -1
  24. package/dist/core/plugin-bridge-server.js +211 -87
  25. package/dist/core/plugin-bridge-server.js.map +1 -1
  26. package/dist/local-plugin-only.d.ts.map +1 -1
  27. package/dist/local-plugin-only.js +163 -43
  28. package/dist/local-plugin-only.js.map +1 -1
  29. package/f-mcp-plugin/README.md +13 -5
  30. package/f-mcp-plugin/code.js +216 -2
  31. package/f-mcp-plugin/manifest.json +6 -2
  32. package/f-mcp-plugin/ui.html +694 -213
  33. package/package.json +7 -6
@@ -19,8 +19,15 @@ console.error = function() { _pushLog('error', arguments); _origError.apply(cons
19
19
 
20
20
  console.log('🌉 [F-MCP ATezer Bridge] Plugin loaded and ready');
21
21
 
22
- // Show minimal UI - compact status indicator
23
- figma.showUI(__html__, { width: 200, height: 56, visible: true, themeColors: true });
22
+ // Start compact; UI asks for dynamic resize based on content.
23
+ figma.showUI(__html__, { width: 280, height: 96, visible: true, themeColors: true });
24
+
25
+ // Send file identity to UI so it can include it in the WebSocket handshake
26
+ figma.ui.postMessage({
27
+ type: 'FILE_IDENTITY',
28
+ fileKey: figma.fileKey || null,
29
+ fileName: figma.root.name || null
30
+ });
24
31
 
25
32
  // Immediately fetch and send variables data to UI
26
33
  (async () => {
@@ -165,6 +172,18 @@ function hexToFigmaRGB(hex) {
165
172
 
166
173
  // Listen for requests from UI (e.g., component data requests, write operations)
167
174
  figma.ui.onmessage = async (msg) => {
175
+ if (msg.type === 'RESIZE_UI') {
176
+ try {
177
+ var requestedWidth = Number(msg.width);
178
+ var requestedHeight = Number(msg.height);
179
+ var width = Math.max(220, Math.min(520, Math.round(isNaN(requestedWidth) ? 280 : requestedWidth)));
180
+ var height = Math.max(72, Math.min(420, Math.round(isNaN(requestedHeight) ? 96 : requestedHeight)));
181
+ figma.ui.resize(width, height);
182
+ } catch (error) {
183
+ console.warn('🌉 [F-MCP ATezer Bridge] UI resize failed:', error && error.message ? error.message : String(error));
184
+ }
185
+ return;
186
+ }
168
187
 
169
188
  function rgbaToHex(color) {
170
189
  if (!color || typeof color !== 'object') return null;
@@ -1146,6 +1165,201 @@ figma.ui.onmessage = async (msg) => {
1146
1165
  }
1147
1166
  }
1148
1167
 
1168
+ // ============================================================================
1169
+ // SEARCH_LIBRARY_ASSETS — enabled library variables + file components (query filter)
1170
+ // ============================================================================
1171
+ else if (msg.type === 'SEARCH_LIBRARY_ASSETS') {
1172
+ try {
1173
+ var q = (msg.query || '').toLowerCase().trim();
1174
+ var assetTypes = msg.assetTypes && msg.assetTypes.length ? msg.assetTypes : ['variables', 'components'];
1175
+ var limit = Math.min(Math.max(parseInt(msg.limit, 10) || 25, 1), 80);
1176
+ var currentPageOnly = msg.currentPageOnly !== false;
1177
+ var out = {
1178
+ success: true,
1179
+ query: msg.query || '',
1180
+ variables: [],
1181
+ components: [],
1182
+ componentSets: [],
1183
+ librariesScanned: [],
1184
+ notes: []
1185
+ };
1186
+
1187
+ if (assetTypes.indexOf('variables') >= 0 && figma.teamLibrary && typeof figma.teamLibrary.getAvailableLibraryVariableCollectionsAsync === 'function') {
1188
+ try {
1189
+ var cols = await figma.teamLibrary.getAvailableLibraryVariableCollectionsAsync();
1190
+ for (var ci = 0; ci < cols.length && out.variables.length < limit; ci++) {
1191
+ var col = cols[ci];
1192
+ out.librariesScanned.push({ kind: 'variableCollection', name: col.name || '', key: col.key || '' });
1193
+ var libVars = await figma.teamLibrary.getVariablesInLibraryCollectionAsync(col.key);
1194
+ for (var vi = 0; vi < libVars.length && out.variables.length < limit; vi++) {
1195
+ var lv = libVars[vi];
1196
+ var nm = (lv.name || '').toLowerCase();
1197
+ if (!q || nm.indexOf(q) >= 0) {
1198
+ out.variables.push({
1199
+ name: lv.name,
1200
+ key: lv.key,
1201
+ resolvedType: lv.resolvedType,
1202
+ libraryCollectionKey: col.key,
1203
+ libraryCollectionName: col.name
1204
+ });
1205
+ }
1206
+ }
1207
+ }
1208
+ } catch (ve) {
1209
+ out.variableLibraryError = ve && ve.message ? ve.message : String(ve);
1210
+ }
1211
+ } else if (assetTypes.indexOf('variables') >= 0) {
1212
+ out.notes.push('Library variables skipped: teamLibrary API unavailable or permission missing (manifest permissions: teamlibrary).');
1213
+ }
1214
+
1215
+ if (assetTypes.indexOf('components') >= 0) {
1216
+ var components = [];
1217
+ var componentSets = [];
1218
+ var hitLimit = false;
1219
+ function extractComponentData(node, fromSet) {
1220
+ return {
1221
+ id: node.id,
1222
+ name: node.name,
1223
+ key: node.key,
1224
+ description: node.description || null,
1225
+ fromLibraryComponentSet: !!fromSet
1226
+ };
1227
+ }
1228
+ function extractComponentSetData(node) {
1229
+ return {
1230
+ id: node.id,
1231
+ name: node.name,
1232
+ key: node.key,
1233
+ description: node.description || null,
1234
+ variantCount: node.children ? node.children.length : 0
1235
+ };
1236
+ }
1237
+ async function processNodeList(nodes) {
1238
+ for (var i = 0; i < nodes.length && !hitLimit; i++) {
1239
+ if (components.length + componentSets.length >= limit) {
1240
+ hitLimit = true;
1241
+ break;
1242
+ }
1243
+ var node = nodes[i];
1244
+ if (!node) continue;
1245
+ if (node.loadAsync) await node.loadAsync();
1246
+ var nname = (node.name || '').toLowerCase();
1247
+ var ndesc = (node.description || '').toLowerCase();
1248
+ var match = !q || nname.indexOf(q) >= 0 || ndesc.indexOf(q) >= 0;
1249
+ if (node.type === 'COMPONENT_SET') {
1250
+ if (match) componentSets.push(extractComponentSetData(node));
1251
+ } else if (node.type === 'COMPONENT') {
1252
+ if (!node.parent || node.parent.type !== 'COMPONENT_SET') {
1253
+ if (match) components.push(extractComponentData(node, false));
1254
+ }
1255
+ }
1256
+ }
1257
+ }
1258
+ if (currentPageOnly) {
1259
+ var page = figma.currentPage;
1260
+ if (page) {
1261
+ if (page.loadAsync) await page.loadAsync();
1262
+ var pn = page.findAllWithCriteria ? page.findAllWithCriteria({ types: ['COMPONENT', 'COMPONENT_SET'] }) : [];
1263
+ await processNodeList(pn);
1264
+ }
1265
+ } else {
1266
+ await figma.loadAllPagesAsync();
1267
+ var pages = figma.root.children;
1268
+ for (var p = 0; p < pages.length && !hitLimit; p++) {
1269
+ var pg = pages[p];
1270
+ if (pg && pg.loadAsync) await pg.loadAsync();
1271
+ var pageNodes = pg && pg.findAllWithCriteria ? pg.findAllWithCriteria({ types: ['COMPONENT', 'COMPONENT_SET'] }) : [];
1272
+ await processNodeList(pageNodes);
1273
+ }
1274
+ }
1275
+ out.components = components;
1276
+ out.componentSets = componentSets;
1277
+ out.truncatedByLimit = hitLimit;
1278
+ out.currentPageOnly = currentPageOnly;
1279
+ out.notes.push('Components are from the current file (local + published keys via component.key). Full remote catalog may require REST API.');
1280
+ }
1281
+
1282
+ figma.ui.postMessage({
1283
+ type: 'SEARCH_LIBRARY_ASSETS_RESULT',
1284
+ requestId: msg.requestId,
1285
+ success: true,
1286
+ data: out
1287
+ });
1288
+ } catch (error) {
1289
+ var emsg = error && error.message ? error.message : String(error);
1290
+ figma.ui.postMessage({
1291
+ type: 'SEARCH_LIBRARY_ASSETS_RESULT',
1292
+ requestId: msg.requestId,
1293
+ success: false,
1294
+ error: emsg
1295
+ });
1296
+ }
1297
+ }
1298
+
1299
+ // ============================================================================
1300
+ // GET_CODE_CONNECT_HINTS — documentationLinks + component keys (not full Code Connect file map)
1301
+ // ============================================================================
1302
+ else if (msg.type === 'GET_CODE_CONNECT_HINTS') {
1303
+ try {
1304
+ var ids = msg.nodeIds && msg.nodeIds.length ? msg.nodeIds : [];
1305
+ var scanPage = msg.scanCurrentPage === true;
1306
+ var maxNodes = Math.min(parseInt(msg.maxNodes, 10) || 40, 120);
1307
+ var nodes = [];
1308
+
1309
+ async function pushNodeEntry(n) {
1310
+ if (!n) return;
1311
+ if (n.loadAsync) await n.loadAsync();
1312
+ var entry = { nodeId: n.id, name: n.name, type: n.type };
1313
+ if (n.type === 'COMPONENT' || n.type === 'COMPONENT_SET') {
1314
+ entry.componentKey = n.key;
1315
+ }
1316
+ if ('description' in n && n.description) entry.description = n.description;
1317
+ if ('documentationLinks' in n && n.documentationLinks && n.documentationLinks.length) {
1318
+ entry.documentationLinks = n.documentationLinks.map(function (link) {
1319
+ return { uri: link.uri, type: link.type };
1320
+ });
1321
+ }
1322
+ nodes.push(entry);
1323
+ }
1324
+
1325
+ if (ids.length > 0) {
1326
+ for (var ii = 0; ii < ids.length && nodes.length < maxNodes; ii++) {
1327
+ var node = await figma.getNodeByIdAsync(ids[ii]);
1328
+ await pushNodeEntry(node);
1329
+ }
1330
+ } else if (scanPage) {
1331
+ var cur = figma.currentPage;
1332
+ if (cur && cur.loadAsync) await cur.loadAsync();
1333
+ var found = cur && cur.findAllWithCriteria
1334
+ ? cur.findAllWithCriteria({ types: ['COMPONENT', 'COMPONENT_SET', 'INSTANCE'] })
1335
+ : [];
1336
+ for (var fi = 0; fi < found.length && nodes.length < maxNodes; fi++) {
1337
+ await pushNodeEntry(found[fi]);
1338
+ }
1339
+ } else {
1340
+ throw new Error('Provide nodeIds array or set scanCurrentPage=true');
1341
+ }
1342
+
1343
+ figma.ui.postMessage({
1344
+ type: 'GET_CODE_CONNECT_HINTS_RESULT',
1345
+ requestId: msg.requestId,
1346
+ success: true,
1347
+ data: {
1348
+ nodes: nodes,
1349
+ note: 'Full Code Connect source paths live in repo figma.config / CLI; use Figma official MCP for native get_code_connect_map when needed.'
1350
+ }
1351
+ });
1352
+ } catch (error2) {
1353
+ var emsg2 = error2 && error2.message ? error2.message : String(error2);
1354
+ figma.ui.postMessage({
1355
+ type: 'GET_CODE_CONNECT_HINTS_RESULT',
1356
+ requestId: msg.requestId,
1357
+ success: false,
1358
+ error: emsg2
1359
+ });
1360
+ }
1361
+ }
1362
+
1149
1363
  // ============================================================================
1150
1364
  // INSTANTIATE_COMPONENT - Create a component instance with overrides
1151
1365
  // ============================================================================
@@ -7,6 +7,8 @@
7
7
  "editorType": ["figma", "figjam", "dev"],
8
8
  "capabilities": ["inspect"],
9
9
  "documentAccess": "dynamic-page",
10
+ "permissions": ["teamlibrary"],
11
+ "enablePrivatePluginApi": true,
10
12
  "networkAccess": {
11
13
  "allowedDomains": [
12
14
  "http://localhost",
@@ -26,8 +28,10 @@
26
28
  "http://localhost:5467", "ws://localhost:5467",
27
29
  "http://localhost:5468", "ws://localhost:5468",
28
30
  "http://localhost:5469", "ws://localhost:5469",
29
- "http://localhost:5470", "ws://localhost:5470"
31
+ "http://localhost:5470", "ws://localhost:5470",
32
+ "https://figma-mcp-bridge.workers.dev",
33
+ "wss://figma-mcp-bridge.workers.dev"
30
34
  ],
31
- "reasoning": "Connect to local MCP server (no Figma debug port needed)"
35
+ "reasoning": "Local MCP WebSocket (5454–5470) and optional FMCP Cloud Mode (Workers); add your custom worker host here if different."
32
36
  }
33
37
  }