@atezer/figma-mcp-bridge 1.1.1 → 1.2.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.
@@ -20,13 +20,30 @@ console.error = function() { _pushLog('error', arguments); _origError.apply(cons
20
20
  console.log('🌉 [F-MCP ATezer Bridge] Plugin loaded and ready');
21
21
 
22
22
  // Show minimal UI - compact status indicator
23
- figma.showUI(__html__, { width: 120, height: 36, visible: true, themeColors: true });
23
+ figma.showUI(__html__, { width: 200, height: 56, visible: true, themeColors: true });
24
24
 
25
25
  // Immediately fetch and send variables data to UI
26
26
  (async () => {
27
27
  try {
28
28
  console.log('🌉 [F-MCP ATezer Bridge] Fetching variables...');
29
29
 
30
+ // FigJam does not support figma.variables API
31
+ if (!figma.variables || typeof figma.variables.getLocalVariablesAsync !== 'function') {
32
+ console.log('🌉 [F-MCP ATezer Bridge] Variables API not available (FigJam or limited editor), skipping variable load');
33
+ figma.ui.postMessage({
34
+ type: 'VARIABLES_DATA',
35
+ data: {
36
+ success: true,
37
+ timestamp: Date.now(),
38
+ fileKey: figma.fileKey || null,
39
+ variables: [],
40
+ variableCollections: [],
41
+ _note: 'Variables API not available in this editor type'
42
+ }
43
+ });
44
+ return;
45
+ }
46
+
30
47
  // Get all local variables and collections
31
48
  const variables = await figma.variables.getLocalVariablesAsync();
32
49
  const collections = await figma.variables.getLocalVariableCollectionsAsync();
@@ -149,6 +166,205 @@ function hexToFigmaRGB(hex) {
149
166
  // Listen for requests from UI (e.g., component data requests, write operations)
150
167
  figma.ui.onmessage = async (msg) => {
151
168
 
169
+ function rgbaToHex(color) {
170
+ if (!color || typeof color !== 'object') return null;
171
+ var r = Math.round((Number(color.r) !== undefined ? Number(color.r) : 0) * 255);
172
+ var g = Math.round((Number(color.g) !== undefined ? Number(color.g) : 0) * 255);
173
+ var b = Math.round((Number(color.b) !== undefined ? Number(color.b) : 0) * 255);
174
+ return '#' + [r, g, b].map(function(x) { return x.toString(16).padStart(2, '0'); }).join('');
175
+ }
176
+
177
+ function nameToSuiComponent(name, description) {
178
+ var raw = (name || '') + (description ? ' ' + description : '');
179
+ raw = raw.replace(/[0-9_\-\s]+/g, ' ').trim().split(/\s+/);
180
+ if (raw.length === 0 || (raw.length === 1 && !raw[0])) return null;
181
+ return raw.map(function(w) { return w.charAt(0).toUpperCase() + w.slice(1).toLowerCase(); }).join('');
182
+ }
183
+
184
+ function buildLayoutSummary(layout, outputHint) {
185
+ if (!layout || !layout.layoutMode) return null;
186
+ var parts = [];
187
+ var dir = layout.layoutMode === 'HORIZONTAL' ? 'row' : layout.layoutMode === 'VERTICAL' ? 'col' : 'grid';
188
+ if (outputHint === 'tailwind') {
189
+ parts.push('flex' + (dir === 'row' ? '' : '-col'));
190
+ if (layout.itemSpacing != null) parts.push('gap-' + Math.round(layout.itemSpacing));
191
+ var p = layout.paddingLeft || layout.paddingRight || layout.paddingTop || layout.paddingBottom;
192
+ if (p != null) parts.push('p-' + Math.round(p));
193
+ } else if (outputHint === 'react') {
194
+ parts.push('flex ' + dir);
195
+ if (layout.itemSpacing != null) parts.push('gap ' + layout.itemSpacing);
196
+ if (layout.paddingLeft != null) parts.push('paddingLeft ' + layout.paddingLeft);
197
+ if (layout.paddingTop != null) parts.push('paddingTop ' + layout.paddingTop);
198
+ } else {
199
+ parts.push(dir === 'grid' ? 'grid' : 'flex ' + dir);
200
+ if (layout.itemSpacing != null) parts.push('gap ' + layout.itemSpacing);
201
+ if (layout.paddingLeft != null || layout.paddingTop != null) parts.push('padding ' + (layout.paddingLeft || 0) + '/' + (layout.paddingTop || 0));
202
+ }
203
+ return parts.join(', ');
204
+ }
205
+
206
+ // Resolve variable alias(es) to names (SUI token reference — 2.3)
207
+ async function resolveVariableNames(aliases) {
208
+ if (!aliases) return [];
209
+ var list = Array.isArray(aliases) ? aliases : (aliases.id ? [aliases] : []);
210
+ var names = [];
211
+ for (var i = 0; i < list.length; i++) {
212
+ var alias = list[i];
213
+ if (alias && alias.id) {
214
+ try {
215
+ var v = await figma.variables.getVariableByIdAsync(alias.id);
216
+ if (v && v.name) names.push(v.name);
217
+ } catch (e) { /* skip */ }
218
+ }
219
+ }
220
+ return names;
221
+ }
222
+
223
+ // Build node payload for GET_DOCUMENT_STRUCTURE / GET_NODE_CONTEXT (layout, constraints, visual, typography, code-ready, SUI)
224
+ async function buildNodePayload(node, currentDepth, maxDepth, opts) {
225
+ if (currentDepth > maxDepth) return null;
226
+ var verbosity = opts.verbosity || 'summary';
227
+ var includeLayout = opts.includeLayout === true || verbosity === 'full';
228
+ var includeVisual = opts.includeVisual === true || verbosity === 'full';
229
+ var includeTypography = opts.includeTypography === true || verbosity === 'full';
230
+ var includeCodeReady = opts.includeCodeReady !== false && (includeLayout || includeVisual);
231
+ var outputHint = opts.outputHint || null;
232
+
233
+ var out = { id: node.id, name: node.name, type: node.type };
234
+
235
+ if (verbosity !== 'inventory' && verbosity !== 'summary') {
236
+ if (node.absoluteBoundingBox) out.absoluteBoundingBox = node.absoluteBoundingBox;
237
+ if (node.width !== undefined) out.width = node.width;
238
+ if (node.height !== undefined) out.height = node.height;
239
+ if (node.type === 'TEXT' && node.characters !== undefined) out.characters = node.characters;
240
+ }
241
+
242
+ var incompleteReasons = [];
243
+ if (node.description !== undefined && node.description !== '') out.description = node.description;
244
+ var suiName = nameToSuiComponent(node.name, node.description);
245
+ if (suiName) { out.roleHint = suiName; out.suiComponent = suiName; }
246
+
247
+ if (node.type === 'INSTANCE' && 'componentProperties' in node && node.componentProperties) {
248
+ var props = node.componentProperties;
249
+ var propNames = Object.keys(props);
250
+ if (propNames.length > 0) {
251
+ out.suggestedProps = {};
252
+ var summaryParts = [];
253
+ for (var i = 0; i < propNames.length; i++) {
254
+ var pn = propNames[i];
255
+ var v = props[pn];
256
+ out.suggestedProps[pn] = v;
257
+ summaryParts.push(pn + '=' + (typeof v === 'string' ? v : String(v)));
258
+ }
259
+ out.variantSummary = summaryParts.join(', ');
260
+ }
261
+ }
262
+
263
+ if (includeLayout) {
264
+ if ('constraints' in node && node.constraints) {
265
+ out.constraints = { horizontal: node.constraints.horizontal, vertical: node.constraints.vertical };
266
+ }
267
+ if ('layoutMode' in node && node.layoutMode && node.layoutMode !== 'NONE') {
268
+ var layout = { layoutMode: node.layoutMode };
269
+ if (node.paddingLeft !== undefined) layout.paddingLeft = node.paddingLeft;
270
+ if (node.paddingRight !== undefined) layout.paddingRight = node.paddingRight;
271
+ if (node.paddingTop !== undefined) layout.paddingTop = node.paddingTop;
272
+ if (node.paddingBottom !== undefined) layout.paddingBottom = node.paddingBottom;
273
+ if (node.itemSpacing !== undefined) layout.itemSpacing = node.itemSpacing;
274
+ if (node.primaryAxisAlignItems !== undefined) layout.primaryAxisAlignItems = node.primaryAxisAlignItems;
275
+ if (node.counterAxisAlignItems !== undefined) layout.counterAxisAlignItems = node.counterAxisAlignItems;
276
+ if (node.primaryAxisSizingMode !== undefined) layout.primaryAxisSizingMode = node.primaryAxisSizingMode;
277
+ if (node.counterAxisSizingMode !== undefined) layout.counterAxisSizingMode = node.counterAxisSizingMode;
278
+ if (node.layoutWrap !== undefined) layout.layoutWrap = node.layoutWrap;
279
+ if (node.counterAxisSpacing != null) layout.counterAxisSpacing = node.counterAxisSpacing;
280
+ if (node.layoutMode === 'GRID') {
281
+ if (node.gridRowCount !== undefined) layout.gridRowCount = node.gridRowCount;
282
+ if (node.gridColumnCount !== undefined) layout.gridColumnCount = node.gridColumnCount;
283
+ if (node.gridRowGap !== undefined) layout.gridRowGap = node.gridRowGap;
284
+ if (node.gridColumnGap !== undefined) layout.gridColumnGap = node.gridColumnGap;
285
+ }
286
+ out.layout = layout;
287
+ if (includeCodeReady) out.layoutSummary = buildLayoutSummary(layout, outputHint);
288
+ }
289
+ if ('layoutAlign' in node) out.layoutAlign = node.layoutAlign;
290
+ if ('layoutGrow' in node) out.layoutGrow = node.layoutGrow;
291
+ if ('layoutPositioning' in node) out.layoutPositioning = node.layoutPositioning;
292
+ if ('layoutSizingHorizontal' in node) out.layoutSizingHorizontal = node.layoutSizingHorizontal;
293
+ if ('layoutSizingVertical' in node) out.layoutSizingVertical = node.layoutSizingVertical;
294
+ if (node.minWidth != null) out.minWidth = node.minWidth;
295
+ if (node.maxWidth != null) out.maxWidth = node.maxWidth;
296
+ if (node.minHeight != null) out.minHeight = node.minHeight;
297
+ if (node.maxHeight != null) out.maxHeight = node.maxHeight;
298
+ }
299
+
300
+ var isMixed = typeof figma !== 'undefined' && figma.mixed !== undefined ? function(v) { return v === figma.mixed; } : function() { return false; };
301
+ if (includeVisual) {
302
+ var hasImageFill = false;
303
+ if ('fills' in node && node.fills !== undefined && !isMixed(node.fills)) {
304
+ try {
305
+ var fillsCopy = JSON.parse(JSON.stringify(node.fills));
306
+ out.fills = fillsCopy;
307
+ if (Array.isArray(fillsCopy) && fillsCopy.length > 0) {
308
+ var first = fillsCopy[0];
309
+ if (first && first.type === 'SOLID' && first.color) {
310
+ var hex = rgbaToHex(first.color);
311
+ if (hex) { out.colorHex = hex; out.primaryColorHex = hex; }
312
+ }
313
+ for (var f = 0; f < fillsCopy.length; f++) {
314
+ if (fillsCopy[f].type === 'IMAGE' || fillsCopy[f].imageRef) hasImageFill = true;
315
+ }
316
+ }
317
+ } catch (e) { out.fills = []; }
318
+ } else if ('fills' in node && node.fills !== undefined) {
319
+ out.fills = 'mixed';
320
+ incompleteReasons.push('mixed fills');
321
+ }
322
+ if (hasImageFill) { out.hasImageFill = true; incompleteReasons.push('image fill'); }
323
+ if ('strokes' in node && node.strokes !== undefined && !isMixed(node.strokes)) {
324
+ try { out.strokes = JSON.parse(JSON.stringify(node.strokes)); } catch (e) { out.strokes = []; }
325
+ } else if ('strokes' in node && node.strokes !== undefined) {
326
+ out.strokes = 'mixed';
327
+ incompleteReasons.push('mixed stroke');
328
+ }
329
+ if ('effects' in node && node.effects && node.effects.length > 0) {
330
+ try { out.effects = JSON.parse(JSON.stringify(node.effects)); } catch (e) { out.effects = []; }
331
+ }
332
+ if ('opacity' in node && node.opacity !== undefined) out.opacity = node.opacity;
333
+ if ('cornerRadius' in node && node.cornerRadius !== undefined && !isMixed(node.cornerRadius)) out.cornerRadius = node.cornerRadius;
334
+ if ('strokeWeight' in node && node.strokeWeight !== undefined && !isMixed(node.strokeWeight)) out.strokeWeight = node.strokeWeight;
335
+ if ('strokeAlign' in node) out.strokeAlign = node.strokeAlign;
336
+ /* Variable adı çözümlemesi sadece verbosity full'da (büyük dosyada binlerce getVariableByIdAsync timeout'a yol açar) */
337
+ if (verbosity === 'full' && 'boundVariables' in node && node.boundVariables) {
338
+ var bv = node.boundVariables;
339
+ if (bv.fills && (Array.isArray(bv.fills) ? bv.fills.length : (bv.fills && bv.fills.id))) {
340
+ var fillNames = await resolveVariableNames(bv.fills);
341
+ if (fillNames.length) out.fillVariableNames = fillNames;
342
+ }
343
+ if (bv.strokes && (Array.isArray(bv.strokes) ? bv.strokes.length : (bv.strokes && bv.strokes.id))) {
344
+ var strokeNames = await resolveVariableNames(bv.strokes);
345
+ if (strokeNames.length) out.strokeVariableNames = strokeNames;
346
+ }
347
+ }
348
+ }
349
+
350
+ if (includeTypography && node.type === 'TEXT') {
351
+ if (node.fontName !== undefined && !isMixed(node.fontName)) out.fontName = node.fontName;
352
+ else if (node.type === 'TEXT') incompleteReasons.push('font not loaded');
353
+ if (node.fontSize !== undefined && !isMixed(node.fontSize)) out.fontSize = node.fontSize;
354
+ if (node.lineHeight !== undefined && !isMixed(node.lineHeight)) out.lineHeight = node.lineHeight;
355
+ if (node.textStyleId !== undefined && !isMixed(node.textStyleId) && node.textStyleId) out.textStyleId = node.textStyleId;
356
+ }
357
+
358
+ if (incompleteReasons.length > 0) out.incompleteReasons = incompleteReasons;
359
+
360
+ if (node.children && node.children.length > 0 && currentDepth < maxDepth) {
361
+ var childPayloads = await Promise.all(node.children.map(function(c) { return buildNodePayload(c, currentDepth + 1, maxDepth, opts); }));
362
+ out.children = childPayloads.filter(Boolean);
363
+ if (verbosity === 'summary' || verbosity === 'inventory') out.childCount = node.children.length;
364
+ }
365
+ return out;
366
+ }
367
+
152
368
  // ============================================================================
153
369
  // EXECUTE_CODE - Arbitrary code execution (Power Tool)
154
370
  // ============================================================================
@@ -621,6 +837,16 @@ figma.ui.onmessage = async (msg) => {
621
837
  try {
622
838
  console.log('🌉 [F-MCP ATezer Bridge] Refreshing variables data...');
623
839
 
840
+ if (!figma.variables || typeof figma.variables.getLocalVariablesAsync !== 'function') {
841
+ figma.ui.postMessage({
842
+ type: 'REFRESH_VARIABLES_RESULT',
843
+ requestId: msg.requestId,
844
+ success: true,
845
+ data: { success: true, timestamp: Date.now(), fileKey: figma.fileKey || null, variables: [], variableCollections: [] }
846
+ });
847
+ return;
848
+ }
849
+
624
850
  var variables = await figma.variables.getLocalVariablesAsync();
625
851
  var collections = await figma.variables.getLocalVariableCollectionsAsync();
626
852
 
@@ -702,12 +928,12 @@ figma.ui.onmessage = async (msg) => {
702
928
  componentPropertyDefinitions: (node.type === 'COMPONENT_SET' || (node.type === 'COMPONENT' && !isVariant))
703
929
  ? node.componentPropertyDefinitions
704
930
  : undefined,
705
- // Get children info (lightweight)
706
- children: node.children ? node.children.map(child => ({
707
- id: child.id,
708
- name: child.name,
709
- type: child.type
710
- })) : undefined
931
+ // Get children info (include text content for TEXT nodes)
932
+ children: node.children ? node.children.map(child => {
933
+ var c = { id: child.id, name: child.name, type: child.type };
934
+ if (child.type === 'TEXT' && child.characters !== undefined) c.characters = child.characters;
935
+ return c;
936
+ }) : undefined
711
937
  }
712
938
  };
713
939
 
@@ -735,11 +961,15 @@ figma.ui.onmessage = async (msg) => {
735
961
  // ============================================================================
736
962
  else if (msg.type === 'GET_LOCAL_COMPONENTS') {
737
963
  try {
738
- console.log('🌉 [F-MCP ATezer Bridge] Fetching all local components for manifest...');
964
+ // Default true: avoid full-doc scan (timeout on large files). Only scan all pages when explicitly false.
965
+ var currentPageOnly = msg.currentPageOnly !== false;
966
+ var limit = msg.limit != null ? Math.max(0, parseInt(msg.limit, 10) || 0) : 0;
967
+ console.log('🌉 [F-MCP ATezer Bridge] Fetching local components (currentPageOnly:', currentPageOnly, ', limit:', limit || 'none', ')...');
739
968
 
740
969
  // Find all component sets and standalone components in the file
741
970
  var components = [];
742
971
  var componentSets = [];
972
+ var hitLimit = false;
743
973
 
744
974
  // Helper to extract component data
745
975
  function extractComponentData(node, isPartOfSet) {
@@ -845,38 +1075,46 @@ figma.ui.onmessage = async (msg) => {
845
1075
  };
846
1076
  }
847
1077
 
848
- // Recursively search for components
849
- function findComponents(node) {
850
- if (!node) return;
851
-
852
- if (node.type === 'COMPONENT_SET') {
853
- componentSets.push(extractComponentSetData(node));
854
- } else if (node.type === 'COMPONENT') {
855
- // Only add standalone components (not variants inside component sets)
856
- if (!node.parent || node.parent.type !== 'COMPONENT_SET') {
857
- components.push(extractComponentData(node, false));
1078
+ // Use findAllWithCriteria (async-friendly, no sync tree walk) — works in dynamic-page
1079
+ async function processNodeList(nodes) {
1080
+ for (var i = 0; i < nodes.length && !hitLimit; i++) {
1081
+ if (limit > 0 && components.length + componentSets.length >= limit) {
1082
+ hitLimit = true;
1083
+ break;
1084
+ }
1085
+ var node = nodes[i];
1086
+ if (!node) continue;
1087
+ if (node.loadAsync) await node.loadAsync();
1088
+ if (node.type === 'COMPONENT_SET') {
1089
+ componentSets.push(extractComponentSetData(node));
1090
+ } else if (node.type === 'COMPONENT') {
1091
+ if (!node.parent || node.parent.type !== 'COMPONENT_SET') {
1092
+ components.push(extractComponentData(node, false));
1093
+ }
858
1094
  }
859
1095
  }
1096
+ }
860
1097
 
861
- // Recurse into children
862
- if (node.children) {
863
- node.children.forEach(function(child) {
864
- findComponents(child);
865
- });
1098
+ if (currentPageOnly) {
1099
+ var page = figma.currentPage;
1100
+ if (page) {
1101
+ await (page.loadAsync && page.loadAsync());
1102
+ var nodes = page.findAllWithCriteria ? page.findAllWithCriteria({ types: ['COMPONENT', 'COMPONENT_SET'] }) : [];
1103
+ await processNodeList(nodes);
1104
+ }
1105
+ } else {
1106
+ console.log('🌉 [F-MCP ATezer Bridge] Loading all pages...');
1107
+ await figma.loadAllPagesAsync();
1108
+ console.log('🌉 [F-MCP ATezer Bridge] All pages loaded, searching for components...');
1109
+ var pages = figma.root.children;
1110
+ for (var p = 0; p < pages.length && !hitLimit; p++) {
1111
+ var pg = pages[p];
1112
+ if (pg && pg.loadAsync) await pg.loadAsync();
1113
+ var pageNodes = pg && pg.findAllWithCriteria ? pg.findAllWithCriteria({ types: ['COMPONENT', 'COMPONENT_SET'] }) : [];
1114
+ await processNodeList(pageNodes);
866
1115
  }
867
1116
  }
868
1117
 
869
- // Load all pages first (required before accessing children)
870
- console.log('🌉 [F-MCP ATezer Bridge] Loading all pages...');
871
- await figma.loadAllPagesAsync();
872
- console.log('🌉 [F-MCP ATezer Bridge] All pages loaded, searching for components...');
873
-
874
- // Search through all pages
875
- var pages = figma.root.children;
876
- pages.forEach(function(page) {
877
- findComponents(page);
878
- });
879
-
880
1118
  console.log('🌉 [F-MCP ATezer Bridge] Found ' + components.length + ' components and ' + componentSets.length + ' component sets');
881
1119
 
882
1120
  figma.ui.postMessage({
@@ -888,7 +1126,8 @@ figma.ui.onmessage = async (msg) => {
888
1126
  componentSets: componentSets,
889
1127
  totalComponents: components.length,
890
1128
  totalComponentSets: componentSets.length,
891
- // Include file metadata for context verification
1129
+ currentPageOnly: currentPageOnly,
1130
+ truncatedByLimit: hitLimit && limit > 0,
892
1131
  fileName: figma.root.name,
893
1132
  fileKey: figma.fileKey || null,
894
1133
  timestamp: Date.now()
@@ -1969,30 +2208,21 @@ figma.ui.onmessage = async (msg) => {
1969
2208
 
1970
2209
  var depth = Math.min(Math.max(msg.depth || 1, 0), 3);
1971
2210
  var verbosity = msg.verbosity || 'summary';
1972
-
1973
- function walkNode(node, currentDepth) {
1974
- if (currentDepth > depth) return null;
1975
- var out = { id: node.id, name: node.name, type: node.type };
1976
- if (verbosity !== 'inventory' && verbosity !== 'summary') {
1977
- if (node.absoluteBoundingBox) {
1978
- out.absoluteBoundingBox = node.absoluteBoundingBox;
1979
- }
1980
- if (node.width !== undefined) out.width = node.width;
1981
- if (node.height !== undefined) out.height = node.height;
1982
- }
1983
- if (node.children && node.children.length > 0 && currentDepth < depth) {
1984
- out.children = node.children.map(function(c) { return walkNode(c, currentDepth + 1); }).filter(Boolean);
1985
- if (verbosity === 'summary' || verbosity === 'inventory') out.childCount = node.children.length;
1986
- }
1987
- return out;
1988
- }
2211
+ var opts = {
2212
+ verbosity: verbosity,
2213
+ includeLayout: msg.includeLayout === true,
2214
+ includeVisual: msg.includeVisual === true,
2215
+ includeTypography: msg.includeTypography === true,
2216
+ includeCodeReady: msg.includeCodeReady !== false,
2217
+ outputHint: msg.outputHint || null
2218
+ };
1989
2219
 
1990
2220
  var document = {
1991
2221
  name: figma.root.name,
1992
2222
  id: figma.root.id,
1993
2223
  type: 'DOCUMENT',
1994
2224
  fileKey: figma.fileKey || null,
1995
- children: figma.root.children ? figma.root.children.map(function(p) { return walkNode(p, 1); }).filter(Boolean) : []
2225
+ children: figma.root.children ? (await Promise.all(figma.root.children.map(function(p) { return buildNodePayload(p, 1, depth, opts); }))).filter(Boolean) : []
1996
2226
  };
1997
2227
 
1998
2228
  figma.ui.postMessage({
@@ -2012,6 +2242,64 @@ figma.ui.onmessage = async (msg) => {
2012
2242
  }
2013
2243
  }
2014
2244
 
2245
+ // ============================================================================
2246
+ // GET_NODE_CONTEXT - Subtree for one node with text content (token-efficient design context)
2247
+ // ============================================================================
2248
+ else if (msg.type === 'GET_NODE_CONTEXT') {
2249
+ try {
2250
+ var nodeId = msg.nodeId;
2251
+ if (!nodeId) {
2252
+ figma.ui.postMessage({
2253
+ type: 'GET_NODE_CONTEXT_RESULT',
2254
+ requestId: msg.requestId,
2255
+ success: false,
2256
+ error: 'nodeId is required'
2257
+ });
2258
+ } else {
2259
+ var targetNode = await figma.getNodeByIdAsync(nodeId);
2260
+ if (!targetNode) {
2261
+ figma.ui.postMessage({
2262
+ type: 'GET_NODE_CONTEXT_RESULT',
2263
+ requestId: msg.requestId,
2264
+ success: false,
2265
+ error: 'Node not found: ' + nodeId
2266
+ });
2267
+ } else {
2268
+ var depthNode = Math.min(Math.max(msg.depth || 2, 0), 3);
2269
+ var verbosityNode = msg.verbosity || 'standard';
2270
+ var optsNode = {
2271
+ verbosity: verbosityNode,
2272
+ includeLayout: msg.includeLayout === true,
2273
+ includeVisual: msg.includeVisual === true,
2274
+ includeTypography: msg.includeTypography === true,
2275
+ includeCodeReady: msg.includeCodeReady !== false,
2276
+ outputHint: msg.outputHint || null
2277
+ };
2278
+
2279
+ var nodeTree = await buildNodePayload(targetNode, 0, depthNode, optsNode);
2280
+ figma.ui.postMessage({
2281
+ type: 'GET_NODE_CONTEXT_RESULT',
2282
+ requestId: msg.requestId,
2283
+ success: true,
2284
+ data: {
2285
+ node: nodeTree,
2286
+ fileKey: figma.fileKey || null,
2287
+ fileName: figma.root.name
2288
+ }
2289
+ });
2290
+ }
2291
+ }
2292
+ } catch (error) {
2293
+ var errMsg = error && error.message ? error.message : String(error);
2294
+ figma.ui.postMessage({
2295
+ type: 'GET_NODE_CONTEXT_RESULT',
2296
+ requestId: msg.requestId,
2297
+ success: false,
2298
+ error: errMsg
2299
+ });
2300
+ }
2301
+ }
2302
+
2015
2303
  // ============================================================================
2016
2304
  // GET_CONSOLE_LOGS - Plugin console buffer (no CDP)
2017
2305
  else if (msg.type === 'GET_CONSOLE_LOGS') {
@@ -1,14 +1,33 @@
1
1
  {
2
2
  "name": "F-MCP ATezer Bridge",
3
- "id": "f-mcp-atezer-bridge",
3
+ "id": "1608016072388581583",
4
4
  "api": "1.0.0",
5
5
  "main": "code.js",
6
6
  "ui": "ui.html",
7
- "editorType": ["figma", "dev"],
7
+ "editorType": ["figma", "figjam", "dev"],
8
8
  "capabilities": ["inspect"],
9
9
  "documentAccess": "dynamic-page",
10
10
  "networkAccess": {
11
- "allowedDomains": ["http://localhost", "http://localhost:5454", "ws://localhost:5454", "http://localhost:5455", "ws://localhost:5455", "http://localhost:5456", "ws://localhost:5456", "http://localhost:5457", "ws://localhost:5457", "http://localhost:5458", "ws://localhost:5458", "http://localhost:5459", "ws://localhost:5459", "http://localhost:5460", "ws://localhost:5460", "http://localhost:5461", "ws://localhost:5461", "http://localhost:5462", "ws://localhost:5462", "http://localhost:5463", "ws://localhost:5463", "http://localhost:5464", "ws://localhost:5464", "http://localhost:5465", "ws://localhost:5465", "http://localhost:5466", "ws://localhost:5466", "http://localhost:5467", "ws://localhost:5467", "http://localhost:5468", "ws://localhost:5468", "http://localhost:5469", "ws://localhost:5469", "http://localhost:5470", "ws://localhost:5470"],
11
+ "allowedDomains": [
12
+ "http://localhost",
13
+ "http://localhost:5454", "ws://localhost:5454",
14
+ "http://localhost:5455", "ws://localhost:5455",
15
+ "http://localhost:5456", "ws://localhost:5456",
16
+ "http://localhost:5457", "ws://localhost:5457",
17
+ "http://localhost:5458", "ws://localhost:5458",
18
+ "http://localhost:5459", "ws://localhost:5459",
19
+ "http://localhost:5460", "ws://localhost:5460",
20
+ "http://localhost:5461", "ws://localhost:5461",
21
+ "http://localhost:5462", "ws://localhost:5462",
22
+ "http://localhost:5463", "ws://localhost:5463",
23
+ "http://localhost:5464", "ws://localhost:5464",
24
+ "http://localhost:5465", "ws://localhost:5465",
25
+ "http://localhost:5466", "ws://localhost:5466",
26
+ "http://localhost:5467", "ws://localhost:5467",
27
+ "http://localhost:5468", "ws://localhost:5468",
28
+ "http://localhost:5469", "ws://localhost:5469",
29
+ "http://localhost:5470", "ws://localhost:5470"
30
+ ],
12
31
  "reasoning": "Connect to local MCP server (no Figma debug port needed)"
13
32
  }
14
33
  }