figma-code-agent-mcp 1.0.0

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.
Files changed (67) hide show
  1. package/dist/assetServer.d.ts +34 -0
  2. package/dist/assetServer.js +168 -0
  3. package/dist/codeGenerator/componentDetector.d.ts +57 -0
  4. package/dist/codeGenerator/componentDetector.js +171 -0
  5. package/dist/codeGenerator/index.d.ts +77 -0
  6. package/dist/codeGenerator/index.js +184 -0
  7. package/dist/codeGenerator/jsxGenerator.d.ts +46 -0
  8. package/dist/codeGenerator/jsxGenerator.js +182 -0
  9. package/dist/codeGenerator/styleConverter.d.ts +95 -0
  10. package/dist/codeGenerator/styleConverter.js +306 -0
  11. package/dist/hints.d.ts +14 -0
  12. package/dist/hints.js +105 -0
  13. package/dist/hub.d.ts +9 -0
  14. package/dist/hub.js +252 -0
  15. package/dist/index.d.ts +2 -0
  16. package/dist/index.js +146 -0
  17. package/dist/sandbox.d.ts +18 -0
  18. package/dist/sandbox.js +154 -0
  19. package/dist/toolRegistry.d.ts +19 -0
  20. package/dist/toolRegistry.js +729 -0
  21. package/dist/tools/captureScreenshot.d.ts +28 -0
  22. package/dist/tools/captureScreenshot.js +31 -0
  23. package/dist/tools/cloneNode.d.ts +43 -0
  24. package/dist/tools/cloneNode.js +46 -0
  25. package/dist/tools/createFrame.d.ts +157 -0
  26. package/dist/tools/createFrame.js +114 -0
  27. package/dist/tools/createInstance.d.ts +38 -0
  28. package/dist/tools/createInstance.js +41 -0
  29. package/dist/tools/createRectangle.d.ts +108 -0
  30. package/dist/tools/createRectangle.js +77 -0
  31. package/dist/tools/createText.d.ts +81 -0
  32. package/dist/tools/createText.js +67 -0
  33. package/dist/tools/deleteNode.d.ts +15 -0
  34. package/dist/tools/deleteNode.js +18 -0
  35. package/dist/tools/execute.d.ts +17 -0
  36. package/dist/tools/execute.js +68 -0
  37. package/dist/tools/getCurrentPage.d.ts +16 -0
  38. package/dist/tools/getCurrentPage.js +19 -0
  39. package/dist/tools/getDesignContext.d.ts +42 -0
  40. package/dist/tools/getDesignContext.js +55 -0
  41. package/dist/tools/getLocalComponents.d.ts +20 -0
  42. package/dist/tools/getLocalComponents.js +23 -0
  43. package/dist/tools/getNode.d.ts +30 -0
  44. package/dist/tools/getNode.js +33 -0
  45. package/dist/tools/getSelection.d.ts +10 -0
  46. package/dist/tools/getSelection.js +13 -0
  47. package/dist/tools/getStyles.d.ts +17 -0
  48. package/dist/tools/getStyles.js +20 -0
  49. package/dist/tools/getVariables.d.ts +26 -0
  50. package/dist/tools/getVariables.js +29 -0
  51. package/dist/tools/moveNode.d.ts +28 -0
  52. package/dist/tools/moveNode.js +31 -0
  53. package/dist/tools/openInEditor.d.ts +21 -0
  54. package/dist/tools/openInEditor.js +98 -0
  55. package/dist/tools/searchNodes.d.ts +30 -0
  56. package/dist/tools/searchNodes.js +46 -0
  57. package/dist/tools/searchTools.d.ts +28 -0
  58. package/dist/tools/searchTools.js +28 -0
  59. package/dist/tools/swapComponent.d.ts +23 -0
  60. package/dist/tools/swapComponent.js +26 -0
  61. package/dist/tools/updateNode.d.ts +194 -0
  62. package/dist/tools/updateNode.js +163 -0
  63. package/dist/types.d.ts +101 -0
  64. package/dist/types.js +1 -0
  65. package/dist/websocket.d.ts +30 -0
  66. package/dist/websocket.js +282 -0
  67. package/package.json +29 -0
@@ -0,0 +1,34 @@
1
+ declare class AssetServer {
2
+ private server;
3
+ private assets;
4
+ private port;
5
+ private isInitialized;
6
+ initialize(config?: {
7
+ port?: number;
8
+ }): void;
9
+ /**
10
+ * Store an asset and return its URL
11
+ * @param base64Data Base64-encoded image data
12
+ * @param format Image format (png, svg, jpg)
13
+ * @returns URL to access the asset
14
+ */
15
+ storeAsset(base64Data: string, format: 'png' | 'svg' | 'jpg'): string;
16
+ /**
17
+ * Get the base URL for the asset server
18
+ */
19
+ getBaseUrl(): string;
20
+ /**
21
+ * Clear all stored assets (useful for cleanup)
22
+ */
23
+ clearAssets(): void;
24
+ /**
25
+ * Get the number of stored assets
26
+ */
27
+ getAssetCount(): number;
28
+ /**
29
+ * Close the server
30
+ */
31
+ close(): void;
32
+ }
33
+ export declare const assetServer: AssetServer;
34
+ export {};
@@ -0,0 +1,168 @@
1
+ import http from 'http';
2
+ import { v4 as uuidv4 } from 'uuid';
3
+ import { figmaWebSocket } from './websocket.js';
4
+ const DEFAULT_PORT = 3845;
5
+ const MAX_ASSET_SIZE = 10 * 1024 * 1024; // 10MB per asset
6
+ const MAX_ASSETS = 100;
7
+ class AssetServer {
8
+ server = null;
9
+ assets = new Map();
10
+ port = DEFAULT_PORT;
11
+ isInitialized = false;
12
+ initialize(config) {
13
+ if (this.isInitialized) {
14
+ return;
15
+ }
16
+ this.port = config?.port || DEFAULT_PORT;
17
+ this.server = http.createServer((req, res) => {
18
+ // Enable CORS
19
+ req.socket.setTimeout(30000);
20
+ res.setHeader('Access-Control-Allow-Origin', `http://localhost:${this.port}`);
21
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
22
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
23
+ if (req.method === 'OPTIONS') {
24
+ res.writeHead(200);
25
+ res.end();
26
+ return;
27
+ }
28
+ const url = req.url || '';
29
+ // GET /api/status — health check
30
+ if (req.method === 'GET' && url === '/api/status') {
31
+ res.writeHead(200, { 'Content-Type': 'application/json' });
32
+ res.end(JSON.stringify({
33
+ server: 'ok',
34
+ figmaConnected: figmaWebSocket.isConnected(),
35
+ editorType: figmaWebSocket.getEditorType(),
36
+ }));
37
+ return;
38
+ }
39
+ // POST /api/capture — forward create command to Figma
40
+ if (req.method === 'POST' && url === '/api/capture') {
41
+ let body = '';
42
+ let size = 0;
43
+ const MAX_BODY = 5 * 1024 * 1024; // 5MB
44
+ req.on('data', (chunk) => {
45
+ size += chunk.length;
46
+ if (size > MAX_BODY) {
47
+ res.writeHead(413, { 'Content-Type': 'application/json' });
48
+ res.end(JSON.stringify({ error: 'Payload too large' }));
49
+ req.destroy();
50
+ return;
51
+ }
52
+ body += chunk.toString();
53
+ });
54
+ req.on('end', async () => {
55
+ try {
56
+ const payload = JSON.parse(body);
57
+ const result = await figmaWebSocket.sendCommand('create', payload);
58
+ res.writeHead(200, { 'Content-Type': 'application/json' });
59
+ res.end(JSON.stringify({ success: true, result }));
60
+ }
61
+ catch (error) {
62
+ const message = error instanceof Error ? error.message : 'Unknown error';
63
+ res.writeHead(500, { 'Content-Type': 'application/json' });
64
+ res.end(JSON.stringify({ success: false, error: message }));
65
+ }
66
+ });
67
+ return;
68
+ }
69
+ // GET /assets/* — existing asset serving
70
+ if (req.method === 'GET') {
71
+ const match = url.match(/^\/assets\/([a-f0-9-]+)\.(png|svg|jpg)$/i);
72
+ if (!match) {
73
+ res.writeHead(404);
74
+ res.end('Not Found');
75
+ return;
76
+ }
77
+ const assetId = match[1];
78
+ const asset = this.assets.get(assetId);
79
+ if (!asset) {
80
+ res.writeHead(404);
81
+ res.end('Asset Not Found');
82
+ return;
83
+ }
84
+ res.writeHead(200, {
85
+ 'Content-Type': asset.mimeType,
86
+ 'Content-Length': asset.data.length,
87
+ 'Cache-Control': 'public, max-age=3600',
88
+ });
89
+ res.end(asset.data);
90
+ return;
91
+ }
92
+ res.writeHead(404);
93
+ res.end('Not Found');
94
+ });
95
+ this.server.on('error', (error) => {
96
+ if (error.code === 'EADDRINUSE') {
97
+ console.error(`[AssetServer] Port ${this.port} is already in use. Assets will not be served.`);
98
+ }
99
+ else {
100
+ console.error('[AssetServer] Error:', error);
101
+ }
102
+ });
103
+ this.server.listen(this.port, '127.0.0.1', () => {
104
+ console.error(`[AssetServer] Listening on 127.0.0.1:${this.port}`);
105
+ });
106
+ this.isInitialized = true;
107
+ }
108
+ /**
109
+ * Store an asset and return its URL
110
+ * @param base64Data Base64-encoded image data
111
+ * @param format Image format (png, svg, jpg)
112
+ * @returns URL to access the asset
113
+ */
114
+ storeAsset(base64Data, format) {
115
+ if (this.assets.size >= MAX_ASSETS) {
116
+ // Evict oldest asset
117
+ const firstKey = this.assets.keys().next().value;
118
+ if (firstKey)
119
+ this.assets.delete(firstKey);
120
+ }
121
+ const id = uuidv4();
122
+ const buffer = Buffer.from(base64Data, 'base64');
123
+ if (buffer.length > MAX_ASSET_SIZE) {
124
+ throw new Error(`Asset exceeds maximum size of ${MAX_ASSET_SIZE / 1024 / 1024}MB`);
125
+ }
126
+ const mimeTypes = {
127
+ png: 'image/png',
128
+ svg: 'image/svg+xml',
129
+ jpg: 'image/jpeg',
130
+ };
131
+ this.assets.set(id, {
132
+ data: buffer,
133
+ mimeType: mimeTypes[format] || 'application/octet-stream',
134
+ });
135
+ return `${this.getBaseUrl()}/assets/${id}.${format}`;
136
+ }
137
+ /**
138
+ * Get the base URL for the asset server
139
+ */
140
+ getBaseUrl() {
141
+ return `http://localhost:${this.port}`;
142
+ }
143
+ /**
144
+ * Clear all stored assets (useful for cleanup)
145
+ */
146
+ clearAssets() {
147
+ this.assets.clear();
148
+ }
149
+ /**
150
+ * Get the number of stored assets
151
+ */
152
+ getAssetCount() {
153
+ return this.assets.size;
154
+ }
155
+ /**
156
+ * Close the server
157
+ */
158
+ close() {
159
+ if (this.server) {
160
+ this.server.close();
161
+ this.server = null;
162
+ this.isInitialized = false;
163
+ this.assets.clear();
164
+ }
165
+ }
166
+ }
167
+ // Singleton instance
168
+ export const assetServer = new AssetServer();
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Detects Figma components and generates TypeScript props from variants
3
+ */
4
+ import { FigmaNode } from './jsxGenerator.js';
5
+ export interface VariantProperty {
6
+ name: string;
7
+ values: string[];
8
+ propName: string;
9
+ }
10
+ export interface ComponentInfo {
11
+ name: string;
12
+ componentName: string;
13
+ description?: string;
14
+ documentationLinks?: Array<{
15
+ uri: string;
16
+ }>;
17
+ variantProperties?: VariantProperty[];
18
+ propsInterface?: string;
19
+ defaultProps?: Record<string, string>;
20
+ }
21
+ export interface DetectedComponent {
22
+ info: ComponentInfo;
23
+ instances: string[];
24
+ }
25
+ /**
26
+ * Detect all component instances in the node tree
27
+ */
28
+ export declare function detectComponentInstances(node: FigmaNode): Map<string, DetectedComponent>;
29
+ /**
30
+ * Generate TypeScript props interface for a component
31
+ */
32
+ export declare function generatePropsInterface(component: ComponentInfo): string;
33
+ /**
34
+ * Generate a stub component with variant support
35
+ */
36
+ export declare function generateComponentStub(component: ComponentInfo): string;
37
+ export interface NestedComponentImplementation {
38
+ name: string;
39
+ componentName: string;
40
+ code: string;
41
+ description?: string;
42
+ documentationLinks?: Array<{
43
+ uri: string;
44
+ }>;
45
+ }
46
+ /**
47
+ * Generate implementations for all detected components
48
+ */
49
+ export declare function generateNestedComponentImplementations(components: Map<string, DetectedComponent>): NestedComponentImplementation[];
50
+ /**
51
+ * Collect component information for the output metadata
52
+ */
53
+ export declare function collectComponentMetadata(components: Map<string, DetectedComponent>): ComponentInfo[];
54
+ /**
55
+ * Extract variant properties from a COMPONENT_SET node
56
+ */
57
+ export declare function extractVariantPropertiesFromSet(node: FigmaNode): Record<string, string[]> | undefined;
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Detects Figma components and generates TypeScript props from variants
3
+ */
4
+ import { toComponentName, toVariableName } from './jsxGenerator.js';
5
+ /**
6
+ * Detect all component instances in the node tree
7
+ */
8
+ export function detectComponentInstances(node) {
9
+ const components = new Map();
10
+ function traverse(n) {
11
+ if (n.type === 'INSTANCE' && n.componentName) {
12
+ const key = n.componentName;
13
+ if (!components.has(key)) {
14
+ components.set(key, {
15
+ info: {
16
+ name: n.componentName,
17
+ componentName: toComponentName(n.componentName),
18
+ description: n.componentDescription,
19
+ documentationLinks: n.componentDocumentationLinks,
20
+ variantProperties: [],
21
+ },
22
+ instances: [],
23
+ });
24
+ }
25
+ const component = components.get(key);
26
+ component.instances.push(n.id);
27
+ // Collect variant properties from this instance
28
+ if (n.variantProperties) {
29
+ for (const [propName, value] of Object.entries(n.variantProperties)) {
30
+ let variantProp = component.info.variantProperties?.find((p) => p.name === propName);
31
+ if (!variantProp) {
32
+ variantProp = {
33
+ name: propName,
34
+ values: [],
35
+ propName: toVariableName(propName),
36
+ };
37
+ component.info.variantProperties?.push(variantProp);
38
+ }
39
+ if (!variantProp.values.includes(value)) {
40
+ variantProp.values.push(value);
41
+ }
42
+ }
43
+ }
44
+ }
45
+ // Recurse into children
46
+ if (n.children) {
47
+ for (const child of n.children) {
48
+ traverse(child);
49
+ }
50
+ }
51
+ }
52
+ traverse(node);
53
+ return components;
54
+ }
55
+ /**
56
+ * Generate TypeScript props interface for a component
57
+ */
58
+ export function generatePropsInterface(component) {
59
+ const lines = [];
60
+ const typeName = `${component.componentName}Props`;
61
+ lines.push(`type ${typeName} = {`);
62
+ lines.push(' className?: string;');
63
+ if (component.variantProperties && component.variantProperties.length > 0) {
64
+ for (const prop of component.variantProperties) {
65
+ const values = prop.values.map((v) => `"${v}"`).join(' | ');
66
+ lines.push(` ${prop.propName}?: ${values};`);
67
+ }
68
+ }
69
+ lines.push('};');
70
+ return lines.join('\n');
71
+ }
72
+ /**
73
+ * Generate a stub component with variant support
74
+ */
75
+ export function generateComponentStub(component) {
76
+ const lines = [];
77
+ const typeName = `${component.componentName}Props`;
78
+ // Props interface
79
+ lines.push(generatePropsInterface(component));
80
+ lines.push('');
81
+ // Component function
82
+ const defaultValues = [];
83
+ if (component.variantProperties && component.variantProperties.length > 0) {
84
+ for (const prop of component.variantProperties) {
85
+ if (prop.values.length > 0) {
86
+ defaultValues.push(`${prop.propName} = "${prop.values[0]}"`);
87
+ }
88
+ }
89
+ }
90
+ const propsDestructure = defaultValues.length > 0
91
+ ? `{ className, ${defaultValues.join(', ')} }`
92
+ : '{ className }';
93
+ lines.push(`function ${component.componentName}(${propsDestructure}: ${typeName}) {`);
94
+ // Generate variant-based className logic
95
+ if (component.variantProperties && component.variantProperties.length > 0) {
96
+ lines.push(' // Variant-based styling');
97
+ lines.push(' const baseClasses = ""; // Add base styles');
98
+ for (const prop of component.variantProperties) {
99
+ const varName = prop.propName + 'Classes';
100
+ lines.push(` const ${varName} = {`);
101
+ for (const value of prop.values) {
102
+ lines.push(` "${value}": "", // Add ${prop.name}=${value} styles`);
103
+ }
104
+ lines.push(' };');
105
+ }
106
+ lines.push('');
107
+ }
108
+ lines.push(' return (');
109
+ if (component.variantProperties && component.variantProperties.length > 0) {
110
+ const classExpr = component.variantProperties
111
+ .map(p => `\${${p.propName}Classes[${p.propName}]}`)
112
+ .join(' ');
113
+ lines.push(` <div className={\`\${baseClasses} ${classExpr} \${className ?? ""}\`}>`);
114
+ }
115
+ else {
116
+ lines.push(` <div className={className ?? ""}>`);
117
+ }
118
+ lines.push(` {/* ${component.name} content */}`);
119
+ lines.push(' </div>');
120
+ lines.push(' );');
121
+ lines.push('}');
122
+ return lines.join('\n');
123
+ }
124
+ /**
125
+ * Generate implementations for all detected components
126
+ */
127
+ export function generateNestedComponentImplementations(components) {
128
+ const implementations = [];
129
+ for (const [, component] of components) {
130
+ const code = generateComponentStub(component.info);
131
+ implementations.push({
132
+ name: component.info.name,
133
+ componentName: component.info.componentName,
134
+ code,
135
+ description: component.info.description,
136
+ documentationLinks: component.info.documentationLinks,
137
+ });
138
+ }
139
+ return implementations;
140
+ }
141
+ /**
142
+ * Collect component information for the output metadata
143
+ */
144
+ export function collectComponentMetadata(components) {
145
+ const result = [];
146
+ for (const [, component] of components) {
147
+ const info = { ...component.info };
148
+ // Generate the props interface
149
+ info.propsInterface = generatePropsInterface(info);
150
+ // Set default props
151
+ if (info.variantProperties && info.variantProperties.length > 0) {
152
+ info.defaultProps = {};
153
+ for (const prop of info.variantProperties) {
154
+ if (prop.values.length > 0) {
155
+ info.defaultProps[prop.propName] = prop.values[0];
156
+ }
157
+ }
158
+ }
159
+ result.push(info);
160
+ }
161
+ return result;
162
+ }
163
+ /**
164
+ * Extract variant properties from a COMPONENT_SET node
165
+ */
166
+ export function extractVariantPropertiesFromSet(node) {
167
+ if (node.type !== 'COMPONENT_SET') {
168
+ return undefined;
169
+ }
170
+ return node.variantGroupProperties;
171
+ }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Code Generator - Main orchestrator
3
+ *
4
+ * Combines style conversion, JSX generation, and component detection
5
+ * to produce React + Tailwind code from Figma designs.
6
+ */
7
+ import { FigmaNode } from './jsxGenerator.js';
8
+ import { ComponentInfo } from './componentDetector.js';
9
+ export interface DesignContextInput {
10
+ node: FigmaNode;
11
+ images?: Record<string, {
12
+ data: string;
13
+ format: string;
14
+ }>;
15
+ tokens?: {
16
+ colors?: Array<{
17
+ name: string;
18
+ value: string;
19
+ opacity?: number;
20
+ }>;
21
+ typography?: Array<{
22
+ name: string;
23
+ fontFamily: string;
24
+ fontStyle: string;
25
+ fontSize: number;
26
+ lineHeight?: unknown;
27
+ letterSpacing?: unknown;
28
+ }>;
29
+ effects?: Array<{
30
+ name: string;
31
+ effects: unknown[];
32
+ }>;
33
+ };
34
+ }
35
+ export interface GenerateOptions {
36
+ generateNestedComponents?: boolean;
37
+ }
38
+ export interface NestedComponentImpl {
39
+ name: string;
40
+ code: string;
41
+ description?: string;
42
+ documentationLinks?: Array<{
43
+ uri: string;
44
+ }>;
45
+ }
46
+ export interface DesignContextOutput {
47
+ code: string;
48
+ componentName: string;
49
+ components: ComponentInfo[];
50
+ nestedComponents?: NestedComponentImpl[];
51
+ tokens: {
52
+ colors: Array<{
53
+ name: string;
54
+ value: string;
55
+ }>;
56
+ typography: Array<{
57
+ name: string;
58
+ fontFamily: string;
59
+ fontSize: number;
60
+ }>;
61
+ };
62
+ assets: Array<{
63
+ nodeId: string;
64
+ url: string;
65
+ }>;
66
+ }
67
+ /**
68
+ * Main function to generate code from Figma design context
69
+ */
70
+ export declare function generateDesignContext(input: DesignContextInput, options?: GenerateOptions): DesignContextOutput;
71
+ /**
72
+ * Format the output for the MCP tool response
73
+ */
74
+ export declare function formatDesignContextResponse(output: DesignContextOutput, nodeId: string): string;
75
+ export { FigmaNode } from './jsxGenerator.js';
76
+ export { ComponentInfo } from './componentDetector.js';
77
+ export { rgbToHex } from './styleConverter.js';
@@ -0,0 +1,184 @@
1
+ /**
2
+ * Code Generator - Main orchestrator
3
+ *
4
+ * Combines style conversion, JSX generation, and component detection
5
+ * to produce React + Tailwind code from Figma designs.
6
+ */
7
+ import { assetServer } from '../assetServer.js';
8
+ import { generateComponent, toComponentName } from './jsxGenerator.js';
9
+ import { detectComponentInstances, collectComponentMetadata, generateNestedComponentImplementations, } from './componentDetector.js';
10
+ /**
11
+ * Process images and store them in the asset server
12
+ */
13
+ function processImages(images) {
14
+ const imageUrls = new Map();
15
+ if (!images) {
16
+ return imageUrls;
17
+ }
18
+ for (const [nodeId, imageData] of Object.entries(images)) {
19
+ const format = imageData.format;
20
+ const url = assetServer.storeAsset(imageData.data, format);
21
+ imageUrls.set(nodeId, url);
22
+ }
23
+ return imageUrls;
24
+ }
25
+ /**
26
+ * Process design tokens from Figma
27
+ */
28
+ function processTokens(tokens) {
29
+ const result = {
30
+ colors: [],
31
+ typography: [],
32
+ };
33
+ if (!tokens) {
34
+ return result;
35
+ }
36
+ // Process colors
37
+ if (tokens.colors) {
38
+ for (const color of tokens.colors) {
39
+ result.colors.push({
40
+ name: color.name,
41
+ value: color.value,
42
+ });
43
+ }
44
+ }
45
+ // Process typography
46
+ if (tokens.typography) {
47
+ for (const typo of tokens.typography) {
48
+ result.typography.push({
49
+ name: typo.name,
50
+ fontFamily: typo.fontFamily,
51
+ fontSize: typo.fontSize,
52
+ });
53
+ }
54
+ }
55
+ return result;
56
+ }
57
+ /**
58
+ * Main function to generate code from Figma design context
59
+ */
60
+ export function generateDesignContext(input, options = {}) {
61
+ const { node, images, tokens } = input;
62
+ const { generateNestedComponents = true } = options;
63
+ // Process and store images
64
+ const imageUrls = processImages(images);
65
+ // Detect component instances
66
+ const detectedComponents = detectComponentInstances(node);
67
+ const componentMetadata = collectComponentMetadata(detectedComponents);
68
+ // Generate the main component
69
+ const componentName = toComponentName(node.name) || 'Component';
70
+ const generatedComponent = generateComponent(node, imageUrls, componentName);
71
+ // Generate nested component implementations if requested
72
+ let nestedComponents;
73
+ if (generateNestedComponents && detectedComponents.size > 0) {
74
+ const implementations = generateNestedComponentImplementations(detectedComponents);
75
+ nestedComponents = implementations.map(impl => ({
76
+ name: impl.componentName,
77
+ code: impl.code,
78
+ description: impl.description,
79
+ documentationLinks: impl.documentationLinks,
80
+ }));
81
+ }
82
+ // Process tokens
83
+ const processedTokens = processTokens(tokens);
84
+ // Build assets list
85
+ const assets = [];
86
+ for (const [nodeId, url] of imageUrls) {
87
+ assets.push({ nodeId, url });
88
+ }
89
+ return {
90
+ code: generatedComponent.code,
91
+ componentName: generatedComponent.name,
92
+ components: componentMetadata,
93
+ nestedComponents,
94
+ tokens: processedTokens,
95
+ assets,
96
+ };
97
+ }
98
+ /**
99
+ * Format the output for the MCP tool response
100
+ */
101
+ export function formatDesignContextResponse(output, nodeId) {
102
+ const sections = [];
103
+ // Generated React code
104
+ sections.push('## Generated React + Tailwind Code\n');
105
+ sections.push('```tsx');
106
+ sections.push(output.code);
107
+ sections.push('```\n');
108
+ // Nested component implementations
109
+ if (output.nestedComponents && output.nestedComponents.length > 0) {
110
+ sections.push('## Nested Component Implementations\n');
111
+ sections.push('The following components are used in this design:\n');
112
+ for (const comp of output.nestedComponents) {
113
+ sections.push(`### ${comp.name}`);
114
+ if (comp.description) {
115
+ sections.push(`\n${comp.description}\n`);
116
+ }
117
+ if (comp.documentationLinks && comp.documentationLinks.length > 0) {
118
+ sections.push('**Documentation:**');
119
+ for (const link of comp.documentationLinks) {
120
+ sections.push(`- ${link.uri}`);
121
+ }
122
+ sections.push('');
123
+ }
124
+ sections.push('```tsx');
125
+ sections.push(comp.code);
126
+ sections.push('```\n');
127
+ }
128
+ }
129
+ // Component prop types (for components without full implementations)
130
+ if (output.components.length > 0) {
131
+ const componentsWithoutImpl = output.components.filter(c => !output.nestedComponents?.some(nc => nc.name === c.componentName));
132
+ if (componentsWithoutImpl.length > 0) {
133
+ sections.push('## Component Types\n');
134
+ for (const comp of componentsWithoutImpl) {
135
+ sections.push(`### ${comp.componentName}`);
136
+ if (comp.description) {
137
+ sections.push(`\n${comp.description}`);
138
+ }
139
+ if (comp.documentationLinks && comp.documentationLinks.length > 0) {
140
+ sections.push('\n**Documentation:**');
141
+ for (const link of comp.documentationLinks) {
142
+ sections.push(`- ${link.uri}`);
143
+ }
144
+ }
145
+ if (comp.propsInterface) {
146
+ sections.push('\n```typescript');
147
+ sections.push(comp.propsInterface);
148
+ sections.push('```');
149
+ }
150
+ sections.push('');
151
+ }
152
+ }
153
+ }
154
+ // Design tokens summary (inline format like figma-desktop)
155
+ if (output.tokens.colors.length > 0 || output.tokens.typography.length > 0) {
156
+ sections.push('## Design Tokens\n');
157
+ if (output.tokens.colors.length > 0) {
158
+ const colorList = output.tokens.colors
159
+ .map(c => `${c.name}: ${c.value}`)
160
+ .join(', ');
161
+ sections.push(`**Colors:** ${colorList}\n`);
162
+ }
163
+ if (output.tokens.typography.length > 0) {
164
+ const typoList = output.tokens.typography
165
+ .map(t => `${t.name}: ${t.fontFamily} ${t.fontSize}px`)
166
+ .join(', ');
167
+ sections.push(`**Typography:** ${typoList}\n`);
168
+ }
169
+ }
170
+ // Assets
171
+ if (output.assets.length > 0) {
172
+ sections.push('## Assets\n');
173
+ sections.push('The following images are available:');
174
+ for (const asset of output.assets) {
175
+ sections.push(`- Node ${asset.nodeId}: ${asset.url}`);
176
+ }
177
+ sections.push('');
178
+ }
179
+ // Screenshot reminder
180
+ sections.push('---');
181
+ sections.push(`💡 **Tip:** Use \`capture_screenshot\` with nodeId "${nodeId}" to see the visual design for better context.`);
182
+ return sections.join('\n');
183
+ }
184
+ export { rgbToHex } from './styleConverter.js';