@atezer/figma-mcp-bridge 1.1.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -9,12 +9,27 @@
9
9
  padding: 0;
10
10
  }
11
11
 
12
+ html, body {
13
+ overflow: hidden !important;
14
+ overflow-x: hidden !important;
15
+ overflow-y: hidden !important;
16
+ /* Scrollbar'ları gizle; içerik ne ise öyle görünsün (taşma yok) */
17
+ -ms-overflow-style: none;
18
+ scrollbar-width: none;
19
+ }
20
+ html::-webkit-scrollbar, body::-webkit-scrollbar {
21
+ display: none;
22
+ }
23
+
12
24
  body {
13
25
  font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
14
26
  font-size: 11px;
15
27
  background: var(--figma-color-bg, #2c2c2c);
16
28
  color: var(--figma-color-text, rgba(255, 255, 255, 0.9));
17
- height: 100%;
29
+ width: fit-content;
30
+ min-width: 0;
31
+ height: auto;
32
+ min-height: 0;
18
33
  display: flex;
19
34
  align-items: center;
20
35
  justify-content: center;
@@ -83,17 +98,29 @@
83
98
  display: flex;
84
99
  flex-direction: column;
85
100
  gap: 6px;
86
- width: 100%;
101
+ width: fit-content;
102
+ min-width: 0;
103
+ max-width: 100%;
104
+ overflow: hidden;
87
105
  }
88
- .bridge-port {
106
+ .bridge-host, .bridge-port {
89
107
  display: flex;
90
108
  align-items: center;
91
109
  gap: 6px;
92
110
  font-size: 10px;
93
111
  }
94
- .bridge-port label {
112
+ .bridge-host label, .bridge-port label {
95
113
  color: var(--figma-color-text-secondary, rgba(255, 255, 255, 0.7));
96
114
  }
115
+ .bridge-host input {
116
+ width: 90px;
117
+ padding: 2px 4px;
118
+ background: var(--figma-color-bg-tertiary, #1e1e1e);
119
+ border: 1px solid var(--figma-color-border, #4a4a4a);
120
+ border-radius: 3px;
121
+ color: inherit;
122
+ font-size: 11px;
123
+ }
97
124
  .bridge-port input {
98
125
  width: 56px;
99
126
  padding: 2px 4px;
@@ -125,16 +152,20 @@
125
152
  </head>
126
153
  <body>
127
154
  <div class="bridge-row">
128
- <div class="bridge-status" id="status-container" title="When 'no server': run MCP server (e.g. npm run dev:local) or set correct port">
155
+ <div class="bridge-status" id="status-container" title="'no server': MCP bridge çalıştığından emin olun. Browser Figma'da da çalışır (aynı makinede localhost, uzakta IP girin).">
129
156
  <div class="status-indicator loading" id="status-dot"></div>
130
157
  <div class="status-text">
131
158
  <span class="label">MCP</span>
132
159
  <span class="state" id="status-state">connecting</span>
133
160
  </div>
134
161
  </div>
135
- <div class="bridge-port" title="Port (multi-instance: her kullanici farkli port)">
162
+ <div class="bridge-host" title="Host: localhost (aynı makine) veya IP adresi (uzak makine). Browser Figma'da da çalışır.">
163
+ <label for="mcp-host">Host</label>
164
+ <input type="text" id="mcp-host" value="localhost" placeholder="localhost" aria-label="MCP bridge host (localhost veya IP)" />
165
+ </div>
166
+ <div class="bridge-port" title="Port: Claude açıkken 5454'te bağlanır. Çoklu kullanıcıda her kullanıcı farklı port (5455–5470) seçer.">
136
167
  <label for="mcp-port">Port</label>
137
- <input type="number" id="mcp-port" min="5454" max="5470" value="5454" />
168
+ <input type="number" id="mcp-port" min="5454" max="5470" value="5454" aria-label="MCP bridge port (5454 varsayılan, Claude açıkken bağlanır)" />
138
169
  </div>
139
170
  </div>
140
171
 
@@ -257,8 +288,11 @@
257
288
  // COMPONENT OPERATIONS
258
289
  // ============================================================================
259
290
 
260
- window.getLocalComponents = () => {
261
- return window.sendPluginCommand('GET_LOCAL_COMPONENTS', {}, 300000)
291
+ window.getLocalComponents = (currentPageOnly, limit) => {
292
+ var params = {};
293
+ params.currentPageOnly = currentPageOnly !== false;
294
+ if (limit != null && limit > 0) params.limit = limit;
295
+ return window.sendPluginCommand('GET_LOCAL_COMPONENTS', params, 120000)
262
296
  .catch(function(err) { return { success: false, error: err.message || String(err) }; });
263
297
  };
264
298
 
@@ -470,7 +504,11 @@
470
504
  case 'VARIABLES_DATA':
471
505
  window.__figmaVariablesData = msg.data;
472
506
  window.__figmaVariablesReady = true;
473
- updateStatus('ready', true, false);
507
+ if (mcpConnectedPort) {
508
+ updateStatus('ready (:' + mcpConnectedPort + ')', true, false);
509
+ } else {
510
+ updateStatus('ready', true, false);
511
+ }
474
512
  console.log('[F-MCP ATezer Bridge] Active - ' + (msg.data.variables?.length || 0) + ' vars');
475
513
  break;
476
514
 
@@ -588,6 +626,9 @@
588
626
  case 'GET_DOCUMENT_STRUCTURE_RESULT':
589
627
  handleResult('GET_DOCUMENT_STRUCTURE', null);
590
628
  break;
629
+ case 'GET_NODE_CONTEXT_RESULT':
630
+ handleResult('GET_NODE_CONTEXT', null);
631
+ break;
591
632
  case 'GET_LOCAL_STYLES_RESULT':
592
633
  handleResult('GET_LOCAL_STYLES', null);
593
634
  break;
@@ -612,8 +653,26 @@
612
653
  }
613
654
  };
614
655
 
615
- window.getDocumentStructure = (depth, verbosity) => {
616
- return window.sendPluginCommand('GET_DOCUMENT_STRUCTURE', { depth: depth || 1, verbosity: verbosity || 'summary' }, 30000).then(function(r) {
656
+ window.getDocumentStructure = (depth, verbosity, includeLayout, includeVisual, includeTypography, includeCodeReady, outputHint) => {
657
+ var payload = { depth: depth || 1, verbosity: verbosity || 'summary' };
658
+ if (includeLayout !== undefined) payload.includeLayout = !!includeLayout;
659
+ if (includeVisual !== undefined) payload.includeVisual = !!includeVisual;
660
+ if (includeTypography !== undefined) payload.includeTypography = !!includeTypography;
661
+ if (includeCodeReady !== undefined) payload.includeCodeReady = !!includeCodeReady;
662
+ if (outputHint !== undefined && outputHint != null) payload.outputHint = outputHint;
663
+ return window.sendPluginCommand('GET_DOCUMENT_STRUCTURE', payload, 90000).then(function(r) {
664
+ if (r && r.success === false) return r;
665
+ return r && r.data !== undefined ? r.data : r;
666
+ });
667
+ };
668
+ window.getNodeContext = (nodeId, depth, verbosity, includeLayout, includeVisual, includeTypography, includeCodeReady, outputHint) => {
669
+ var payload = { nodeId: nodeId, depth: depth || 2, verbosity: verbosity || 'standard' };
670
+ if (includeLayout !== undefined) payload.includeLayout = !!includeLayout;
671
+ if (includeVisual !== undefined) payload.includeVisual = !!includeVisual;
672
+ if (includeTypography !== undefined) payload.includeTypography = !!includeTypography;
673
+ if (includeCodeReady !== undefined) payload.includeCodeReady = !!includeCodeReady;
674
+ if (outputHint !== undefined && outputHint != null) payload.outputHint = outputHint;
675
+ return window.sendPluginCommand('GET_NODE_CONTEXT', payload, 90000).then(function(r) {
617
676
  if (r && r.success === false) return r;
618
677
  return r && r.data !== undefined ? r.data : r;
619
678
  });
@@ -649,11 +708,24 @@
649
708
 
650
709
  // ============================================================================
651
710
  // MCP PLUGIN BRIDGE - WebSocket client (no Figma debug port needed)
652
- // Port configurable for multi-instance: her kullanici kendi portunu secer (5454-5470)
711
+ // Host + Port configurable: browser Figma ve uzak makine destegi
653
712
  // ============================================================================
654
- var MCP_BRIDGE_STORAGE_KEY = 'f-mcp-bridge-port';
713
+ var MCP_BRIDGE_PORT_KEY = 'f-mcp-bridge-port';
714
+ var MCP_BRIDGE_HOST_KEY = 'f-mcp-bridge-host';
655
715
  var mcpBridgeWs = null;
656
716
  var mcpBridgeReconnectTimer = null;
717
+ var mcpReconnectDelay = 1000;
718
+ var MCP_RECONNECT_MIN = 1000;
719
+ var MCP_RECONNECT_MAX = 30000;
720
+ var mcpConnecting = false;
721
+ var mcpConnectedPort = null;
722
+
723
+ function getMcpBridgeHost() {
724
+ var el = document.getElementById('mcp-host');
725
+ if (!el) return 'localhost';
726
+ var v = (el.value || '').trim();
727
+ return v || 'localhost';
728
+ }
657
729
 
658
730
  function getMcpBridgePort() {
659
731
  var el = document.getElementById('mcp-port');
@@ -663,9 +735,42 @@
663
735
  return n;
664
736
  }
665
737
 
738
+ function forceDisconnectAndReconnect() {
739
+ if (mcpBridgeReconnectTimer) { clearTimeout(mcpBridgeReconnectTimer); mcpBridgeReconnectTimer = null; }
740
+ mcpReconnectDelay = MCP_RECONNECT_MIN;
741
+ mcpConnecting = false;
742
+ if (mcpBridgeWs) {
743
+ var old = mcpBridgeWs;
744
+ mcpBridgeWs = null;
745
+ old.onclose = null;
746
+ old.onerror = null;
747
+ old.onmessage = null;
748
+ try { old.close(); } catch (_) {}
749
+ }
750
+ connectMcpBridge();
751
+ }
752
+
753
+ function initMcpHostInput() {
754
+ try {
755
+ var stored = localStorage.getItem(MCP_BRIDGE_HOST_KEY);
756
+ if (stored) {
757
+ var el = document.getElementById('mcp-host');
758
+ if (el) { el.value = stored; }
759
+ }
760
+ } catch (e) {}
761
+ var el = document.getElementById('mcp-host');
762
+ if (el) {
763
+ el.addEventListener('change', function() {
764
+ var host = getMcpBridgeHost();
765
+ try { localStorage.setItem(MCP_BRIDGE_HOST_KEY, host); } catch (e) {}
766
+ forceDisconnectAndReconnect();
767
+ });
768
+ }
769
+ }
770
+
666
771
  function initMcpPortInput() {
667
772
  try {
668
- var stored = localStorage.getItem(MCP_BRIDGE_STORAGE_KEY);
773
+ var stored = localStorage.getItem(MCP_BRIDGE_PORT_KEY);
669
774
  if (stored) {
670
775
  var n = parseInt(stored, 10);
671
776
  if (n >= 5454 && n <= 5470) {
@@ -678,39 +783,96 @@
678
783
  if (el) {
679
784
  el.addEventListener('change', function() {
680
785
  var port = getMcpBridgePort();
681
- try { localStorage.setItem(MCP_BRIDGE_STORAGE_KEY, String(port)); } catch (e) {}
682
- if (mcpBridgeWs) {
683
- mcpBridgeWs.close();
684
- mcpBridgeWs = null;
685
- }
686
- connectMcpBridge();
786
+ try { localStorage.setItem(MCP_BRIDGE_PORT_KEY, String(port)); } catch (e) {}
787
+ forceDisconnectAndReconnect();
687
788
  });
688
789
  }
689
790
  }
690
791
 
792
+ var mcpHandshakeTimer = null;
793
+ var mcpHandshakeDone = false;
794
+ var mcpKeepAliveTimer = null;
795
+
796
+ function scheduleReconnect() {
797
+ if (mcpBridgeReconnectTimer) clearTimeout(mcpBridgeReconnectTimer);
798
+ mcpBridgeReconnectTimer = setTimeout(function() {
799
+ mcpBridgeReconnectTimer = null;
800
+ connectMcpBridge();
801
+ }, mcpReconnectDelay);
802
+ mcpReconnectDelay = Math.min(mcpReconnectDelay * 2, MCP_RECONNECT_MAX);
803
+ }
804
+
805
+ function startKeepAlive(ws) {
806
+ if (mcpKeepAliveTimer) { clearInterval(mcpKeepAliveTimer); mcpKeepAliveTimer = null; }
807
+ mcpKeepAliveTimer = setInterval(function() {
808
+ if (ws && ws.readyState === 1) {
809
+ try { ws.send(JSON.stringify({ type: 'keepalive' })); } catch (_) {}
810
+ } else {
811
+ clearInterval(mcpKeepAliveTimer);
812
+ mcpKeepAliveTimer = null;
813
+ }
814
+ }, 2000);
815
+ }
816
+
817
+ function stopKeepAlive() {
818
+ if (mcpKeepAliveTimer) { clearInterval(mcpKeepAliveTimer); mcpKeepAliveTimer = null; }
819
+ }
820
+
691
821
  function connectMcpBridge() {
692
- if (mcpBridgeWs && mcpBridgeWs.readyState === 1) return;
822
+ if (mcpConnecting) return;
823
+ if (mcpBridgeWs && (mcpBridgeWs.readyState === 0 || mcpBridgeWs.readyState === 1)) return;
824
+ mcpConnecting = true;
825
+ var host = getMcpBridgeHost();
693
826
  var port = getMcpBridgePort();
694
- var url = 'ws://localhost:' + port;
827
+ var url = 'ws://' + host + ':' + port;
828
+ mcpHandshakeDone = false;
695
829
  try {
696
830
  mcpBridgeWs = new WebSocket(url);
831
+ var currentWs = mcpBridgeWs;
697
832
  mcpBridgeWs.onopen = function() {
698
- console.log('[F-MCP ATezer Bridge] Connected to MCP server on port ' + port);
699
- updateStatus('ready', true, false);
700
- mcpBridgeWs.send(JSON.stringify({ type: 'ready' }));
833
+ mcpConnecting = false;
834
+ mcpReconnectDelay = MCP_RECONNECT_MIN;
835
+ console.log('[F-MCP ATezer Bridge] WebSocket opened to ' + url + ', waiting for handshake...');
836
+ updateStatus('connecting...', false, false);
837
+ currentWs.send(JSON.stringify({ type: 'ready' }));
838
+ startKeepAlive(currentWs);
839
+ if (mcpHandshakeTimer) clearTimeout(mcpHandshakeTimer);
840
+ mcpHandshakeTimer = setTimeout(function() {
841
+ if (!mcpHandshakeDone && mcpBridgeWs === currentWs) {
842
+ console.warn('[F-MCP ATezer Bridge] No welcome from server — wrong server or outdated bridge');
843
+ updateStatus('wrong server', false, true);
844
+ }
845
+ }, 5000);
701
846
  };
702
847
  mcpBridgeWs.onmessage = function(event) {
703
848
  try {
704
849
  var msg = JSON.parse(event.data);
850
+ if (msg.type === 'welcome') {
851
+ mcpHandshakeDone = true;
852
+ if (mcpHandshakeTimer) { clearTimeout(mcpHandshakeTimer); mcpHandshakeTimer = null; }
853
+ mcpConnectedPort = msg.port || port;
854
+ console.log('[F-MCP ATezer Bridge] Handshake OK — bridge v' + (msg.bridgeVersion || '?') + ' on port ' + mcpConnectedPort);
855
+ updateStatus('ready (:' + mcpConnectedPort + ')', true, false);
856
+ return;
857
+ }
858
+ if (msg.type === 'ping') {
859
+ if (mcpBridgeWs && mcpBridgeWs.readyState === 1) {
860
+ mcpBridgeWs.send(JSON.stringify({ type: 'pong' }));
861
+ }
862
+ return;
863
+ }
705
864
  if (!msg.id || !msg.method) return;
706
865
  var id = msg.id;
707
866
  var method = msg.method;
708
867
  var params = msg.params || {};
709
868
  var run = function() {
710
869
  if (method === 'getVariablesFromPluginUI') {
711
- if (!window.__figmaVariablesReady || !window.__figmaVariablesData)
712
- return { success: false, error: 'Variables not loaded yet' };
713
- return { success: true, variables: window.__figmaVariablesData.variables, variableCollections: window.__figmaVariablesData.variableCollections };
870
+ // Always refresh first (async API) so variables work in dynamic-page context
871
+ return window.refreshVariables().then(function(r) {
872
+ if (r && r.success === false) return { success: false, error: r.error || 'Variables refresh failed' };
873
+ if (!window.__figmaVariablesData) return { success: false, error: 'Variables not loaded' };
874
+ return { success: true, variables: window.__figmaVariablesData.variables, variableCollections: window.__figmaVariablesData.variableCollections };
875
+ });
714
876
  }
715
877
  if (method === 'getComponentFromPluginUI') {
716
878
  return window.requestComponentData(params.nodeId).then(function(data) {
@@ -758,7 +920,7 @@
758
920
  return window.refreshVariables();
759
921
  }
760
922
  if (method === 'getLocalComponents') {
761
- return window.getLocalComponents();
923
+ return window.getLocalComponents(params.currentPageOnly, params.limit);
762
924
  }
763
925
  if (method === 'instantiateComponent') {
764
926
  return window.instantiateComponent(params.componentKey, params.options);
@@ -815,7 +977,10 @@
815
977
  return window.setInstanceProperties(params.nodeId, params.properties);
816
978
  }
817
979
  if (method === 'getDocumentStructure') {
818
- return window.getDocumentStructure(params.depth, params.verbosity);
980
+ return window.getDocumentStructure(params.depth, params.verbosity, params.includeLayout, params.includeVisual, params.includeTypography, params.includeCodeReady, params.outputHint);
981
+ }
982
+ if (method === 'getNodeContext') {
983
+ return window.getNodeContext(params.nodeId, params.depth, params.verbosity, params.includeLayout, params.includeVisual, params.includeTypography, params.includeCodeReady, params.outputHint);
819
984
  }
820
985
  if (method === 'getLocalStyles') {
821
986
  return window.getLocalStyles(params.verbosity);
@@ -840,35 +1005,53 @@
840
1005
  }
841
1006
  return { success: false, error: 'Unknown method: ' + method };
842
1007
  };
1008
+ var wsRef = mcpBridgeWs;
843
1009
  Promise.resolve(run()).then(function(result) {
844
- mcpBridgeWs.send(JSON.stringify({ id: id, result: result }));
1010
+ if (wsRef && wsRef.readyState === 1) {
1011
+ try { wsRef.send(JSON.stringify({ id: id, result: result })); } catch (_) {}
1012
+ }
845
1013
  }).catch(function(err) {
846
- mcpBridgeWs.send(JSON.stringify({ id: id, error: err.message || String(err) }));
1014
+ if (wsRef && wsRef.readyState === 1) {
1015
+ try { wsRef.send(JSON.stringify({ id: id, error: err.message || String(err) })); } catch (_) {}
1016
+ }
847
1017
  });
848
1018
  } catch (e) {
849
- if (mcpBridgeWs && mcpBridgeWs.readyState === 1 && msg && msg.id)
850
- mcpBridgeWs.send(JSON.stringify({ id: msg.id, error: e.message || String(e) }));
1019
+ if (mcpBridgeWs && mcpBridgeWs.readyState === 1 && msg && msg.id) {
1020
+ try { mcpBridgeWs.send(JSON.stringify({ id: msg.id, error: e.message || String(e) })); } catch (_) {}
1021
+ }
851
1022
  }
852
1023
  };
853
1024
  mcpBridgeWs.onclose = function() {
1025
+ stopKeepAlive();
1026
+ if (mcpBridgeWs !== currentWs) return;
854
1027
  mcpBridgeWs = null;
1028
+ mcpConnecting = false;
1029
+ mcpConnectedPort = null;
1030
+ if (mcpHandshakeTimer) { clearTimeout(mcpHandshakeTimer); mcpHandshakeTimer = null; }
855
1031
  updateStatus('no server', false, true);
856
- if (mcpBridgeReconnectTimer) clearTimeout(mcpBridgeReconnectTimer);
857
- mcpBridgeReconnectTimer = setTimeout(connectMcpBridge, 2000);
1032
+ scheduleReconnect();
858
1033
  };
859
1034
  mcpBridgeWs.onerror = function() {
860
- mcpBridgeWs = null;
861
- updateStatus('no server', false, true);
862
- if (mcpBridgeReconnectTimer) clearTimeout(mcpBridgeReconnectTimer);
863
- mcpBridgeReconnectTimer = setTimeout(connectMcpBridge, 2000);
1035
+ mcpConnecting = false;
864
1036
  };
865
1037
  } catch (e) {
1038
+ mcpConnecting = false;
866
1039
  updateStatus('no server', false, true);
867
- if (mcpBridgeReconnectTimer) clearTimeout(mcpBridgeReconnectTimer);
868
- mcpBridgeReconnectTimer = setTimeout(connectMcpBridge, 2000);
1040
+ scheduleReconnect();
869
1041
  }
870
1042
  }
1043
+
1044
+ if (typeof document !== 'undefined' && document.addEventListener) {
1045
+ document.addEventListener('visibilitychange', function() {
1046
+ if (!document.hidden && (!mcpBridgeWs || mcpBridgeWs.readyState > 1)) {
1047
+ mcpReconnectDelay = MCP_RECONNECT_MIN;
1048
+ connectMcpBridge();
1049
+ }
1050
+ });
1051
+ }
1052
+
871
1053
  if (typeof WebSocket !== 'undefined') {
1054
+ initMcpHostInput();
872
1055
  initMcpPortInput();
873
1056
  connectMcpBridge();
874
1057
  }
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@atezer/figma-mcp-bridge",
3
- "version": "1.1.1",
3
+ "version": "1.2.0",
4
4
  "description": "F-MCP ATezer: MCP server and Figma plugin bridge for Claude/Cursor. No REST token required.",
5
5
  "type": "module",
6
6
  "main": "dist/local.js",
7
7
  "types": "dist/local.d.ts",
8
8
  "bin": {
9
- "figma-mcp-bridge": "./dist/local.js"
9
+ "figma-mcp-bridge": "./dist/local.js",
10
+ "figma-mcp-bridge-plugin": "./dist/local-plugin-only.js"
10
11
  },
11
12
  "files": [
12
13
  "dist",