@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.
- package/companion/bridge-server.cjs +52 -10
- package/companion/mcp-server.mjs +2 -2
- package/companion/server.js +12 -11
- package/package.json +1 -1
|
@@ -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
|
-
//
|
|
282
|
-
//
|
|
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
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
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: '
|
|
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,
|
package/companion/mcp-server.mjs
CHANGED
|
@@ -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
|
|
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('
|
|
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 }) => {
|
package/companion/server.js
CHANGED
|
@@ -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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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