@createlex/figma-swiftui-mcp 1.0.8 → 1.0.9
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 +116 -5
- package/companion/package.json +1 -1
- package/package.json +1 -1
package/companion/mcp-server.mjs
CHANGED
|
@@ -30,6 +30,8 @@ const BRIDGE_HTTP_URL = process.env.FIGMA_SWIFTUI_BRIDGE_HTTP_URL || 'http://loc
|
|
|
30
30
|
const BRIDGE_WS_URL = process.env.FIGMA_SWIFTUI_BRIDGE_WS_URL || 'ws://localhost:7765/bridge';
|
|
31
31
|
const REQUEST_TIMEOUT_MS = Number(process.env.FIGMA_SWIFTUI_BRIDGE_TIMEOUT_MS || 30000);
|
|
32
32
|
const AUTH_REVALIDATION_INTERVAL_MS = Number(process.env.FIGMA_SWIFTUI_AUTH_REVALIDATION_MS || (10 * 60 * 1000));
|
|
33
|
+
const RESPONSE_SIZE_CAP = Number(process.env.FIGMA_SWIFTUI_RESPONSE_SIZE_CAP || 102400); // 100 KB default
|
|
34
|
+
const MAX_DIAGNOSTICS = 20;
|
|
33
35
|
|
|
34
36
|
let bridgeSocket = null;
|
|
35
37
|
let connectPromise = null;
|
|
@@ -666,7 +668,7 @@ server.registerTool('write_generated_swiftui_to_xcode', {
|
|
|
666
668
|
}
|
|
667
669
|
|
|
668
670
|
return jsonResult({
|
|
669
|
-
...result,
|
|
671
|
+
...stripImageData(result),
|
|
670
672
|
structName: effectiveStructName,
|
|
671
673
|
});
|
|
672
674
|
});
|
|
@@ -739,16 +741,125 @@ server.registerTool('write_selection_to_xcode', {
|
|
|
739
741
|
filePath: result.results?.swiftFile ?? null,
|
|
740
742
|
});
|
|
741
743
|
|
|
742
|
-
return jsonResult({
|
|
743
|
-
|
|
744
|
+
return jsonResult(buildCompactResponse({
|
|
745
|
+
result,
|
|
744
746
|
structName: effectiveStructName,
|
|
745
747
|
selection: generated.selection ?? null,
|
|
746
748
|
diagnostics: generated.diagnostics ?? [],
|
|
747
749
|
refinementInstructions,
|
|
748
|
-
|
|
749
|
-
});
|
|
750
|
+
analysisHints,
|
|
751
|
+
}));
|
|
750
752
|
});
|
|
751
753
|
|
|
754
|
+
// ---------------------------------------------------------------------------
|
|
755
|
+
// Response-size mitigation helpers
|
|
756
|
+
// ---------------------------------------------------------------------------
|
|
757
|
+
|
|
758
|
+
/**
|
|
759
|
+
* Remove base64 / svg binary data from any image results.
|
|
760
|
+
* Files are already on disk — the model only needs names and paths.
|
|
761
|
+
*/
|
|
762
|
+
function stripImageData(obj) {
|
|
763
|
+
if (!obj || typeof obj !== 'object') return obj;
|
|
764
|
+
|
|
765
|
+
// Deep-clone to avoid mutating the original
|
|
766
|
+
const clone = JSON.parse(JSON.stringify(obj));
|
|
767
|
+
|
|
768
|
+
const walk = (node) => {
|
|
769
|
+
if (Array.isArray(node)) {
|
|
770
|
+
node.forEach(walk);
|
|
771
|
+
return;
|
|
772
|
+
}
|
|
773
|
+
if (node && typeof node === 'object') {
|
|
774
|
+
delete node.base64;
|
|
775
|
+
delete node.svg;
|
|
776
|
+
delete node.data;
|
|
777
|
+
for (const value of Object.values(node)) {
|
|
778
|
+
walk(value);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
};
|
|
782
|
+
|
|
783
|
+
walk(clone);
|
|
784
|
+
return clone;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
/**
|
|
788
|
+
* Cap the diagnostics array, prioritising rasterized nodes first.
|
|
789
|
+
*/
|
|
790
|
+
function capDiagnostics(diagnostics) {
|
|
791
|
+
if (!Array.isArray(diagnostics) || diagnostics.length <= MAX_DIAGNOSTICS) {
|
|
792
|
+
return diagnostics;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
// Sort: rasterized nodes first (most useful for refinement)
|
|
796
|
+
const sorted = [...diagnostics].sort((a, b) => {
|
|
797
|
+
const aRaster = a.rasterized || a.reason?.toLowerCase().includes('raster') ? 1 : 0;
|
|
798
|
+
const bRaster = b.rasterized || b.reason?.toLowerCase().includes('raster') ? 1 : 0;
|
|
799
|
+
return bRaster - aRaster;
|
|
800
|
+
});
|
|
801
|
+
|
|
802
|
+
const kept = sorted.slice(0, MAX_DIAGNOSTICS);
|
|
803
|
+
kept.push({
|
|
804
|
+
_truncated: true,
|
|
805
|
+
totalCount: diagnostics.length,
|
|
806
|
+
shownCount: MAX_DIAGNOSTICS,
|
|
807
|
+
message: `${diagnostics.length - MAX_DIAGNOSTICS} additional diagnostic(s) omitted to reduce response size.`,
|
|
808
|
+
});
|
|
809
|
+
return kept;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
/**
|
|
813
|
+
* Build a compact MCP response for write_selection_to_xcode.
|
|
814
|
+
*
|
|
815
|
+
* Strategy:
|
|
816
|
+
* 1. Strip all binary (base64/svg) data from image results.
|
|
817
|
+
* 2. Cap diagnostics to MAX_DIAGNOSTICS most relevant entries.
|
|
818
|
+
* 3. Estimate serialised size; if still over RESPONSE_SIZE_CAP,
|
|
819
|
+
* compress analysis hints to summary-only.
|
|
820
|
+
*/
|
|
821
|
+
function buildCompactResponse({ result, structName, selection, diagnostics, refinementInstructions, analysisHints }) {
|
|
822
|
+
const compact = {
|
|
823
|
+
...stripImageData(result),
|
|
824
|
+
structName,
|
|
825
|
+
selection,
|
|
826
|
+
diagnostics: capDiagnostics(diagnostics),
|
|
827
|
+
refinementInstructions,
|
|
828
|
+
...(analysisHints ? { analysisHints } : {}),
|
|
829
|
+
};
|
|
830
|
+
|
|
831
|
+
// First size check
|
|
832
|
+
let serialised = JSON.stringify(compact);
|
|
833
|
+
if (serialised.length <= RESPONSE_SIZE_CAP) {
|
|
834
|
+
return compact;
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
// Compress: remove verbose analysis hints, keep only summary-level data
|
|
838
|
+
if (compact.analysisHints) {
|
|
839
|
+
const { generationHints, manualRefinementHints, ...rest } = compact.analysisHints;
|
|
840
|
+
compact.analysisHints = {
|
|
841
|
+
...rest,
|
|
842
|
+
_compressed: true,
|
|
843
|
+
generationHintCount: generationHints?.interactiveElements?.length ?? 0,
|
|
844
|
+
manualRefinementHintCount: manualRefinementHints?.length ?? 0,
|
|
845
|
+
};
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
// Second size check — drop the full Swift code echo if still too large
|
|
849
|
+
serialised = JSON.stringify(compact);
|
|
850
|
+
if (serialised.length > RESPONSE_SIZE_CAP && compact.results?.swiftFile) {
|
|
851
|
+
// The code is already on disk at swiftFile; no need to echo it
|
|
852
|
+
if (compact.results) {
|
|
853
|
+
compact.results = { ...compact.results };
|
|
854
|
+
delete compact.results.code;
|
|
855
|
+
}
|
|
856
|
+
compact._responseCapped = true;
|
|
857
|
+
compact._responseNote = `Response was compressed to stay under ${Math.round(RESPONSE_SIZE_CAP / 1024)}KB. All files were written successfully to disk.`;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
return compact;
|
|
861
|
+
}
|
|
862
|
+
|
|
752
863
|
function buildRefinementInstructions({ diagnostics, rasterNodes, analysisHints, structName, filePath }) {
|
|
753
864
|
const instructions = [];
|
|
754
865
|
|
package/companion/package.json
CHANGED
package/package.json
CHANGED