# Directory Structure
```
├── .DS_Store
├── .gitignore
├── .prettierrc
├── Dockerfile
├── eslint.config.js
├── package-lock.json
├── package.json
├── README.md
├── smithery.yaml
├── src
│ ├── helpers
│ │ ├── extractComponent.ts
│ │ └── index.ts
│ └── index.ts
└── tsconfig.json
```
# Files
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
node_modules/
build/
.env
```
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
```
{
"semi": false,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5"
}
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
# Figma to React Native MCP
Convert Figma designs to React Native components using Cursor's MCP. This tool extracts components from your Figma designs and generates corresponding React Native components with proper typing and styling.
## Installation
### For Development
Add to your `eas.json`:
```json
{
"mcpServers": {
"figma-to-code": {
"command": "node",
"args": ["PATH_TO_REPO/build/index.js"],
"env": {
"FIGMA_TOKEN": "your_figma_token",
"FIGMA_FILE": "your_figma_file_id",
"PROJECT_DIR": "your_project_directory"
}
}
}
}
```
### For End Users
Install the MCP server in your Cursor IDE:
```bash
npx -y @smithery/cli@latest install @kailashg101/mcp-figma-to-code --client claude --config "{
\"figmaToken\": \"YOUR_FIGMA_TOKEN\",
\"figmaFile\": \"YOUR_FIGMA_FILE_ID\",
\"projectDir\": \"YOUR_PROJECT_DIRECTORY\"
}"
```
## Usage
After installation, you can use the following prompts in Cursor:
### Extract All Components
```
using the extract_components mcp tool get all components from figma and generate their corresponding react native components in components folder
```
### Extract Specific Component
```
using the extract_components mcp tool get the [ComponentName] component from figma and generate its corresponding react native component in components folder
```
## Configuration
The config object accepts the following parameters:
```typescript
{
"figmaToken": string, // Your Figma access token
"figmaFile": string, // Your Figma file ID (from the URL)
"projectDir": string // Where to generate the components
}
```
## Features
Current:
- ✅ Extract components from Figma
- ✅ Generate React Native components
- ✅ Maintain component hierarchy
- ✅ Handle component props and types
- ✅ Support nested components
Coming Soon:
- 🚧 GraphQL schema generation
## Development
To contribute or modify:
1. Clone the repository
2. Install dependencies:
```bash
npm install
```
3. Build:
```bash
npm run build
```
4. Run locally:
```bash
npm start
```
## Environment Variables
When running locally, you'll need these in your `.env`:
```bash
FIGMA_TOKEN=your_figma_token
FIGMA_FILE=your_figma_file_id
PROJECT_DIR=your_project_directory
```
## Error Handling
Common errors and solutions:
- **"Failed to create client"**: Check if all environment variables are properly set
- **"Components page not found"**: Ensure your Figma file has a page named "Components"
- **"Failed to fetch Figma file"**: Verify your Figma token and file ID
## License
MIT
---
For issues and feature requests, please open an issue on GitHub.
```
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "build",
"rootDir": "src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"declaration": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "build"]
}
```
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
```dockerfile
# Generated by https://smithery.ai. See: https://smithery.ai/docs/config#dockerfile
FROM node:lts-alpine
# Create app directory
WORKDIR /app
# Copy package files
COPY package.json package-lock.json ./
# Install dependencies
RUN npm install --ignore-scripts
# Copy source files
COPY tsconfig.json ./
COPY src ./src
# Build the project
RUN npm run build
# Expose port if needed (not used by MCP over stdio)
# Start the MCP server
CMD [ "npm", "start" ]
```
--------------------------------------------------------------------------------
/eslint.config.js:
--------------------------------------------------------------------------------
```javascript
import pluginJs from "@eslint/js";
import prettier from "eslint-config-prettier";
import prettierPlugin from "eslint-plugin-prettier";
export default [
pluginJs.configs.recommended,
prettier, // Disables conflicting ESLint rules
{
plugins: {
prettier: prettierPlugin,
},
rules: {
"prettier/prettier": "error", // Shows Prettier issues as ESLint errors
"no-unused-vars": "warn",
"no-undef": "warn",
"@typescript-eslint/no-unused-vars": ["error"],
},
},
];
```
--------------------------------------------------------------------------------
/smithery.yaml:
--------------------------------------------------------------------------------
```yaml
# Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml
startCommand:
type: stdio
configSchema:
# JSON Schema defining the configuration options for the MCP.
type: object
required:
- figmaToken
- figmaFile
- projectDir
properties:
figmaToken:
type: string
description: Figma API token required to fetch Figma file data
figmaFile:
type: string
description: ID of the Figma file to be analyzed
projectDir:
type: string
description: Directory path for project files where output or assets will be stored
commandFunction:
# A JS function that produces the CLI command based on the given config to start the MCP on stdio.
|-
(config) => ({
command: 'node',
args: ['build/index.js'],
env: {
FIGMA_TOKEN: config.figmaToken,
FIGMA_FILE: config.figmaFile,
PROJECT_DIR: config.projectDir
}
})
exampleConfig:
figmaToken: dummy-figma-token
figmaFile: dummy-figma-file-id
projectDir: /path/to/project
```
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "@kailashg101/mcp-figma-to-code",
"version": "1.0.1",
"description": "MCP server for converting Figma designs to React Native components",
"main": "build/index.js",
"type": "module",
"keywords": [
"mcp",
"figma",
"react-native",
"cursor",
"components",
"design-to-code"
],
"author": "Kailash G",
"license": "MIT",
"bin": {
"mcp-figma": "./build/index.js"
},
"scripts": {
"build": "tsc && chmod 755 build/index.js",
"start": "node build/index.js",
"dev": "ts-node-esm src/index.ts",
"prepublishOnly": "npm run build"
},
"files": [
"build",
"README.md"
],
"publishConfig": {
"access": "public"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.7.0",
"dotenv": "^16.4.5",
"fs-extra": "^11.2.0",
"lodash": "^4.17.21",
"node-fetch": "^3.3.2",
"ws": "^8.16.0",
"zod": "^3.22.4"
},
"devDependencies": {
"@eslint/js": "^9.22.0",
"@types/dotenv": "^8.2.0",
"@types/node": "^20.11.24",
"eslint": "^9.22.0",
"eslint-config-prettier": "^10.1.1",
"eslint-plugin-prettier": "^5.2.3",
"globals": "^16.0.0",
"prettier": "^3.5.3",
"ts-node": "^10.9.2",
"typescript": "^5.3.3",
"typescript-eslint": "^8.26.1"
}
}
```
--------------------------------------------------------------------------------
/src/helpers/index.ts:
--------------------------------------------------------------------------------
```typescript
import fs from 'node:fs'
// Get environment variables
const FIGMA_TOKEN = process.env.FIGMA_TOKEN || ''
const FIGMA_FILE = process.env.FIGMA_FILE || ''
export function camelCaseToDash(string: string) {
return string.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()
}
export async function createFolder(path: string) {
try {
await fs.promises.access(path, fs.constants.F_OK)
} catch (err) {
await fs.promises.mkdir(path)
}
}
export async function fetchSVGURL(id: string) {
const url = `https://api.figma.com/v1/images/${FIGMA_FILE}/?ids=${id}&format=svg`
const headers = { 'X-Figma-Token': FIGMA_TOKEN || '' }
const response = await fetch(url, { headers })
if (!response.ok) {
throw new Error(`Failed to fetch svg url: ${response.statusText}`)
}
const data = await response.json()
return data
}
export async function writeToFile(filename: string, data: string) {
try {
await fs.promises.access(filename, fs.constants.F_OK)
console.log(`File ${filename} already exists. Skipping write.`)
// eslint-disable-next-line no-unused-vars
} catch (error) {
return fs.writeFile(filename, data, (error) => {
if (error) {
console.error(`Error writing file ${filename}: ${error}`)
throw error
}
})
}
}
interface FigmaNode {
id: string
name: string
}
export function findAllByValue(obj: any, valueToFind: string): FigmaNode[] {
return Object.entries(obj).reduce<FigmaNode[]>(
(acc, [key, value]) =>
value === valueToFind
? acc.concat({
id: Object.values(obj.id).join(''),
name: Object.values(obj.name).join(''),
})
: typeof value === 'object' && value !== null
? acc.concat(findAllByValue(value, valueToFind))
: acc,
[]
)
}
// Helper functions for component generation
export function toPascalCase(str: string): string {
return str
.split(/[^a-zA-Z0-9]/g)
.filter(Boolean)
.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
.join('')
}
export const normalizeName = (name: string) =>
name.toLowerCase().replace(/[^a-z0-9]/g, '')
export function toCamelCase(str: string): string {
const pascal = toPascalCase(str)
return pascal.charAt(0).toLowerCase() + pascal.slice(1)
}
export async function fetchFigmaData() {
const response = await fetch(`https://api.figma.com/v1/files/${FIGMA_FILE}`, {
headers: {
'X-Figma-Token': FIGMA_TOKEN,
},
})
if (!response.ok) {
const errorText = await response.text()
return {
isError: true,
content: [
{
type: 'text' as const,
text: `Failed to fetch Figma file: ${response.status} ${response.statusText} - ${errorText}`,
},
],
}
}
return await response.json()
}
```
--------------------------------------------------------------------------------
/src/helpers/extractComponent.ts:
--------------------------------------------------------------------------------
```typescript
import { existsSync } from 'node:fs'
import { normalizeName, toCamelCase, toPascalCase } from './index.js'
import { join } from 'node:path'
const PROJECT_DIR = process.env.PROJECT_DIR || '/'
const componentDir = join(PROJECT_DIR, 'components')
interface ComponentChild {
name: string
type: string
style?: any
fills?: any
children: ComponentChild[]
}
interface ProcessedComponent {
name: string
props: Array<{
name: string
type: string
}>
children: ComponentChild[]
}
const areSameComponent = (name1: string, name2: string): boolean => {
return normalizeName(name1) === normalizeName(name2)
}
function extractComponentChildren(children: any[]): ComponentChild[] {
if (!Array.isArray(children)) return []
return children.map(({ name, children, type, style, fills }) => ({
name,
type,
style,
fills,
children: extractComponentChildren(children || []),
}))
}
function extractComponentProps(children: any[]) {
return children
.flatMap((c: any) => {
const parts = c.name.split(', ')
return parts.map((prop: string) => {
const [key, value] = prop.split('=')
return {
name: toCamelCase(key),
type: value === 'True' || value === 'False' ? 'boolean' : value,
}
})
})
.reduce((acc: Record<string, any>, prop) => {
if (!acc[prop.name]) acc[prop.name] = { ...prop }
else if (!acc[prop.name].type.includes(prop.type))
acc[prop.name].type = `${acc[prop.name].type} | ${prop.type}`
return acc
}, {})
}
export async function generateComponent(
component: any,
validation: boolean = false,
componentToExtract: string = ''
) {
try {
const { document } = component
const componentsPage = document.children.find(
(c: any) => c.name === 'Components'
)
if (!componentsPage) {
console.log('No Components page found in document')
throw new Error('Components page not found in Figma file')
}
const page = componentsPage.children
let componentSets = []
let processedCount = 0
const checkExisting = (componentName: string) =>
validation ? !existsSync(`${componentDir}/${componentName}`) : true
const specificComponent = (
componentName: string,
componentToExtract: string
) =>
componentToExtract
? areSameComponent(componentName, componentToExtract)
: true
for (const section of page) {
const { children } = section
if (!children) continue
for (const item of children) {
const { type, name } = item
const componentName = toPascalCase(name)
if (
type === 'COMPONENT_SET' &&
checkExisting(componentName) &&
specificComponent(componentName, componentToExtract)
) {
processedCount++
try {
const props = extractComponentProps(item.children)
const minified = {
name: componentName,
props,
children: extractComponentChildren(item.children),
}
componentSets.push(minified)
} catch (processError) {
return {
message: `Error processing component ${name}: ${processError}`,
componentSets: [],
}
}
}
}
}
// Create a formatted result for the user
const message = `Successfully processed ${processedCount} components.\n\nComponent sets: ${componentSets.length}\nComponent paths:\n${componentSets.map((cs) => `- ${cs.name}`).join('\n')}`
// Return both the result message and the component data
return {
message,
componentSets,
}
} catch (error) {
console.error(`Error generating component: ${error}`)
throw error
}
}
```
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
```typescript
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
import { z } from 'zod'
import { generateComponent } from './helpers/extractComponent.js'
import { fetchFigmaData } from './helpers/index.js'
// Load environment variables
const logger = {
info: (message: string, meta?: Record<string, any>) => {
console.error(`[INFO] ${message}`, meta ? JSON.stringify(meta) : '')
},
error: (message: string, error?: any) => {
console.error(
`[ERROR] ${message}`,
error instanceof Error ? error.stack : error
)
},
warn: (message: string, meta?: Record<string, any>) => {
console.error(`[WARN] ${message}`, meta ? JSON.stringify(meta) : '')
},
}
// Get environment variables
const FIGMA_TOKEN = process.env.FIGMA_TOKEN
const FIGMA_FILE = process.env.FIGMA_FILE
const PROJECT_DIR = process.env.PROJECT_DIR
if (!FIGMA_TOKEN || !FIGMA_FILE || !PROJECT_DIR) {
console.error(
'Missing required environment variables FIGMA_TOKEN or FIGMA_FILE or PROJECT_DIR'
)
process.exit(1)
}
// Create MCP server with explicit capabilities
const server = new McpServer(
{
name: 'Figma Component Extractor',
version: '1.0.0',
},
{
capabilities: {
tools: {},
},
}
)
// Tool to extract components from Figma file
server.tool(
'extract-components',
'Extract all components from Figma file and get all graphql queries and mutations',
async (extra) => {
try {
// Fetch Figma file data
logger.info('Fetching Figma file data...')
const response = await fetch(
`https://api.figma.com/v1/files/${FIGMA_FILE}`,
{
headers: {
'X-Figma-Token': FIGMA_TOKEN,
},
}
)
if (!response.ok) {
const errorText = await response.text()
throw new Error(
`Failed to fetch Figma file: ${response.status} ${response.statusText} - ${errorText}`
)
}
const data = await response.json()
logger.info('Successfully fetched Figma file data')
// Process the component data
const result = await generateComponent(data)
logger.info('Component extraction successful')
// Return the result to the client
return {
// componentsData: result.componentSets, // Pass the structured component data
content: [
{
type: 'text' as const,
text: result.message,
},
],
}
} catch (error: any) {
logger.error('Error extracting components:', error)
return {
isError: true,
content: [
{
type: 'text' as const,
text: `Error extracting components: ${error.message}`,
},
],
}
}
}
)
server.tool(
'extract-latest-components',
'Extract newly added components from Figma file',
async (extra) => {
try {
// Fetch Figma file data
logger.info('Fetching Figma file data...')
// const data = await response.json()
const data = await fetchFigmaData()
logger.info('Successfully fetched Figma file data')
// Process the component data
const result = await generateComponent(data, true)
logger.info('Component extraction successful')
// Return the result to the client
return {
// componentsData: result.componentSets, // Pass the structured component data
content: [
{
type: 'text' as const,
text: result.message,
},
],
}
} catch (error: any) {
logger.error('Error extracting components:', error)
return {
isError: true,
content: [
{
type: 'text' as const,
text: `Error extracting components: ${error.message}`,
},
],
}
}
}
)
server.tool(
'extract-one-component',
'Extract a single component from Figma file',
{
parameters: z.object({
componentName: z.string(),
}),
},
async ({ parameters: { componentName } }, extra) => {
try {
// Fetch Figma file data
logger.info('Fetching Figma file data...')
// const data = await response.json()
const data = await fetchFigmaData()
logger.info('Successfully fetched Figma file data')
// Process the component data
const result = await generateComponent(data, true, componentName)
logger.info('Component extraction successful')
// Return the result to the client
return {
componentsData: result.componentSets, // Pass the structured component data
content: [
{
type: 'text' as const,
text: result.message,
},
],
}
} catch (error: any) {
logger.error(`Error extracting component ${componentName}:`, error)
return {
isError: true,
content: [
{
type: 'text' as const,
text: `Error extracting component ${componentName}: ${error.message}`,
},
],
}
}
}
)
async function runServer() {
// Start the server with stdio transport
const transport = new StdioServerTransport()
await server.connect(transport)
}
runServer().catch(console.error)
```