@createlex/figma-swiftui-mcp 1.1.0 → 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.
@@ -230,6 +230,55 @@ function startBridgeServer(options = {}) {
230
230
  });
231
231
  });
232
232
 
233
+ // GET /design-context — returns current selection's design context as JSON.
234
+ // Lets any AI tool (Windsurf, a script, a browser) fetch the live Figma context
235
+ // via a simple HTTP GET without needing MCP protocol or copy-paste.
236
+ // Usage: GET http://localhost:7765/design-context?maxDepth=4
237
+ app.get('/design-context', async (req, res) => {
238
+ if (!pluginBridgeClient || pluginBridgeClient.readyState !== WebSocket.OPEN) {
239
+ return res.status(503).json({
240
+ ok: false,
241
+ error: 'No Figma plugin is connected to the bridge. Open the plugin in Figma first.',
242
+ hint: 'npx @createlex/figma-swiftui-mcp start --project ./MyApp',
243
+ });
244
+ }
245
+
246
+ const maxDepth = Math.min(parseInt(req.query.maxDepth, 10) || 4, 8);
247
+ const requestId = `http-ctx-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
248
+
249
+ const response = await new Promise((resolve) => {
250
+ const timeout = setTimeout(() => {
251
+ pendingBridgeRequests.delete(requestId);
252
+ resolve({ ok: false, error: 'Timed out waiting for design context from plugin' });
253
+ }, 20000);
254
+
255
+ pendingBridgeRequests.set(requestId, {
256
+ origin: null,
257
+ action: 'get_design_context',
258
+ resolveCallback: (msg) => {
259
+ clearTimeout(timeout);
260
+ resolve(msg);
261
+ },
262
+ });
263
+
264
+ sendBridgeMessage(pluginBridgeClient, {
265
+ type: 'bridge-request',
266
+ requestId,
267
+ action: 'get_design_context',
268
+ params: { maxDepth, includeScreenshot: false },
269
+ protocolVersion: BRIDGE_PROTOCOL_VERSION,
270
+ timestamp: new Date().toISOString(),
271
+ });
272
+ broadcastBridgeStatus();
273
+ });
274
+
275
+ if (!response.ok) {
276
+ return res.status(502).json({ ok: false, error: response.error || 'Plugin returned an error' });
277
+ }
278
+
279
+ res.json({ ok: true, ...(response.data || {}) });
280
+ });
281
+
233
282
  function sendBridgeMessage(ws, payload) {
234
283
  if (!ws || ws.readyState !== WebSocket.OPEN) {
235
284
  return false;
@@ -497,11 +546,15 @@ function startBridgeServer(options = {}) {
497
546
  }
498
547
 
499
548
  pendingBridgeRequests.delete(message.requestId);
500
- sendBridgeMessage(pending.origin, {
501
- ...message,
502
- protocolVersion: BRIDGE_PROTOCOL_VERSION,
503
- timestamp: new Date().toISOString(),
504
- });
549
+ if (typeof pending.resolveCallback === 'function') {
550
+ pending.resolveCallback(message);
551
+ } else {
552
+ sendBridgeMessage(pending.origin, {
553
+ ...message,
554
+ protocolVersion: BRIDGE_PROTOCOL_VERSION,
555
+ timestamp: new Date().toISOString(),
556
+ });
557
+ }
505
558
  broadcastBridgeStatus();
506
559
  return;
507
560
  }
@@ -706,7 +706,7 @@ server.registerTool('analyze_generation', {
706
706
  });
707
707
 
708
708
  server.registerTool('write_generated_swiftui_to_xcode', {
709
- description: 'Write generated SwiftUI code and optional exported images directly into the configured Xcode project. Supports writing multiple files: use additionalFiles for DesignTokens.swift (dir: "shared"), reusable components (dir: "components"), or extra screen files (dir: "screens"). AI tools generating code with their own model should call this after generating SwiftUI from get_design_context or get_swiftui_generation_prompt.',
709
+ description: 'Write generated SwiftUI code into the configured Xcode project. Images are automatically exported from the current Figma selection and written to Assets.xcassets — you do not need to provide them manually. Supports multiple files: use additionalFiles for DesignTokens.swift (dir: "shared"), reusable components (dir: "components"), or extra screen files (dir: "screens"). AI tools generating code with their own model should call this after generating SwiftUI from get_design_context or the /design-context URL.',
710
710
  inputSchema: {
711
711
  code: z.string().describe('SwiftUI source code for the primary view'),
712
712
  structName: z.string().optional().describe('Optional Swift struct name; if omitted it will be inferred from the code'),
@@ -725,11 +725,31 @@ server.registerTool('write_generated_swiftui_to_xcode', {
725
725
  }, async ({ code, structName, projectPath, images, selectionNames, additionalFiles }) => {
726
726
  const targetDir = resolveTargetProjectPath(projectPath);
727
727
  const effectiveStructName = inferStructName({ structName, code, selectionNames });
728
+
729
+ // Auto-fetch images from the current Figma selection when none were provided.
730
+ // This ensures Assets.xcassets is always populated even when the AI supplied
731
+ // its own SwiftUI code (e.g. from get_design_context / the /design-context URL).
732
+ let effectiveImages = Array.isArray(images) && images.length > 0 ? images : [];
733
+ if (effectiveImages.length === 0) {
734
+ try {
735
+ const generated = await callBridge('generate_swiftui', {
736
+ includeImages: true,
737
+ generationMode: 'editable',
738
+ includeOverflow: false,
739
+ });
740
+ if (Array.isArray(generated.images) && generated.images.length > 0) {
741
+ effectiveImages = generated.images;
742
+ }
743
+ } catch {
744
+ // Non-fatal: bridge may not be connected or selection may have no images.
745
+ }
746
+ }
747
+
728
748
  const result = writeSwiftUIScreen({
729
749
  targetDir,
730
750
  code,
731
751
  structName: effectiveStructName,
732
- images,
752
+ images: effectiveImages,
733
753
  additionalFiles,
734
754
  });
735
755
 
@@ -740,6 +760,7 @@ server.registerTool('write_generated_swiftui_to_xcode', {
740
760
  return jsonResult({
741
761
  ...stripImageData(result),
742
762
  structName: effectiveStructName,
763
+ imagesWritten: effectiveImages.length,
743
764
  });
744
765
  });
745
766
 
@@ -940,8 +961,16 @@ function buildRefinementInstructions({ diagnostics, rasterNodes, analysisHints,
940
961
 
941
962
  // Raster replacements
942
963
  if (rasterNodes.length > 0) {
964
+ const rasterNames = rasterNodes
965
+ .map((d) => d.assetName || d.nodeName || d.name)
966
+ .filter(Boolean)
967
+ .slice(0, 8)
968
+ .join(', ');
943
969
  instructions.push(
944
- `${rasterNodes.length} element(s) were exported as raster images because Figma could not map them to native SwiftUI. Review these and replace obvious UI controls (buttons, text fields, toggles, tab bars) with native SwiftUI equivalents. Keep actual images/illustrations as Image assets.`
970
+ `IMPORTANT: ${rasterNodes.length} element(s) were rasterized and written to Assets.xcassets as PNG files${rasterNames ? ` (${rasterNames})` : ''}. ` +
971
+ `The generated code already references them with Image("name") calls — DO NOT remove or replace these Image() calls. ` +
972
+ `Only replace Image() calls if the asset name clearly identifies a native UI control (button, toggle, tab bar, text field). ` +
973
+ `Photos, illustrations, 3D renders, icons, and decorative graphics MUST stay as Image("name") — do not attempt to reconstruct them with SwiftUI shapes or colors.`
945
974
  );
946
975
  }
947
976
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@createlex/figma-swiftui-mcp",
3
- "version": "1.1.0",
3
+ "version": "1.2.2",
4
4
  "description": "CreateLex MCP runtime for Figma-to-SwiftUI generation and Xcode export",
5
5
  "bin": {
6
6
  "figma-swiftui-mcp": "bin/figma-swiftui-mcp.js"
@@ -41,8 +41,7 @@
41
41
  "zod": "^4.3.6"
42
42
  },
43
43
  "optionalDependencies": {
44
- "@anthropic-ai/sdk": "^0.52.0",
45
- "openai": "^4.100.0"
44
+ "@anthropic-ai/sdk": "^0.52.0"
46
45
  },
47
46
  "devDependencies": {
48
47
  "@figma/plugin-typings": "*",