#
tokens: 4785/50000 11/11 files
lines: off (toggle) GitHub
raw markdown copy
# 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)

```