@mp3wizard/figma-console-mcp 1.20.1 → 1.21.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.
Files changed (36) hide show
  1. package/README.md +9 -9
  2. package/dist/cloudflare/core/cloud-websocket-connector.js +3 -0
  3. package/dist/cloudflare/core/figjam-tools.js +91 -11
  4. package/dist/cloudflare/core/figma-api.js +1 -10
  5. package/dist/cloudflare/core/figma-desktop-connector.js +1 -0
  6. package/dist/cloudflare/core/websocket-connector.js +3 -0
  7. package/dist/cloudflare/core/websocket-server.js +61 -3
  8. package/dist/cloudflare/index.js +8 -10
  9. package/dist/core/figjam-tools.d.ts.map +1 -1
  10. package/dist/core/figjam-tools.js +91 -11
  11. package/dist/core/figjam-tools.js.map +1 -1
  12. package/dist/core/figma-api.d.ts.map +1 -1
  13. package/dist/core/figma-api.js +1 -10
  14. package/dist/core/figma-api.js.map +1 -1
  15. package/dist/core/figma-connector.d.ts +16 -0
  16. package/dist/core/figma-connector.d.ts.map +1 -1
  17. package/dist/core/figma-desktop-connector.d.ts +1 -0
  18. package/dist/core/figma-desktop-connector.d.ts.map +1 -1
  19. package/dist/core/figma-desktop-connector.js +1 -0
  20. package/dist/core/figma-desktop-connector.js.map +1 -1
  21. package/dist/core/websocket-connector.d.ts +16 -0
  22. package/dist/core/websocket-connector.d.ts.map +1 -1
  23. package/dist/core/websocket-connector.js +3 -0
  24. package/dist/core/websocket-connector.js.map +1 -1
  25. package/dist/core/websocket-server.d.ts +18 -1
  26. package/dist/core/websocket-server.d.ts.map +1 -1
  27. package/dist/core/websocket-server.js +61 -3
  28. package/dist/core/websocket-server.js.map +1 -1
  29. package/dist/local.d.ts +13 -0
  30. package/dist/local.d.ts.map +1 -1
  31. package/dist/local.js +129 -19
  32. package/dist/local.js.map +1 -1
  33. package/figma-desktop-bridge/code.js +94 -7
  34. package/figma-desktop-bridge/ui-full.html +24 -0
  35. package/figma-desktop-bridge/ui.html +8 -2
  36. package/package.json +103 -2
@@ -3864,11 +3864,11 @@ figma.ui.onmessage = async (msg) => {
3864
3864
 
3865
3865
  connector.connectorStart = {
3866
3866
  endpointNodeId: msg.startNodeId,
3867
- magnet: 'AUTO'
3867
+ magnet: msg.startMagnet || 'AUTO'
3868
3868
  };
3869
3869
  connector.connectorEnd = {
3870
3870
  endpointNodeId: msg.endNodeId,
3871
- magnet: 'AUTO'
3871
+ magnet: msg.endMagnet || 'AUTO'
3872
3872
  };
3873
3873
 
3874
3874
  // Set label text if provided
@@ -3901,6 +3901,55 @@ figma.ui.onmessage = async (msg) => {
3901
3901
  }
3902
3902
  }
3903
3903
 
3904
+ // CREATE_SECTION - Create a section on FigJam board
3905
+ else if (msg.type === 'CREATE_SECTION') {
3906
+ try {
3907
+ if (__editorType !== 'figjam') {
3908
+ throw new Error('CREATE_SECTION is only available in FigJam files');
3909
+ }
3910
+ console.log('🌉 [Desktop Bridge] Creating section');
3911
+
3912
+ var section = figma.createSection();
3913
+ if (msg.name) section.name = msg.name;
3914
+ if (typeof msg.x === 'number') section.x = msg.x;
3915
+ if (typeof msg.y === 'number') section.y = msg.y;
3916
+ if (typeof msg.width === 'number' && typeof msg.height === 'number') {
3917
+ section.resizeWithoutConstraints(msg.width, msg.height);
3918
+ }
3919
+ if (msg.fillColor) {
3920
+ var scHex = msg.fillColor.replace('#', '');
3921
+ var scR = parseInt(scHex.substring(0, 2), 16) / 255;
3922
+ var scG = parseInt(scHex.substring(2, 4), 16) / 255;
3923
+ var scB = parseInt(scHex.substring(4, 6), 16) / 255;
3924
+ section.fills = [{ type: 'SOLID', color: { r: scR, g: scG, b: scB } }];
3925
+ }
3926
+
3927
+ figma.ui.postMessage({
3928
+ type: 'CREATE_SECTION_RESULT',
3929
+ requestId: msg.requestId,
3930
+ success: true,
3931
+ data: {
3932
+ id: section.id,
3933
+ type: section.type,
3934
+ name: section.name,
3935
+ x: section.x,
3936
+ y: section.y,
3937
+ width: section.width,
3938
+ height: section.height
3939
+ }
3940
+ });
3941
+
3942
+ } catch (error) {
3943
+ console.error('🌉 [Desktop Bridge] Create section error:', error);
3944
+ figma.ui.postMessage({
3945
+ type: 'CREATE_SECTION_RESULT',
3946
+ requestId: msg.requestId,
3947
+ success: false,
3948
+ error: error.message || String(error)
3949
+ });
3950
+ }
3951
+ }
3952
+
3904
3953
  // CREATE_SHAPE_WITH_TEXT - Create a labeled shape
3905
3954
  else if (msg.type === 'CREATE_SHAPE_WITH_TEXT') {
3906
3955
  try {
@@ -3916,7 +3965,45 @@ figma.ui.onmessage = async (msg) => {
3916
3965
  shape.shapeType = msg.shapeType;
3917
3966
  }
3918
3967
 
3919
- // Set text
3968
+ // Set position
3969
+ if (typeof msg.x === 'number') shape.x = msg.x;
3970
+ if (typeof msg.y === 'number') shape.y = msg.y;
3971
+
3972
+ // Resize (before text so text reflows to fit)
3973
+ if (typeof msg.width === 'number' && typeof msg.height === 'number') {
3974
+ shape.resize(msg.width, msg.height);
3975
+ } else if (typeof msg.width === 'number') {
3976
+ shape.resize(msg.width, shape.height);
3977
+ } else if (typeof msg.height === 'number') {
3978
+ shape.resize(shape.width, msg.height);
3979
+ }
3980
+
3981
+ // Fill color
3982
+ if (msg.fillColor) {
3983
+ var fHex = msg.fillColor.replace('#', '');
3984
+ var fR = parseInt(fHex.substring(0, 2), 16) / 255;
3985
+ var fG = parseInt(fHex.substring(2, 4), 16) / 255;
3986
+ var fB = parseInt(fHex.substring(4, 6), 16) / 255;
3987
+ shape.fills = [{ type: 'SOLID', color: { r: fR, g: fG, b: fB } }];
3988
+ }
3989
+
3990
+ // Stroke color
3991
+ if (msg.strokeColor) {
3992
+ var sHex = msg.strokeColor.replace('#', '');
3993
+ var sR = parseInt(sHex.substring(0, 2), 16) / 255;
3994
+ var sG = parseInt(sHex.substring(2, 4), 16) / 255;
3995
+ var sB = parseInt(sHex.substring(4, 6), 16) / 255;
3996
+ shape.strokes = [{ type: 'SOLID', color: { r: sR, g: sG, b: sB } }];
3997
+ shape.strokeWeight = 1;
3998
+ }
3999
+
4000
+ // Dash pattern
4001
+ if (msg.strokeDashPattern) {
4002
+ var parts = msg.strokeDashPattern.split(',').map(function(p) { return parseFloat(p.trim()); });
4003
+ shape.dashPattern = parts;
4004
+ }
4005
+
4006
+ // Set text (after resize so text reflows to fit new size)
3920
4007
  if (msg.text) {
3921
4008
  try {
3922
4009
  await figma.loadFontAsync(shape.text.fontName);
@@ -3925,16 +4012,16 @@ figma.ui.onmessage = async (msg) => {
3925
4012
  shape.text.fontName = { family: 'Inter', style: 'Medium' };
3926
4013
  }
3927
4014
  shape.text.characters = msg.text;
4015
+ if (typeof msg.fontSize === 'number') {
4016
+ shape.text.fontSize = msg.fontSize;
4017
+ }
3928
4018
  }
3929
4019
 
3930
- if (typeof msg.x === 'number') shape.x = msg.x;
3931
- if (typeof msg.y === 'number') shape.y = msg.y;
3932
-
3933
4020
  figma.ui.postMessage({
3934
4021
  type: 'CREATE_SHAPE_WITH_TEXT_RESULT',
3935
4022
  requestId: msg.requestId,
3936
4023
  success: true,
3937
- data: { id: shape.id, type: shape.type, name: shape.name, x: shape.x, y: shape.y }
4024
+ data: { id: shape.id, type: shape.type, name: shape.name, x: shape.x, y: shape.y, width: shape.width, height: shape.height }
3938
4025
  });
3939
4026
 
3940
4027
  } catch (error) {
@@ -828,6 +828,9 @@
828
828
  * Tries the same port first (server may have just restarted),
829
829
  * then does a full rescan to pick up any new servers.
830
830
  */
831
+ // Long-interval reconnect timers — keyed by port to avoid duplicates
832
+ var longIntervalTimers = {};
833
+
831
834
  function wsReconnectPort(port) {
832
835
  try {
833
836
  var testWs = new WebSocket('ws://localhost:' + port);
@@ -839,6 +842,13 @@
839
842
  clearTimeout(timeout);
840
843
  activeConnections.push({ port: port, ws: testWs });
841
844
  updateCompatState();
845
+ // Reset reconnect counter on ANY successful connect (fixes global counter bug)
846
+ wsReconnectAttempts = 0;
847
+ // Cancel long-interval retry for this port
848
+ if (longIntervalTimers[port]) {
849
+ clearInterval(longIntervalTimers[port]);
850
+ delete longIntervalTimers[port];
851
+ }
842
852
  console.log('[MCP Bridge] Reconnected to port ' + port + ' (' + activeConnections.length + ' server(s) total)');
843
853
  attachWsHandlers(testWs, port);
844
854
  initializeConnection(testWs, port);
@@ -915,6 +925,20 @@
915
925
  if (wsReconnectAttempts <= 5) {
916
926
  var delay = Math.min(1000 * wsReconnectAttempts, 5000);
917
927
  setTimeout(function() { wsReconnectPort(port); }, delay);
928
+ } else if (!longIntervalTimers[port]) {
929
+ // After rapid retries exhausted, start a low-frequency background retry.
930
+ // One attempt every 30s — if the MCP server restarts, we'll find it
931
+ // without requiring the user to manually reopen the plugin.
932
+ console.log('[MCP Bridge] Rapid retries exhausted for port ' + port + '. Starting 30s background retry.');
933
+ longIntervalTimers[port] = setInterval(function() {
934
+ if (isPortConnected(port)) {
935
+ // Already reconnected (e.g., via another path) — stop retrying
936
+ clearInterval(longIntervalTimers[port]);
937
+ delete longIntervalTimers[port];
938
+ return;
939
+ }
940
+ wsReconnectPort(port);
941
+ }, 30000);
918
942
  }
919
943
  };
920
944
 
@@ -675,6 +675,9 @@
675
675
  'CREATE_SHAPE_WITH_TEXT': function(params) {
676
676
  return window.sendPluginCommand('CREATE_SHAPE_WITH_TEXT', params);
677
677
  },
678
+ 'CREATE_SECTION': function(params) {
679
+ return window.sendPluginCommand('CREATE_SECTION', params);
680
+ },
678
681
  'CREATE_TABLE': function(params) {
679
682
  return window.sendPluginCommand('CREATE_TABLE', params, 30000);
680
683
  },
@@ -1114,8 +1117,8 @@
1114
1117
  var statusEl = document.getElementById('cloud-status');
1115
1118
  var code = (codeInput.value || '').trim().toUpperCase();
1116
1119
 
1117
- if (!code || code.length < 4) {
1118
- statusEl.textContent = 'Enter pairing code';
1120
+ if (!code || code.length < 6) {
1121
+ statusEl.textContent = 'Pairing code must be 6 characters';
1119
1122
  statusEl.className = 'cloud-status error';
1120
1123
  return;
1121
1124
  }
@@ -1351,6 +1354,9 @@
1351
1354
  case 'CREATE_SHAPE_WITH_TEXT_RESULT':
1352
1355
  handleResult('CREATE_SHAPE_WITH_TEXT', 'data');
1353
1356
  break;
1357
+ case 'CREATE_SECTION_RESULT':
1358
+ handleResult('CREATE_SECTION', 'data');
1359
+ break;
1354
1360
  case 'CREATE_TABLE_RESULT':
1355
1361
  handleResult('CREATE_TABLE', 'data');
1356
1362
  break;
package/package.json CHANGED
@@ -1,97 +1,198 @@
1
1
  {
2
+
2
3
  "name": "@mp3wizard/figma-console-mcp",
3
- "version": "1.20.1",
4
+
5
+ "version": "1.21.2",
6
+
4
7
  "description": "MCP server for accessing Figma plugin console logs and screenshots via Cloudflare Workers or local mode",
8
+
5
9
  "type": "module",
10
+
6
11
  "main": "dist/local.js",
12
+
7
13
  "types": "dist/local.d.ts",
14
+
8
15
  "bin": {
16
+
9
17
  "figma-console-mcp": "./dist/local.js"
18
+
10
19
  },
20
+
11
21
  "files": [
22
+
12
23
  "dist",
24
+
13
25
  "figma-desktop-bridge",
26
+
14
27
  "README.md",
28
+
15
29
  "LICENSE"
30
+
16
31
  ],
32
+
17
33
  "scripts": {
34
+
18
35
  "prepublishOnly": "npm run build",
36
+
19
37
  "deploy": "wrangler deploy",
38
+
20
39
  "dev": "wrangler dev",
40
+
21
41
  "dev:local": "tsx src/local.ts",
42
+
22
43
  "build": "npm run build:local && npm run build:cloudflare && npm run build:apps",
44
+
23
45
  "build:apps": "cross-env APP_NAME=token-browser vite build && cross-env APP_NAME=design-system-dashboard vite build",
46
+
24
47
  "dev:apps": "vite build --watch",
48
+
25
49
  "build:local": "tsc -p tsconfig.local.json",
50
+
26
51
  "build:cloudflare": "tsc -p tsconfig.cloudflare.json",
52
+
27
53
  "start": "wrangler dev",
54
+
28
55
  "test": "jest",
56
+
29
57
  "test:watch": "jest --watch",
58
+
30
59
  "test:coverage": "jest --coverage",
60
+
31
61
  "format": "biome format --write",
62
+
32
63
  "lint:fix": "biome lint --fix",
64
+
33
65
  "cf-typegen": "wrangler types",
66
+
34
67
  "type-check": "tsc --noEmit"
68
+
35
69
  },
70
+
36
71
  "keywords": [
72
+
37
73
  "mcp",
74
+
38
75
  "figma",
76
+
39
77
  "plugin",
78
+
40
79
  "console",
80
+
41
81
  "debugging",
82
+
42
83
  "ai",
84
+
43
85
  "anthropic",
86
+
44
87
  "claude",
88
+
45
89
  "cloudflare",
90
+
46
91
  "workers"
92
+
47
93
  ],
94
+
48
95
  "author": "Your Name",
96
+
49
97
  "license": "MIT",
98
+
50
99
  "repository": {
100
+
51
101
  "type": "git",
102
+
52
103
  "url": "https://github.com/mp3wizard/figma-console-mcp.git"
104
+
53
105
  },
106
+
54
107
  "engines": {
108
+
55
109
  "node": ">=18.0.0"
110
+
56
111
  },
112
+
57
113
  "overrides": {
114
+
58
115
  "path-to-regexp": ">=8.4.1",
116
+
59
117
  "vite": {
118
+
60
119
  "picomatch": ">=4.0.4"
120
+
61
121
  },
122
+
62
123
  "miniflare": {
124
+
63
125
  "undici": ">=7.24.0"
126
+
64
127
  },
128
+
65
129
  "handlebars": ">=4.7.9",
66
- "brace-expansion": ">=1.1.13"
130
+
131
+ "brace-expansion": ">=1.1.13",
132
+
133
+ "lodash": ">=4.18.0",
134
+
135
+ "picomatch": ">=4.0.4"
136
+
67
137
  },
138
+
68
139
  "dependencies": {
140
+
69
141
  "@cloudflare/puppeteer": "^1.0.4",
142
+
70
143
  "@modelcontextprotocol/ext-apps": "^1.0.1",
144
+
71
145
  "@modelcontextprotocol/sdk": "^1.26.0",
146
+
72
147
  "agents": "^0.7.1",
148
+
73
149
  "chrome-remote-interface": "^0.33.2",
150
+
74
151
  "pino": "^9.5.0",
152
+
75
153
  "pino-pretty": "^13.0.0",
154
+
76
155
  "puppeteer-core": "^23.11.1",
156
+
77
157
  "uuid": "^11.0.3",
158
+
78
159
  "ws": "^8.19.0",
160
+
79
161
  "zod": "^3.25.76"
162
+
80
163
  },
164
+
81
165
  "devDependencies": {
166
+
82
167
  "@biomejs/biome": "^2.2.5",
168
+
83
169
  "@types/jest": "^29.5.14",
170
+
84
171
  "@types/node": "^22.10.2",
172
+
85
173
  "@types/uuid": "^10.0.0",
174
+
86
175
  "@types/ws": "^8.18.1",
176
+
87
177
  "cross-env": "^7.0.3",
178
+
88
179
  "jest": "^29.7.0",
180
+
89
181
  "ts-jest": "^29.2.5",
182
+
90
183
  "tsx": "^4.19.2",
184
+
91
185
  "typescript": "5.9.3",
186
+
92
187
  "vite": "^6.0.0",
188
+
93
189
  "vite-plugin-singlefile": "^2.0.0",
190
+
94
191
  "wrangler": "^4.42.0",
192
+
95
193
  "zod-to-json-schema": "^3.25.1"
194
+
96
195
  }
196
+
97
197
  }
198
+