@createlex/figma-swiftui-mcp 1.0.7 → 1.0.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/mcp-server.mjs +93 -1
- package/package.json +1 -1
package/companion/mcp-server.mjs
CHANGED
|
@@ -672,7 +672,7 @@ server.registerTool('write_generated_swiftui_to_xcode', {
|
|
|
672
672
|
});
|
|
673
673
|
|
|
674
674
|
server.registerTool('write_selection_to_xcode', {
|
|
675
|
-
description: 'Generate SwiftUI from the connected Figma selection
|
|
675
|
+
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 interactive — replacing raster placeholders with native SwiftUI controls, adding GeometryReader for adaptive layout, and wiring basic interactions.',
|
|
676
676
|
inputSchema: {
|
|
677
677
|
nodeIds: z.array(z.string()).optional().describe('Optional list of Figma node ids. If omitted, uses the current selection'),
|
|
678
678
|
includeOverflow: z.boolean().default(false).describe('Ignore Figma clipping when generating layout'),
|
|
@@ -681,6 +681,29 @@ server.registerTool('write_selection_to_xcode', {
|
|
|
681
681
|
},
|
|
682
682
|
}, async ({ nodeIds, includeOverflow, generationMode, projectPath }) => {
|
|
683
683
|
const targetDir = resolveTargetProjectPath(projectPath);
|
|
684
|
+
|
|
685
|
+
// Try hosted analysis first to get refinement hints alongside code
|
|
686
|
+
let analysisHints = null;
|
|
687
|
+
try {
|
|
688
|
+
const analysis = await tryHostedSemanticGeneration({
|
|
689
|
+
nodeIds,
|
|
690
|
+
generationMode,
|
|
691
|
+
includeOverflow,
|
|
692
|
+
analyze: true,
|
|
693
|
+
});
|
|
694
|
+
if (analysis) {
|
|
695
|
+
analysisHints = {
|
|
696
|
+
generationHints: analysis.generationHints,
|
|
697
|
+
manualRefinementHints: analysis.manualRefinementHints,
|
|
698
|
+
reusableComponents: analysis.reusableComponents,
|
|
699
|
+
assetExportPlan: analysis.assetExportPlan,
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
} catch (err) {
|
|
703
|
+
// Non-fatal — continue with generation even if analysis fails
|
|
704
|
+
console.error('[figma-swiftui-mcp] Hosted analysis failed (non-fatal):', err?.message ?? err);
|
|
705
|
+
}
|
|
706
|
+
|
|
684
707
|
const generated = await callBridge('generate_swiftui', {
|
|
685
708
|
nodeIds,
|
|
686
709
|
includeOverflow,
|
|
@@ -704,14 +727,83 @@ server.registerTool('write_selection_to_xcode', {
|
|
|
704
727
|
throw new Error(result.results.errors.join(' | ') || 'Failed to write generated selection to Xcode');
|
|
705
728
|
}
|
|
706
729
|
|
|
730
|
+
// Build refinement instructions from diagnostics and hints
|
|
731
|
+
const rasterNodes = (generated.diagnostics ?? []).filter(
|
|
732
|
+
(d) => d.rasterized || d.reason?.toLowerCase().includes('raster')
|
|
733
|
+
);
|
|
734
|
+
const refinementInstructions = buildRefinementInstructions({
|
|
735
|
+
diagnostics: generated.diagnostics ?? [],
|
|
736
|
+
rasterNodes,
|
|
737
|
+
analysisHints,
|
|
738
|
+
structName: effectiveStructName,
|
|
739
|
+
filePath: result.results?.swiftFile ?? null,
|
|
740
|
+
});
|
|
741
|
+
|
|
707
742
|
return jsonResult({
|
|
708
743
|
...result,
|
|
709
744
|
structName: effectiveStructName,
|
|
710
745
|
selection: generated.selection ?? null,
|
|
711
746
|
diagnostics: generated.diagnostics ?? [],
|
|
747
|
+
refinementInstructions,
|
|
748
|
+
...(analysisHints ? { analysisHints } : {}),
|
|
712
749
|
});
|
|
713
750
|
});
|
|
714
751
|
|
|
752
|
+
function buildRefinementInstructions({ diagnostics, rasterNodes, analysisHints, structName, filePath }) {
|
|
753
|
+
const instructions = [];
|
|
754
|
+
|
|
755
|
+
// Adaptive layout
|
|
756
|
+
instructions.push(
|
|
757
|
+
`Make ${structName} adaptive: replace any fixed .frame(width:height:) on the root body with GeometryReader or .frame(maxWidth: .infinity, maxHeight: .infinity) so the screen fills any device.`
|
|
758
|
+
);
|
|
759
|
+
|
|
760
|
+
// Raster replacements
|
|
761
|
+
if (rasterNodes.length > 0) {
|
|
762
|
+
instructions.push(
|
|
763
|
+
`${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.`
|
|
764
|
+
);
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
// Hosted analysis hints
|
|
768
|
+
if (analysisHints?.manualRefinementHints?.length) {
|
|
769
|
+
for (const hint of analysisHints.manualRefinementHints) {
|
|
770
|
+
const text = typeof hint === 'string' ? hint : hint.description || hint.message || hint.text || hint.title || null;
|
|
771
|
+
if (text) {
|
|
772
|
+
instructions.push(text);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
if (analysisHints?.generationHints?.interactiveElements?.length) {
|
|
778
|
+
const names = analysisHints.generationHints.interactiveElements
|
|
779
|
+
.map((e) => e.name || e.nodeId || e.id || '(unnamed)')
|
|
780
|
+
.filter(Boolean)
|
|
781
|
+
.join(', ');
|
|
782
|
+
if (names) {
|
|
783
|
+
instructions.push(`These elements likely need interaction: ${names}`);
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
// Reusable components
|
|
788
|
+
if (analysisHints?.reusableComponents?.length) {
|
|
789
|
+
const names = analysisHints.reusableComponents
|
|
790
|
+
.map((c) => c.name || c.componentName || c.id || '(unnamed)')
|
|
791
|
+
.filter(Boolean)
|
|
792
|
+
.join(', ');
|
|
793
|
+
if (names) {
|
|
794
|
+
instructions.push(
|
|
795
|
+
`Consider extracting ${analysisHints.reusableComponents.length} reusable component(s): ${names}`
|
|
796
|
+
);
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
return {
|
|
801
|
+
summary: `After writing ${structName}.swift, apply these refinements to make it production-ready:`,
|
|
802
|
+
steps: instructions,
|
|
803
|
+
filePath,
|
|
804
|
+
};
|
|
805
|
+
}
|
|
806
|
+
|
|
715
807
|
async function shutdownBridgeAndExit(message, exitCode = 1) {
|
|
716
808
|
console.error(message);
|
|
717
809
|
if (authValidationTimer) {
|
package/package.json
CHANGED