@atezer/figma-mcp-bridge 1.2.2 → 1.3.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.
@@ -172,62 +172,6 @@
172
172
  opacity: 0.9;
173
173
  }
174
174
 
175
- .cloud-panel {
176
- display: flex;
177
- flex-direction: column;
178
- gap: 6px;
179
- padding: 8px 10px;
180
- background: var(--figma-color-bg-secondary, #383838);
181
- border: 1px solid var(--figma-color-border, #4a4a4a);
182
- border-radius: 8px;
183
- font-size: 10px;
184
- }
185
- .cloud-panel label.cloud-toggle {
186
- display: flex;
187
- align-items: center;
188
- gap: 6px;
189
- cursor: pointer;
190
- font-weight: 500;
191
- }
192
- .cloud-fields {
193
- display: none;
194
- flex-direction: column;
195
- gap: 6px;
196
- }
197
- .cloud-fields.is-visible {
198
- display: flex;
199
- }
200
- .cloud-fields .field label {
201
- display: block;
202
- color: var(--figma-color-text-secondary, rgba(255, 255, 255, 0.7));
203
- margin-bottom: 2px;
204
- }
205
- .cloud-fields input[type="text"],
206
- .cloud-fields input[type="password"] {
207
- width: 100%;
208
- max-width: 220px;
209
- padding: 4px 6px;
210
- background: var(--figma-color-bg-tertiary, #1e1e1e);
211
- border: 1px solid var(--figma-color-border, #4a4a4a);
212
- border-radius: 4px;
213
- color: inherit;
214
- font-size: 11px;
215
- }
216
- .cloud-connect {
217
- align-self: flex-start;
218
- border: 1px solid var(--figma-color-border, #4a4a4a);
219
- background: transparent;
220
- color: inherit;
221
- border-radius: 6px;
222
- font-size: 10px;
223
- padding: 4px 10px;
224
- cursor: pointer;
225
- }
226
- .cloud-hint {
227
- font-size: 9px;
228
- color: var(--figma-color-text-secondary, rgba(255, 255, 255, 0.55));
229
- }
230
-
231
175
  /* Light theme support */
232
176
  @media (prefers-color-scheme: light) {
233
177
  body {
@@ -275,25 +219,6 @@
275
219
  </div>
276
220
  <button type="button" id="auto-port-reset" class="auto-port-reset" title="Tek porta kilitlenmeyi kaldırır; 5454–5470 yeniden taranır (Claude config portu ile eşleşmeli)." aria-label="5454 ile 5470 arası otomatik port taraması">Otomatik tara</button>
277
221
  </div>
278
- <div class="cloud-panel" id="cloud-panel">
279
- <label class="cloud-toggle"><input type="checkbox" id="cloud-mode-toggle" aria-label="Cloud Mode" /> Cloud Mode (uzak MCP, yerel Node yok)</label>
280
- <div class="cloud-fields" id="cloud-fields">
281
- <div class="field">
282
- <label for="cloud-base">Worker URL (https://…)</label>
283
- <input type="text" id="cloud-base" placeholder="https://figma-mcp-bridge.workers.dev" autocomplete="off" />
284
- </div>
285
- <div class="field">
286
- <label for="cloud-code">Pairing code</label>
287
- <input type="text" id="cloud-code" maxlength="8" placeholder="6 karakter" autocomplete="off" />
288
- </div>
289
- <div class="field">
290
- <label for="cloud-secret">Secret</label>
291
- <input type="password" id="cloud-secret" placeholder="AI / fmcp_generate_pairing_code çıktısı" autocomplete="off" />
292
- </div>
293
- <button type="button" id="cloud-connect" class="cloud-connect">Bağlan</button>
294
- <div class="cloud-hint" id="cloud-hint">Kodlar yaklaşık 5 dk geçerlidir. Önce uzak MCP’de fmcp_generate_pairing_code çalıştırın, sonra buraya yapıştırın.</div>
295
- </div>
296
- </div>
297
222
  </div>
298
223
 
299
224
  <script>
@@ -670,7 +595,6 @@
670
595
  window.__figmaFileKey = msg.fileKey || null;
671
596
  window.__figmaFileName = msg.fileName || null;
672
597
  console.log('[F-MCP ATezer Bridge] File identity: ' + (msg.fileName || '?') + ' (' + (msg.fileKey || '?') + ')');
673
- pushBridgeFileIdentity();
674
598
  break;
675
599
 
676
600
  case 'VARIABLES_DATA':
@@ -906,71 +830,6 @@
906
830
  var candidatePorts = [];
907
831
  var candidatePortIndex = 0;
908
832
 
909
- /**
910
- * WebSocket onopen often runs before FILE_IDENTITY arrives from the plugin main thread.
911
- * Re-send "ready" when fileKey/fileName are known so the bridge can route multi-client RPCs.
912
- */
913
- function pushBridgeFileIdentity() {
914
- if (!mcpBridgeWs || mcpBridgeWs.readyState !== 1) return;
915
- try {
916
- mcpBridgeWs.send(JSON.stringify({
917
- type: 'ready',
918
- fileKey: window.__figmaFileKey || null,
919
- fileName: window.__figmaFileName || null
920
- }));
921
- } catch (_) {}
922
- }
923
-
924
- var CLOUD_MODE_KEY = 'f-mcp-cloud-mode';
925
- var CLOUD_BASE_STORAGE = 'f-mcp-cloud-base';
926
-
927
- function isCloudMode() {
928
- var el = document.getElementById('cloud-mode-toggle');
929
- return !!(el && el.checked);
930
- }
931
-
932
- function syncCloudFieldsVisibility() {
933
- var f = document.getElementById('cloud-fields');
934
- if (f) f.classList.toggle('is-visible', isCloudMode());
935
- }
936
-
937
- function buildCloudWsUrl() {
938
- var baseEl = document.getElementById('cloud-base');
939
- var codeEl = document.getElementById('cloud-code');
940
- var secEl = document.getElementById('cloud-secret');
941
- var base = (baseEl && baseEl.value || '').trim().replace(/\/$/, '');
942
- var code = (codeEl && codeEl.value || '').trim().toUpperCase();
943
- var secret = (secEl && secEl.value || '').trim();
944
- if (!base || !code || !secret) return null;
945
- var wsBase = base.indexOf('https://') === 0 ? 'wss://' + base.slice(8) : (base.indexOf('http://') === 0 ? 'ws://' + base.slice(7) : base);
946
- if (wsBase.indexOf('wss://') !== 0 && wsBase.indexOf('ws://') !== 0) wsBase = 'wss://' + wsBase;
947
- return wsBase + '/fmcp-cloud/plugin?code=' + encodeURIComponent(code) + '&secret=' + encodeURIComponent(secret);
948
- }
949
-
950
- function initCloudModeUi() {
951
- var toggle = document.getElementById('cloud-mode-toggle');
952
- var baseEl = document.getElementById('cloud-base');
953
- try {
954
- if (baseEl && localStorage.getItem(CLOUD_BASE_STORAGE)) baseEl.value = localStorage.getItem(CLOUD_BASE_STORAGE);
955
- if (toggle && localStorage.getItem(CLOUD_MODE_KEY) === '1') toggle.checked = true;
956
- } catch (e) {}
957
- syncCloudFieldsVisibility();
958
- if (toggle) {
959
- toggle.addEventListener('change', function() {
960
- try { localStorage.setItem(CLOUD_MODE_KEY, toggle.checked ? '1' : '0'); } catch (e2) {}
961
- syncCloudFieldsVisibility();
962
- forceDisconnectAndReconnect();
963
- });
964
- }
965
- if (baseEl) {
966
- baseEl.addEventListener('change', function() {
967
- try { localStorage.setItem(CLOUD_BASE_STORAGE, (baseEl.value || '').trim()); } catch (e3) {}
968
- });
969
- }
970
- var cbtn = document.getElementById('cloud-connect');
971
- if (cbtn) cbtn.addEventListener('click', function() { forceDisconnectAndReconnect(); });
972
- }
973
-
974
833
  /** Port alanı elle değiştirilince manualPortLocked=true olur; bu kilit 5454–5470 taramasını durdurur. */
975
834
  function unlockAutoPortScan() {
976
835
  manualPortLocked = false;
@@ -1058,8 +917,6 @@
1058
917
  }
1059
918
 
1060
919
  function forceDisconnectAndReconnect() {
1061
- stopCloudPairCountdown();
1062
- resetCloudHintDefault();
1063
920
  if (mcpBridgeReconnectTimer) { clearTimeout(mcpBridgeReconnectTimer); mcpBridgeReconnectTimer = null; }
1064
921
  mcpReconnectDelay = MCP_RECONNECT_MIN;
1065
922
  candidatePorts = listCandidatePorts();
@@ -1121,46 +978,6 @@
1121
978
  var mcpHandshakeTimer = null;
1122
979
  var mcpHandshakeDone = false;
1123
980
  var mcpKeepAliveTimer = null;
1124
- var cloudPairCountdownTimer = null;
1125
- var CLOUD_PAIR_TTL_SEC = 300;
1126
- var CLOUD_HINT_DEFAULT = 'Kodlar yaklaşık 5 dk geçerlidir. Önce uzak MCP’de fmcp_generate_pairing_code çalıştırın, sonra buraya yapıştırın.';
1127
-
1128
- function stopCloudPairCountdown() {
1129
- if (cloudPairCountdownTimer) {
1130
- clearInterval(cloudPairCountdownTimer);
1131
- cloudPairCountdownTimer = null;
1132
- }
1133
- }
1134
-
1135
- function startCloudPairCountdown() {
1136
- stopCloudPairCountdown();
1137
- var hint = document.getElementById('cloud-hint');
1138
- var left = CLOUD_PAIR_TTL_SEC;
1139
- function fmt(t) {
1140
- var m = Math.floor(t / 60);
1141
- var s = t % 60;
1142
- return m + ':' + (s < 10 ? '0' : '') + s;
1143
- }
1144
- function tick() {
1145
- if (hint) {
1146
- hint.textContent = left > 0
1147
- ? ('Pairing penceresi (yakl.): ' + fmt(left) + ' — sunucu TTL 5 dk')
1148
- : 'Süre doldu; uzak MCP’de fmcp_generate_pairing_code ile yeni kod alın.';
1149
- }
1150
- if (left <= 0) {
1151
- stopCloudPairCountdown();
1152
- return;
1153
- }
1154
- left--;
1155
- }
1156
- tick();
1157
- cloudPairCountdownTimer = setInterval(tick, 1000);
1158
- }
1159
-
1160
- function resetCloudHintDefault() {
1161
- var hint = document.getElementById('cloud-hint');
1162
- if (hint) hint.textContent = CLOUD_HINT_DEFAULT;
1163
- }
1164
981
 
1165
982
  function scheduleReconnect() {
1166
983
  if (mcpBridgeReconnectTimer) clearTimeout(mcpBridgeReconnectTimer);
@@ -1187,269 +1004,7 @@
1187
1004
  if (mcpKeepAliveTimer) { clearInterval(mcpKeepAliveTimer); mcpKeepAliveTimer = null; }
1188
1005
  }
1189
1006
 
1190
- function handleMcpBridgeMessage(event) {
1191
- try {
1192
- var msg = JSON.parse(event.data);
1193
- if (msg.type === 'welcome') {
1194
- mcpHandshakeDone = true;
1195
- if (mcpHandshakeTimer) { clearTimeout(mcpHandshakeTimer); mcpHandshakeTimer = null; }
1196
- if (isCloudMode()) {
1197
- stopCloudPairCountdown();
1198
- var ch = document.getElementById('cloud-hint');
1199
- if (ch) ch.textContent = 'Bağlı. Uzak MCP oturumunda fmcp_cloud_bind çağrılmalı.';
1200
- console.log('[F-MCP ATezer Bridge] Cloud handshake OK — ' + (msg.bridgeVersion || '?'));
1201
- updateStatus('cloud ready', true, false);
1202
- return;
1203
- }
1204
- var port = candidatePorts[Math.min(candidatePortIndex, candidatePorts.length - 1)];
1205
- mcpConnectedPort = msg.port || port;
1206
- try { localStorage.setItem(MCP_BRIDGE_PORT_KEY, String(mcpConnectedPort)); } catch (e) {}
1207
- var portEl = document.getElementById('mcp-port');
1208
- if (portEl && mcpConnectedPort >= MCP_PORT_MIN && mcpConnectedPort <= MCP_PORT_MAX) {
1209
- portEl.value = String(mcpConnectedPort);
1210
- }
1211
- if (!manualPortLocked) {
1212
- candidatePorts = [mcpConnectedPort];
1213
- candidatePortIndex = 0;
1214
- }
1215
- console.log('[F-MCP ATezer Bridge] Handshake OK — bridge v' + (msg.bridgeVersion || '?') + ' on port ' + mcpConnectedPort);
1216
- updateStatus('ready (:' + mcpConnectedPort + ')', true, false);
1217
- return;
1218
- }
1219
- if (msg.type === 'ping') {
1220
- if (mcpBridgeWs && mcpBridgeWs.readyState === 1) {
1221
- mcpBridgeWs.send(JSON.stringify({ type: 'pong' }));
1222
- }
1223
- return;
1224
- }
1225
- if (!msg.id || !msg.method) return;
1226
- var id = msg.id;
1227
- var method = msg.method;
1228
- var params = msg.params || {};
1229
- var run = function() {
1230
- if (method === 'getVariablesFromPluginUI') {
1231
- return window.refreshVariables().then(function(r) {
1232
- if (r && r.success === false) return { success: false, error: r.error || 'Variables refresh failed' };
1233
- if (!window.__figmaVariablesData) return { success: false, error: 'Variables not loaded' };
1234
- return { success: true, variables: window.__figmaVariablesData.variables, variableCollections: window.__figmaVariablesData.variableCollections };
1235
- });
1236
- }
1237
- if (method === 'getComponentFromPluginUI') {
1238
- return window.requestComponentData(params.nodeId).then(function(data) {
1239
- return { success: true, component: data };
1240
- });
1241
- }
1242
- if (method === 'getComponentByNodeId') {
1243
- return window.requestComponentData(params.nodeId).then(function(data) {
1244
- return { success: true, component: data };
1245
- });
1246
- }
1247
- if (method === 'getVariables') {
1248
- if (!window.__figmaVariablesReady || !window.__figmaVariablesData)
1249
- return { success: false, error: 'Variables not loaded yet' };
1250
- return { success: true, timestamp: Date.now(), fileMetadata: {}, variables: window.__figmaVariablesData.variables, variableCollections: window.__figmaVariablesData.variableCollections };
1251
- }
1252
- if (method === 'executeCodeViaUI') {
1253
- return window.executeCode(params.code, params.timeout);
1254
- }
1255
- if (method === 'updateVariable') {
1256
- return window.updateVariable(params.variableId, params.modeId, params.value);
1257
- }
1258
- if (method === 'createVariable') {
1259
- return window.createVariable(params.name, params.collectionId, params.resolvedType, params.options);
1260
- }
1261
- if (method === 'createVariableCollection') {
1262
- return window.createVariableCollection(params.name, params.options);
1263
- }
1264
- if (method === 'deleteVariable') {
1265
- return window.deleteVariable(params.variableId);
1266
- }
1267
- if (method === 'deleteVariableCollection') {
1268
- return window.deleteVariableCollection(params.collectionId);
1269
- }
1270
- if (method === 'renameVariable') {
1271
- return window.renameVariable(params.variableId, params.newName);
1272
- }
1273
- if (method === 'addMode') {
1274
- return window.addMode(params.collectionId, params.modeName);
1275
- }
1276
- if (method === 'renameMode') {
1277
- return window.renameMode(params.collectionId, params.modeId, params.newName);
1278
- }
1279
- if (method === 'refreshVariables') {
1280
- return window.refreshVariables();
1281
- }
1282
- if (method === 'getLocalComponents') {
1283
- return window.getLocalComponents(params.currentPageOnly, params.limit);
1284
- }
1285
- if (method === 'searchLibraryAssets') {
1286
- return window.searchLibraryAssets(params.query, params.assetTypes, params.limit, params.currentPageOnly);
1287
- }
1288
- if (method === 'getCodeConnectHints') {
1289
- return window.getCodeConnectHints(params.nodeIds, params.scanCurrentPage, params.maxNodes);
1290
- }
1291
- if (method === 'instantiateComponent') {
1292
- return window.instantiateComponent(params.componentKey, params.options);
1293
- }
1294
- if (method === 'setNodeDescription') {
1295
- return window.setNodeDescription(params.nodeId, params.description, params.descriptionMarkdown);
1296
- }
1297
- if (method === 'addComponentProperty') {
1298
- return window.addComponentProperty(params.nodeId, params.propertyName, params.type, params.defaultValue, params.options);
1299
- }
1300
- if (method === 'editComponentProperty') {
1301
- return window.editComponentProperty(params.nodeId, params.propertyName, params.newValue);
1302
- }
1303
- if (method === 'deleteComponentProperty') {
1304
- return window.deleteComponentProperty(params.nodeId, params.propertyName);
1305
- }
1306
- if (method === 'resizeNode') {
1307
- return window.resizeNode(params.nodeId, params.width, params.height, params.withConstraints);
1308
- }
1309
- if (method === 'moveNode') {
1310
- return window.moveNode(params.nodeId, params.x, params.y);
1311
- }
1312
- if (method === 'setNodeFills') {
1313
- return window.setNodeFills(params.nodeId, params.fills);
1314
- }
1315
- if (method === 'setNodeStrokes') {
1316
- return window.setNodeStrokes(params.nodeId, params.strokes, params.strokeWeight);
1317
- }
1318
- if (method === 'setNodeOpacity') {
1319
- return window.setNodeOpacity(params.nodeId, params.opacity);
1320
- }
1321
- if (method === 'setNodeCornerRadius') {
1322
- return window.setNodeCornerRadius(params.nodeId, params.radius);
1323
- }
1324
- if (method === 'cloneNode') {
1325
- return window.cloneNode(params.nodeId);
1326
- }
1327
- if (method === 'deleteNode') {
1328
- return window.deleteNode(params.nodeId);
1329
- }
1330
- if (method === 'renameNode') {
1331
- return window.renameNode(params.nodeId, params.newName);
1332
- }
1333
- if (method === 'setTextContent') {
1334
- return window.setTextContent(params.nodeId, params.text, params.options);
1335
- }
1336
- if (method === 'createChildNode') {
1337
- return window.createChildNode(params.parentId, params.nodeType, params.properties);
1338
- }
1339
- if (method === 'captureScreenshot') {
1340
- return window.captureScreenshot(params.nodeId, params.options);
1341
- }
1342
- if (method === 'setInstanceProperties') {
1343
- return window.setInstanceProperties(params.nodeId, params.properties);
1344
- }
1345
- if (method === 'getDocumentStructure') {
1346
- return window.getDocumentStructure(params.depth, params.verbosity, params.includeLayout, params.includeVisual, params.includeTypography, params.includeCodeReady, params.outputHint);
1347
- }
1348
- if (method === 'getNodeContext') {
1349
- return window.getNodeContext(params.nodeId, params.depth, params.verbosity, params.includeLayout, params.includeVisual, params.includeTypography, params.includeCodeReady, params.outputHint);
1350
- }
1351
- if (method === 'getLocalStyles') {
1352
- return window.getLocalStyles(params.verbosity);
1353
- }
1354
- if (method === 'getConsoleLogs') {
1355
- return window.getConsoleLogs(params.limit);
1356
- }
1357
- if (method === 'clearConsole') {
1358
- return window.clearConsole();
1359
- }
1360
- if (method === 'batchCreateVariables') {
1361
- return window.batchCreateVariables(params.items);
1362
- }
1363
- if (method === 'batchUpdateVariables') {
1364
- return window.batchUpdateVariables(params.items);
1365
- }
1366
- if (method === 'setupDesignTokens') {
1367
- return window.setupDesignTokens(params);
1368
- }
1369
- if (method === 'arrangeComponentSet') {
1370
- return window.arrangeComponentSet(params.nodeIds);
1371
- }
1372
- return { success: false, error: 'Unknown method: ' + method };
1373
- };
1374
- var wsRef = mcpBridgeWs;
1375
- Promise.resolve(run()).then(function(result) {
1376
- if (wsRef && wsRef.readyState === 1) {
1377
- try { wsRef.send(JSON.stringify({ id: id, result: result })); } catch (_) {}
1378
- }
1379
- }).catch(function(err) {
1380
- if (wsRef && wsRef.readyState === 1) {
1381
- try { wsRef.send(JSON.stringify({ id: id, error: err.message || String(err) })); } catch (_) {}
1382
- }
1383
- });
1384
- } catch (e) {
1385
- if (mcpBridgeWs && mcpBridgeWs.readyState === 1 && msg && msg.id) {
1386
- try { mcpBridgeWs.send(JSON.stringify({ id: msg.id, error: e.message || String(e) })); } catch (_) {}
1387
- }
1388
- }
1389
- }
1390
-
1391
- function connectCloudMcpBridge() {
1392
- if (mcpConnecting) return;
1393
- if (mcpBridgeWs && (mcpBridgeWs.readyState === 0 || mcpBridgeWs.readyState === 1)) return;
1394
- var url = buildCloudWsUrl();
1395
- if (!url) {
1396
- updateStatus('cloud: eksik alan', false, true);
1397
- return;
1398
- }
1399
- startCloudPairCountdown();
1400
- mcpConnecting = true;
1401
- mcpHandshakeDone = false;
1402
- autoPortProbeActive = false;
1403
- try {
1404
- mcpBridgeWs = new WebSocket(url);
1405
- var currentWs = mcpBridgeWs;
1406
- mcpBridgeWs.onopen = function() {
1407
- mcpConnecting = false;
1408
- mcpReconnectDelay = MCP_RECONNECT_MIN;
1409
- console.log('[F-MCP ATezer Bridge] Cloud WebSocket: ' + url);
1410
- updateStatus('cloud…', false, false);
1411
- currentWs.send(JSON.stringify({
1412
- type: 'ready',
1413
- fileKey: window.__figmaFileKey || null,
1414
- fileName: window.__figmaFileName || null
1415
- }));
1416
- startKeepAlive(currentWs);
1417
- if (mcpHandshakeTimer) clearTimeout(mcpHandshakeTimer);
1418
- mcpHandshakeTimer = setTimeout(function() {
1419
- if (!mcpHandshakeDone && mcpBridgeWs === currentWs) {
1420
- updateStatus('cloud handshake?', false, true);
1421
- }
1422
- }, 8000);
1423
- };
1424
- mcpBridgeWs.onmessage = handleMcpBridgeMessage;
1425
- mcpBridgeWs.onclose = function() {
1426
- stopKeepAlive();
1427
- stopCloudPairCountdown();
1428
- resetCloudHintDefault();
1429
- if (mcpBridgeWs !== currentWs) return;
1430
- mcpBridgeWs = null;
1431
- mcpConnecting = false;
1432
- if (mcpHandshakeTimer) { clearTimeout(mcpHandshakeTimer); mcpHandshakeTimer = null; }
1433
- updateStatus('cloud kapalı', false, true);
1434
- scheduleReconnect();
1435
- };
1436
- mcpBridgeWs.onerror = function() {
1437
- mcpConnecting = false;
1438
- };
1439
- } catch (e) {
1440
- stopCloudPairCountdown();
1441
- resetCloudHintDefault();
1442
- mcpConnecting = false;
1443
- updateStatus('cloud hata', false, true);
1444
- scheduleReconnect();
1445
- }
1446
- }
1447
-
1448
1007
  function connectMcpBridge() {
1449
- if (isCloudMode()) {
1450
- connectCloudMcpBridge();
1451
- return;
1452
- }
1453
1008
  if (mcpConnecting) return;
1454
1009
  if (mcpBridgeWs && (mcpBridgeWs.readyState === 0 || mcpBridgeWs.readyState === 1)) return;
1455
1010
  mcpConnecting = true;
@@ -1484,7 +1039,198 @@
1484
1039
  }
1485
1040
  }, 5000);
1486
1041
  };
1487
- mcpBridgeWs.onmessage = handleMcpBridgeMessage;
1042
+ mcpBridgeWs.onmessage = function(event) {
1043
+ try {
1044
+ var msg = JSON.parse(event.data);
1045
+ if (msg.type === 'welcome') {
1046
+ mcpHandshakeDone = true;
1047
+ if (mcpHandshakeTimer) { clearTimeout(mcpHandshakeTimer); mcpHandshakeTimer = null; }
1048
+ mcpConnectedPort = msg.port || port;
1049
+ try { localStorage.setItem(MCP_BRIDGE_PORT_KEY, String(mcpConnectedPort)); } catch (e) {}
1050
+ var portEl = document.getElementById('mcp-port');
1051
+ if (portEl && mcpConnectedPort >= MCP_PORT_MIN && mcpConnectedPort <= MCP_PORT_MAX) {
1052
+ portEl.value = String(mcpConnectedPort);
1053
+ }
1054
+ if (!manualPortLocked) {
1055
+ candidatePorts = [mcpConnectedPort];
1056
+ candidatePortIndex = 0;
1057
+ }
1058
+ console.log('[F-MCP ATezer Bridge] Handshake OK — bridge v' + (msg.bridgeVersion || '?') + ' on port ' + mcpConnectedPort);
1059
+ updateStatus('ready (:' + mcpConnectedPort + ')', true, false);
1060
+ return;
1061
+ }
1062
+ if (msg.type === 'ping') {
1063
+ if (mcpBridgeWs && mcpBridgeWs.readyState === 1) {
1064
+ mcpBridgeWs.send(JSON.stringify({ type: 'pong' }));
1065
+ }
1066
+ return;
1067
+ }
1068
+ if (!msg.id || !msg.method) return;
1069
+ var id = msg.id;
1070
+ var method = msg.method;
1071
+ var params = msg.params || {};
1072
+ var run = function() {
1073
+ if (method === 'getVariablesFromPluginUI') {
1074
+ // Always refresh first (async API) so variables work in dynamic-page context
1075
+ return window.refreshVariables().then(function(r) {
1076
+ if (r && r.success === false) return { success: false, error: r.error || 'Variables refresh failed' };
1077
+ if (!window.__figmaVariablesData) return { success: false, error: 'Variables not loaded' };
1078
+ return { success: true, variables: window.__figmaVariablesData.variables, variableCollections: window.__figmaVariablesData.variableCollections };
1079
+ });
1080
+ }
1081
+ if (method === 'getComponentFromPluginUI') {
1082
+ return window.requestComponentData(params.nodeId).then(function(data) {
1083
+ return { success: true, component: data };
1084
+ });
1085
+ }
1086
+ if (method === 'getComponentByNodeId') {
1087
+ return window.requestComponentData(params.nodeId).then(function(data) {
1088
+ return { success: true, component: data };
1089
+ });
1090
+ }
1091
+ if (method === 'getVariables') {
1092
+ if (!window.__figmaVariablesReady || !window.__figmaVariablesData)
1093
+ return { success: false, error: 'Variables not loaded yet' };
1094
+ return { success: true, timestamp: Date.now(), fileMetadata: {}, variables: window.__figmaVariablesData.variables, variableCollections: window.__figmaVariablesData.variableCollections };
1095
+ }
1096
+ if (method === 'executeCodeViaUI') {
1097
+ return window.executeCode(params.code, params.timeout);
1098
+ }
1099
+ if (method === 'updateVariable') {
1100
+ return window.updateVariable(params.variableId, params.modeId, params.value);
1101
+ }
1102
+ if (method === 'createVariable') {
1103
+ return window.createVariable(params.name, params.collectionId, params.resolvedType, params.options);
1104
+ }
1105
+ if (method === 'createVariableCollection') {
1106
+ return window.createVariableCollection(params.name, params.options);
1107
+ }
1108
+ if (method === 'deleteVariable') {
1109
+ return window.deleteVariable(params.variableId);
1110
+ }
1111
+ if (method === 'deleteVariableCollection') {
1112
+ return window.deleteVariableCollection(params.collectionId);
1113
+ }
1114
+ if (method === 'renameVariable') {
1115
+ return window.renameVariable(params.variableId, params.newName);
1116
+ }
1117
+ if (method === 'addMode') {
1118
+ return window.addMode(params.collectionId, params.modeName);
1119
+ }
1120
+ if (method === 'renameMode') {
1121
+ return window.renameMode(params.collectionId, params.modeId, params.newName);
1122
+ }
1123
+ if (method === 'refreshVariables') {
1124
+ return window.refreshVariables();
1125
+ }
1126
+ if (method === 'getLocalComponents') {
1127
+ return window.getLocalComponents(params.currentPageOnly, params.limit);
1128
+ }
1129
+ if (method === 'searchLibraryAssets') {
1130
+ return window.searchLibraryAssets(params.query, params.assetTypes, params.limit, params.currentPageOnly);
1131
+ }
1132
+ if (method === 'getCodeConnectHints') {
1133
+ return window.getCodeConnectHints(params.nodeIds, params.scanCurrentPage, params.maxNodes);
1134
+ }
1135
+ if (method === 'instantiateComponent') {
1136
+ return window.instantiateComponent(params.componentKey, params.options);
1137
+ }
1138
+ if (method === 'setNodeDescription') {
1139
+ return window.setNodeDescription(params.nodeId, params.description, params.descriptionMarkdown);
1140
+ }
1141
+ if (method === 'addComponentProperty') {
1142
+ return window.addComponentProperty(params.nodeId, params.propertyName, params.type, params.defaultValue, params.options);
1143
+ }
1144
+ if (method === 'editComponentProperty') {
1145
+ return window.editComponentProperty(params.nodeId, params.propertyName, params.newValue);
1146
+ }
1147
+ if (method === 'deleteComponentProperty') {
1148
+ return window.deleteComponentProperty(params.nodeId, params.propertyName);
1149
+ }
1150
+ if (method === 'resizeNode') {
1151
+ return window.resizeNode(params.nodeId, params.width, params.height, params.withConstraints);
1152
+ }
1153
+ if (method === 'moveNode') {
1154
+ return window.moveNode(params.nodeId, params.x, params.y);
1155
+ }
1156
+ if (method === 'setNodeFills') {
1157
+ return window.setNodeFills(params.nodeId, params.fills);
1158
+ }
1159
+ if (method === 'setNodeStrokes') {
1160
+ return window.setNodeStrokes(params.nodeId, params.strokes, params.strokeWeight);
1161
+ }
1162
+ if (method === 'setNodeOpacity') {
1163
+ return window.setNodeOpacity(params.nodeId, params.opacity);
1164
+ }
1165
+ if (method === 'setNodeCornerRadius') {
1166
+ return window.setNodeCornerRadius(params.nodeId, params.radius);
1167
+ }
1168
+ if (method === 'cloneNode') {
1169
+ return window.cloneNode(params.nodeId);
1170
+ }
1171
+ if (method === 'deleteNode') {
1172
+ return window.deleteNode(params.nodeId);
1173
+ }
1174
+ if (method === 'renameNode') {
1175
+ return window.renameNode(params.nodeId, params.newName);
1176
+ }
1177
+ if (method === 'setTextContent') {
1178
+ return window.setTextContent(params.nodeId, params.text, params.options);
1179
+ }
1180
+ if (method === 'createChildNode') {
1181
+ return window.createChildNode(params.parentId, params.nodeType, params.properties);
1182
+ }
1183
+ if (method === 'captureScreenshot') {
1184
+ return window.captureScreenshot(params.nodeId, params.options);
1185
+ }
1186
+ if (method === 'setInstanceProperties') {
1187
+ return window.setInstanceProperties(params.nodeId, params.properties);
1188
+ }
1189
+ if (method === 'getDocumentStructure') {
1190
+ return window.getDocumentStructure(params.depth, params.verbosity, params.includeLayout, params.includeVisual, params.includeTypography, params.includeCodeReady, params.outputHint);
1191
+ }
1192
+ if (method === 'getNodeContext') {
1193
+ return window.getNodeContext(params.nodeId, params.depth, params.verbosity, params.includeLayout, params.includeVisual, params.includeTypography, params.includeCodeReady, params.outputHint);
1194
+ }
1195
+ if (method === 'getLocalStyles') {
1196
+ return window.getLocalStyles(params.verbosity);
1197
+ }
1198
+ if (method === 'getConsoleLogs') {
1199
+ return window.getConsoleLogs(params.limit);
1200
+ }
1201
+ if (method === 'clearConsole') {
1202
+ return window.clearConsole();
1203
+ }
1204
+ if (method === 'batchCreateVariables') {
1205
+ return window.batchCreateVariables(params.items);
1206
+ }
1207
+ if (method === 'batchUpdateVariables') {
1208
+ return window.batchUpdateVariables(params.items);
1209
+ }
1210
+ if (method === 'setupDesignTokens') {
1211
+ return window.setupDesignTokens(params);
1212
+ }
1213
+ if (method === 'arrangeComponentSet') {
1214
+ return window.arrangeComponentSet(params.nodeIds);
1215
+ }
1216
+ return { success: false, error: 'Unknown method: ' + method };
1217
+ };
1218
+ var wsRef = mcpBridgeWs;
1219
+ Promise.resolve(run()).then(function(result) {
1220
+ if (wsRef && wsRef.readyState === 1) {
1221
+ try { wsRef.send(JSON.stringify({ id: id, result: result })); } catch (_) {}
1222
+ }
1223
+ }).catch(function(err) {
1224
+ if (wsRef && wsRef.readyState === 1) {
1225
+ try { wsRef.send(JSON.stringify({ id: id, error: err.message || String(err) })); } catch (_) {}
1226
+ }
1227
+ });
1228
+ } catch (e) {
1229
+ if (mcpBridgeWs && mcpBridgeWs.readyState === 1 && msg && msg.id) {
1230
+ try { mcpBridgeWs.send(JSON.stringify({ id: msg.id, error: e.message || String(e) })); } catch (_) {}
1231
+ }
1232
+ }
1233
+ };
1488
1234
  mcpBridgeWs.onclose = function() {
1489
1235
  stopKeepAlive();
1490
1236
  if (mcpBridgeWs !== currentWs) return;
@@ -1520,7 +1266,6 @@
1520
1266
  }
1521
1267
 
1522
1268
  if (typeof WebSocket !== 'undefined') {
1523
- initCloudModeUi();
1524
1269
  initMcpHostInput();
1525
1270
  initMcpPortInput();
1526
1271
  initAdvancedPanel();
@@ -1530,9 +1275,7 @@
1530
1275
  DEFAULT_MCP_PORT = rememberedPort;
1531
1276
  }
1532
1277
  } catch (e) {}
1533
- if (!isCloudMode()) {
1534
- candidatePorts = listCandidatePorts();
1535
- }
1278
+ candidatePorts = listCandidatePorts();
1536
1279
  connectMcpBridge();
1537
1280
  requestUiResize();
1538
1281
  }