@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.
- package/CHANGELOG.md +7 -27
- package/README.md +4 -2
- package/dist/cloudflare/cloud-cors.js +40 -0
- package/dist/cloudflare/cloud-mode-kv.js +86 -0
- package/dist/cloudflare/cloud-mode-routes.js +97 -0
- package/dist/cloudflare/cloud-relay-session.js +141 -0
- package/dist/cloudflare/index.js +243 -4
- package/f-mcp-plugin/README.md +8 -0
- package/f-mcp-plugin/manifest.json +4 -2
- package/f-mcp-plugin/ui.html +450 -193
- package/package.json +3 -2
package/f-mcp-plugin/ui.html
CHANGED
|
@@ -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 =
|
|
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
|
-
|
|
1533
|
+
if (!isCloudMode()) {
|
|
1534
|
+
candidatePorts = listCandidatePorts();
|
|
1535
|
+
}
|
|
1279
1536
|
connectMcpBridge();
|
|
1280
1537
|
requestUiResize();
|
|
1281
1538
|
}
|