@createlex/figma-swiftui-mcp 1.2.5 → 1.2.8

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.
@@ -13,6 +13,7 @@ const {
13
13
  setSavedProjectPath,
14
14
  writeSwiftUIScreen,
15
15
  } = require('./xcode-writer.cjs');
16
+ const { buildGenerationPrompt } = require('./local-llm-generator.cjs');
16
17
 
17
18
  const DEFAULT_PORT = 7765;
18
19
  const DEFAULT_HOST = 'localhost';
@@ -114,7 +115,25 @@ function startBridgeServer(options = {}) {
114
115
  app.use(cors({ origin: '*' }));
115
116
  app.use(express.json({ limit: '50mb' }));
116
117
 
118
+ // Auth state exposed to the plugin UI via /ping
119
+ let authRequired = false;
120
+ let authRequiredReason = null;
121
+
122
+ function setAuthRequired(reason) {
123
+ authRequired = true;
124
+ authRequiredReason = reason || 'Token expired';
125
+ logger.warn(`[figma-swiftui-bridge] Auth required: ${authRequiredReason}`);
126
+ }
127
+
117
128
  app.get('/ping', (req, res) => {
129
+ if (authRequired) {
130
+ return res.status(401).json({
131
+ ok: false,
132
+ authRequired: true,
133
+ authRequiredReason,
134
+ loginCommand: 'npx @createlex/figma-swiftui-mcp login',
135
+ });
136
+ }
118
137
  res.json({
119
138
  ok: true,
120
139
  projectPath: projectPath || null,
@@ -123,6 +142,13 @@ function startBridgeServer(options = {}) {
123
142
  });
124
143
  });
125
144
 
145
+ // Graceful shutdown — called by the plugin UI "Disconnect MCP" to stop the runtime
146
+ app.post('/shutdown', (req, res) => {
147
+ res.json({ ok: true, message: 'Shutting down bridge server…' });
148
+ logger.info('[figma-swiftui-bridge] Shutdown requested by plugin UI');
149
+ setTimeout(() => process.exit(0), 400);
150
+ });
151
+
126
152
  app.get('/bridge/info', (req, res) => {
127
153
  res.json({
128
154
  ok: true,
@@ -278,21 +304,35 @@ function startBridgeServer(options = {}) {
278
304
 
279
305
  const data = response.data || {};
280
306
 
281
- // Attach explicit AI workflow instructions so any agent that fetches this URL
282
- // knows exactly how to generate and write correct SwiftUI code with real images.
307
+ // Build the full generation prompt so AI tools without MCP access
308
+ // can still generate proper native SwiftUI with individual elements.
309
+ let generationPrompt = null;
310
+ try {
311
+ generationPrompt = buildGenerationPrompt(data);
312
+ } catch (_) {
313
+ // Non-fatal — prompt is a bonus, not required
314
+ }
315
+
283
316
  const assetCount = data.assetExportPlan?.candidates?.length ?? 0;
284
317
  const aiWorkflow = {
285
- step1: 'You have the Figma design context. Do NOT generate SwiftUI code from scratch — that produces grey placeholder rectangles instead of real images.',
286
- step2: 'Call the MCP tool `write_selection_to_xcode`. It generates SwiftUI with correct Image("name") asset references AND writes the PNG files to Assets.xcassets automatically.',
287
- step3: 'After write_selection_to_xcode returns, ONLY refine the code it produced: add GeometryReader for responsiveness, wire interactions. DO NOT rewrite Image("name") calls or replace them with Rectangle()/shapes.',
288
- step4: 'If the caller has no MCP access, use the structName and assetExportPlan below to write SwiftUI that references Image("name") for every asset candidate listed.',
289
- mcpTool: 'write_selection_to_xcode',
290
- mcpServer: 'figma-swiftui',
318
+ IF_YOU_HAVE_MCP_ACCESS: {
319
+ step1: 'Call the MCP tool `write_selection_to_xcode` (server: figma-swiftui). It generates SwiftUI with correct Image("name") asset references AND writes the PNG files to Assets.xcassets automatically.',
320
+ step2: 'After write_selection_to_xcode returns, ONLY refine the code it produced: add GeometryReader for responsiveness, wire @State interactions. DO NOT rewrite Image("name") calls or replace them with Rectangle()/shapes.',
321
+ mcpTool: 'write_selection_to_xcode',
322
+ mcpServer: 'figma-swiftui',
323
+ },
324
+ IF_NO_MCP_ACCESS: {
325
+ instruction: 'Use _generationPrompt.systemPrompt as the system message and _generationPrompt.userMessage as the user message to generate native SwiftUI code. Output individual SwiftUI views — Text(), VStack, HStack, ZStack, Image("name") — NOT a single composite Image().',
326
+ rule1: 'Every asset in assetExportPlan.candidates MUST become Image("assetName").resizable() — never Rectangle() or Color.',
327
+ rule2: 'Text nodes become Text("content") with .font(.system(size: X, weight: .bold)) — NEVER custom font names.',
328
+ rule3: 'Output a full SwiftUI View struct with individual elements positioned using the node geometry in the design context.',
329
+ rule4: 'End with #Preview { StructName() }.',
330
+ },
291
331
  assetCandidateCount: assetCount,
292
- warning: 'Photos, 3D renders, illustrations and icons are pre-exported as PNG assets. Always use Image("name") never reconstruct them with SwiftUI shapes or colors.',
332
+ warning: 'DO NOT generate a single Image() wrapping the whole frame. Generate individual SwiftUI elements for each node.',
293
333
  };
294
334
 
295
- res.json({ ok: true, ...data, _aiWorkflow: aiWorkflow });
335
+ res.json({ ok: true, ...data, _generationPrompt: generationPrompt, _aiWorkflow: aiWorkflow });
296
336
  });
297
337
 
298
338
  function sendBridgeMessage(ws, payload) {
@@ -667,6 +707,7 @@ function startBridgeServer(options = {}) {
667
707
  host,
668
708
  alreadyRunning: false,
669
709
  getProjectPath: () => projectPath,
710
+ setAuthRequired,
670
711
  getBridgeInfo: () => ({
671
712
  protocolVersion: BRIDGE_PROTOCOL_VERSION,
672
713
  pluginConnected: !!pluginBridgeClient,
@@ -712,6 +753,7 @@ function startBridgeServer(options = {}) {
712
753
  host,
713
754
  alreadyRunning: false,
714
755
  getProjectPath: () => projectPath,
756
+ setAuthRequired,
715
757
  getBridgeInfo: () => ({
716
758
  protocolVersion: BRIDGE_PROTOCOL_VERSION,
717
759
  pluginConnected: !!pluginBridgeClient,
@@ -765,11 +765,11 @@ server.registerTool('write_generated_swiftui_to_xcode', {
765
765
  });
766
766
 
767
767
  server.registerTool('write_selection_to_xcode', {
768
- description: 'Generate SwiftUI from the connected Figma selection, write it into the configured Xcode project, and return refinement hints. After writing, the AI assistant SHOULD automatically apply the returned refinementInstructions to make the generated code adaptive and interactivereplacing raster placeholders with native SwiftUI controls, adding GeometryReader for adaptive layout, and wiring basic interactions.',
768
+ description: 'Generate SwiftUI from the connected Figma selection and write it into the configured Xcode project. THIS IS THE CORRECT TOOL TO CALL it exports real PNG assets to Assets.xcassets and generates Image("name") references automatically. Use generationMode="fidelity" for designs with blend modes, 3D renders, complex shadows, or photographic images fidelity rasterizes the whole frame as one pixel-perfect PNG instead of trying to reconstruct it with shapes.',
769
769
  inputSchema: {
770
770
  nodeIds: z.array(z.string()).optional().describe('Optional list of Figma node ids. If omitted, uses the current selection'),
771
771
  includeOverflow: z.boolean().default(false).describe('Ignore Figma clipping when generating layout'),
772
- generationMode: z.enum(['editable', 'fidelity']).default('editable').describe('Editable keeps more native SwiftUI structure; fidelity rasterizes more complex layouts'),
772
+ generationMode: z.enum(['editable', 'fidelity']).default('editable').describe('Use fidelity for designs with blend modes, 3D renders, complex shadows, or photographic content — rasterizes the whole frame as one pixel-perfect PNG. Use editable for UI screens with native controls (buttons, lists, forms).'),
773
773
  projectPath: z.string().optional().describe('Optional Xcode source folder override'),
774
774
  },
775
775
  }, async ({ nodeIds, includeOverflow, generationMode, projectPath }) => {
@@ -30,12 +30,13 @@ async function main() {
30
30
  const validation = await validateRuntimeSession(authState);
31
31
  if (!validation.valid) {
32
32
  console.error(`[figma-swiftui-bridge] Authorization lost: ${validation.error}`);
33
- if (bridgeRuntime && typeof bridgeRuntime.close === 'function' && !bridgeRuntime.alreadyRunning) {
34
- await bridgeRuntime.close().catch((error) => {
35
- console.error('[figma-swiftui-bridge] Failed to close bridge cleanly:', error.message);
36
- });
33
+ // Signal the bridge to return authRequired from /ping so the plugin UI
34
+ // can show a "Login required" panel instead of just "runtime off".
35
+ if (bridgeRuntime && typeof bridgeRuntime.setAuthRequired === 'function') {
36
+ bridgeRuntime.setAuthRequired(validation.error || 'Token expired. Run: npx @createlex/figma-swiftui-mcp login');
37
+ } else {
38
+ process.exit(1);
37
39
  }
38
- process.exit(1);
39
40
  return;
40
41
  }
41
42
 
@@ -44,13 +45,13 @@ async function main() {
44
45
  console.log('[figma-swiftui-bridge] Refreshed CreateLex MCP authorization');
45
46
  }
46
47
  } catch (error) {
47
- console.error(`[figma-swiftui-bridge] Authorization revalidation failed: ${error instanceof Error ? error.message : 'unknown_error'}`);
48
- if (bridgeRuntime && typeof bridgeRuntime.close === 'function' && !bridgeRuntime.alreadyRunning) {
49
- await bridgeRuntime.close().catch((closeError) => {
50
- console.error('[figma-swiftui-bridge] Failed to close bridge cleanly:', closeError.message);
51
- });
48
+ const reason = error instanceof Error ? error.message : 'unknown_error';
49
+ console.error(`[figma-swiftui-bridge] Authorization revalidation failed: ${reason}`);
50
+ if (bridgeRuntime && typeof bridgeRuntime.setAuthRequired === 'function') {
51
+ bridgeRuntime.setAuthRequired(`Session revalidation failed: ${reason}`);
52
+ } else {
53
+ process.exit(1);
52
54
  }
53
- process.exit(1);
54
55
  }
55
56
  }, AUTH_REVALIDATION_INTERVAL_MS);
56
57
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@createlex/figma-swiftui-mcp",
3
- "version": "1.2.5",
3
+ "version": "1.2.8",
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"