@atezer/figma-mcp-bridge 1.2.1 → 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.
@@ -172,6 +172,62 @@
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
+
175
231
  /* Light theme support */
176
232
  @media (prefers-color-scheme: light) {
177
233
  body {
@@ -219,6 +275,25 @@
219
275
  </div>
220
276
  <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>
221
277
  </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>
222
297
  </div>
223
298
 
224
299
  <script>
@@ -595,6 +670,7 @@
595
670
  window.__figmaFileKey = msg.fileKey || null;
596
671
  window.__figmaFileName = msg.fileName || null;
597
672
  console.log('[F-MCP ATezer Bridge] File identity: ' + (msg.fileName || '?') + ' (' + (msg.fileKey || '?') + ')');
673
+ pushBridgeFileIdentity();
598
674
  break;
599
675
 
600
676
  case 'VARIABLES_DATA':
@@ -830,6 +906,71 @@
830
906
  var candidatePorts = [];
831
907
  var candidatePortIndex = 0;
832
908
 
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
+
833
974
  /** Port alanı elle değiştirilince manualPortLocked=true olur; bu kilit 5454–5470 taramasını durdurur. */
834
975
  function unlockAutoPortScan() {
835
976
  manualPortLocked = false;
@@ -917,6 +1058,8 @@
917
1058
  }
918
1059
 
919
1060
  function forceDisconnectAndReconnect() {
1061
+ stopCloudPairCountdown();
1062
+ resetCloudHintDefault();
920
1063
  if (mcpBridgeReconnectTimer) { clearTimeout(mcpBridgeReconnectTimer); mcpBridgeReconnectTimer = null; }
921
1064
  mcpReconnectDelay = MCP_RECONNECT_MIN;
922
1065
  candidatePorts = listCandidatePorts();
@@ -978,6 +1121,46 @@
978
1121
  var mcpHandshakeTimer = null;
979
1122
  var mcpHandshakeDone = false;
980
1123
  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
+ }
981
1164
 
982
1165
  function scheduleReconnect() {
983
1166
  if (mcpBridgeReconnectTimer) clearTimeout(mcpBridgeReconnectTimer);
@@ -1004,7 +1187,269 @@
1004
1187
  if (mcpKeepAliveTimer) { clearInterval(mcpKeepAliveTimer); mcpKeepAliveTimer = null; }
1005
1188
  }
1006
1189
 
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
+
1007
1448
  function connectMcpBridge() {
1449
+ if (isCloudMode()) {
1450
+ connectCloudMcpBridge();
1451
+ return;
1452
+ }
1008
1453
  if (mcpConnecting) return;
1009
1454
  if (mcpBridgeWs && (mcpBridgeWs.readyState === 0 || mcpBridgeWs.readyState === 1)) return;
1010
1455
  mcpConnecting = true;
@@ -1039,198 +1484,7 @@
1039
1484
  }
1040
1485
  }, 5000);
1041
1486
  };
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
- };
1487
+ mcpBridgeWs.onmessage = handleMcpBridgeMessage;
1234
1488
  mcpBridgeWs.onclose = function() {
1235
1489
  stopKeepAlive();
1236
1490
  if (mcpBridgeWs !== currentWs) return;
@@ -1266,6 +1520,7 @@
1266
1520
  }
1267
1521
 
1268
1522
  if (typeof WebSocket !== 'undefined') {
1523
+ initCloudModeUi();
1269
1524
  initMcpHostInput();
1270
1525
  initMcpPortInput();
1271
1526
  initAdvancedPanel();
@@ -1275,7 +1530,9 @@
1275
1530
  DEFAULT_MCP_PORT = rememberedPort;
1276
1531
  }
1277
1532
  } catch (e) {}
1278
- candidatePorts = listCandidatePorts();
1533
+ if (!isCloudMode()) {
1534
+ candidatePorts = listCandidatePorts();
1535
+ }
1279
1536
  connectMcpBridge();
1280
1537
  requestUiResize();
1281
1538
  }