返回笔记首页

基于Figma的MCP实战操作手册

主题配置

一、现状说明

重要

  1. 手动截图 + AI识别(最简单,马上能用)
  2. Figma API + 自建MCP(需要开发,最灵活)
  3. Figma Inspect导出 + AI解析(折中方案)

二、方案1:手动截图 + AI识别(推荐新手)

这是最简单直接的方式,不需要任何配置。

Step 1: 准备设计稿

在Figma中打开你的设计稿,比如一个用户卡片组件。

假设设计稿长这样:

  • 头像(圆形,64x64px)
  • 用户名(16px,粗体,颜色#333)
  • 职位(14px,常规,颜色#666)
  • 在线状态指示器(绿色小圆点)

Step 2: 截图

Cmd/Ctrl + Shift + 4 截图,保存为 user-card.png

Step 3: 上传给AI

打开Claude,上传图片,然后输入:

plain
这是一个用户卡片的设计稿,帮我生成React组件代码。

【要求】
1. 使用Ant Design
2. 使用CSS Modules写样式
3. 支持在线/离线状态
4. 组件要有TypeScript类型

请提取设计稿中的:
- 尺寸(宽高、间距、圆角)
- 颜色(文字色、背景色、边框色)
- 字体大小和粗细
- 布局方式

Step 4: AI分析并生成代码

AI会分析图片,输出:

UserCard.tsx

typescript
import { Avatar } from 'antd';
import styles from './UserCard.module.css';

interface UserCardProps {
  user: {
    id: string;
    name: string;
    position: string;
    avatar: string;
    isOnline: boolean;
  };
  onClick?: (userId: string) => void;
}

export default function UserCard({ user, onClick }: UserCardProps) {
  return (
    <div
      className={styles.card}
      onClick={() => onClick?.(user.id)}
    >
      <div className={styles.avatarWrapper}>
        <Avatar size={64} src={user.avatar} />
        {user.isOnline && <span className={styles.onlineBadge} />}
      </div>
      <div className={styles.info}>
        <h3 className={styles.name}>{user.name}</h3>
        <p className={styles.position}>{user.position}</p>
      </div>
    </div>
  );
}
UserCard.module.css
css
.card {
    width: 280px;
    padding: 16px;
    background: #ffffff;
    border-radius: 8px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
    cursor: pointer;
    transition: box-shadow 0.3s;
}

.card:hover {
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}

.avatarWrapper {
    position: relative;
    display: inline-block;
    margin-bottom: 12px;
}

.onlineBadge {
    position: absolute;
    bottom: 2px;
    right: 2px;
    width: 12px;
    height: 12px;
    background: #52c41a;
    border: 2px solid #fff;
    border-radius: 50%;
}

.info {
    text-align: center;
}

.name {
    margin: 0 0 4px;
    font-size: 16px;
    font-weight: 600;
    color: #333333;
}

.position {
    margin: 0;
    font-size: 14px;
    color: #666666;
}

Step 5: 调整细节

如果AI生成的某个细节不对,比如间距不准确,直接告诉AI:

plain
名字和职位之间的间距应该是8px,不是4px。还有卡片宽度应该是300px。

AI会立即修正代码。


三、方案2:Figma API + 自建MCP(进阶)

这个方案可以直接读取Figma文件数据,提取精确的设计token。

Step 1: 获取Figma Access Token

  1. 登录Figma
  2. 访问 https://www.figma.com/developers/api#access-tokens
  3. 点击 "Get personal access token"
  4. 生成token,保存好(只显示一次)

Step 2: 获取Figma File Key

打开你的Figma文件,URL类似这样:

plain
https://www.figma.com/file/ABC123xyz/MyDesign

其中 ABC123xyz 就是File Key。

Step 3: 测试Figma API

用curl测试能否访问:

bash
curl -H "X-Figma-Token: YOUR_TOKEN_HERE" \
  https://api.figma.com/v1/files/ABC123xyz

如果返回JSON数据,说明成功。

Step 4: 创建Figma MCP Server

创建项目:

bash
mkdir figma-mcp
cd figma-mcp
npm init -y
npm install @modelcontextprotocol/sdk axios

创建 server.js

javascript
#!/usr/bin/env node

import { Server } from '@modelcontextprotocol/sdk/server/index.js'
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
import axios from 'axios'

const FIGMA_TOKEN = process.env.FIGMA_TOKEN

const server = new Server(
    {
        name: 'figma-mcp-server',
        version: '1.0.0',
    },
    {
        capabilities: {
            tools: {},
        },
    }
)

server.setRequestHandler('tools/list', async () => {
    return {
        tools: [
            {
                name: 'read_figma_file',
                description: '读取Figma文件内容',
                inputSchema: {
                    type: 'object',
                    properties: {
                        fileKey: {
                            type: 'string',
                            description: 'Figma文件的Key(从URL获取)',
                        },
                    },
                    required: ['fileKey'],
                },
            },
            {
                name: 'get_component_styles',
                description: '提取组件的样式信息',
                inputSchema: {
                    type: 'object',
                    properties: {
                        fileKey: { type: 'string' },
                        nodeId: { type: 'string' },
                    },
                    required: ['fileKey', 'nodeId'],
                },
            },
        ],
    }
})

server.setRequestHandler('tools/call', async (request) => {
    const { name, arguments: args } = request.params

    if (name === 'read_figma_file') {
        const { fileKey } = args

        try {
            const response = await axios.get(
                `https://api.figma.com/v1/files/${fileKey}`,
                {
                    headers: { 'X-Figma-Token': FIGMA_TOKEN },
                }
            )

            const file = response.data

            // 提取关键信息
            const summary = {
                name: file.name,
                pages: file.document.children.map((page) => ({
                    name: page.name,
                    components: extractComponents(page),
                })),
            }

            return {
                content: [
                    {
                        type: 'text',
                        text: JSON.stringify(summary, null, 2),
                    },
                ],
            }
        } catch (error) {
            return {
                content: [{ type: 'text', text: `错误: ${error.message}` }],
                isError: true,
            }
        }
    }

    if (name === 'get_component_styles') {
        const { fileKey, nodeId } = args

        try {
            const response = await axios.get(
                `https://api.figma.com/v1/files/${fileKey}/nodes?ids=${nodeId}`,
                {
                    headers: { 'X-Figma-Token': FIGMA_TOKEN },
                }
            )

            const node = response.data.nodes[nodeId].document
            const styles = extractStyles(node)

            return {
                content: [
                    {
                        type: 'text',
                        text: JSON.stringify(styles, null, 2),
                    },
                ],
            }
        } catch (error) {
            return {
                content: [{ type: 'text', text: `错误: ${error.message}` }],
                isError: true,
            }
        }
    }
})

// 提取组件
function extractComponents(node) {
    const components = []

    if (node.type === 'COMPONENT') {
        components.push({
            id: node.id,
            name: node.name,
        })
    }

    if (node.children) {
        node.children.forEach((child) => {
            components.push(...extractComponents(child))
        })
    }

    return components
}

// 提取样式
function extractStyles(node) {
    const styles = {
        name: node.name,
        type: node.type,
    }

    // 尺寸
    if (node.absoluteBoundingBox) {
        styles.width = node.absoluteBoundingBox.width
        styles.height = node.absoluteBoundingBox.height
    }

    // 背景色
    if (node.fills && node.fills.length > 0) {
        const fill = node.fills[0]
        if (fill.type === 'SOLID') {
            styles.backgroundColor = rgbToHex(fill.color)
        }
    }

    // 边框
    if (node.strokes && node.strokes.length > 0) {
        const stroke = node.strokes[0]
        styles.borderColor = rgbToHex(stroke.color)
        styles.borderWidth = node.strokeWeight
    }

    // 圆角
    if (node.cornerRadius !== undefined) {
        styles.borderRadius = node.cornerRadius
    }

    // 文字样式
    if (node.style) {
        styles.fontSize = node.style.fontSize
        styles.fontWeight = node.style.fontWeight
        styles.textColor = rgbToHex(node.style.fills[0]?.color)
    }

    return styles
}

// RGB转HEX
function rgbToHex(color) {
    if (!color) return null
    const r = Math.round(color.r * 255)
    const g = Math.round(color.g * 255)
    const b = Math.round(color.b * 255)
    return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`
}

const transport = new StdioServerTransport()
await server.connect(transport)

修改 package.json

json
{
    "name": "figma-mcp-server",
    "version": "1.0.0",
    "type": "module",
    "bin": {
        "figma-mcp": "./server.js"
    },
    "dependencies": {
        "@modelcontextprotocol/sdk": "^0.5.0",
        "axios": "^1.6.0"
    }
}

安装到全局:

bash
npm link

Step 5: 配置Claude Desktop

编辑配置文件:

json
{
    "mcpServers": {
        "figma": {
            "command": "figma-mcp",
            "env": {
                "FIGMA_TOKEN": "你的Figma Token"
            }
        }
    }
}

重启Claude Desktop。

Step 6: 使用

在Claude中:

plain
使用 read_figma_file 读取我的Figma文件:ABC123xyz

然后找到名为 "UserCard" 的组件,提取它的样式并生成React代码。

AI会调用MCP,读取Figma文件,提取精确的设计数据,生成代码。


四、方案3:Figma Inspect导出(折中方案)

Figma有个Inspect功能,可以查看设计的CSS代码。

Step 1: 打开Inspect

  1. 在Figma中选中组件
  2. 右侧面板切换到 "Inspect" 标签
  3. 代码类型选择 "CSS"

Step 2: 复制CSS

Figma会显示类似这样的代码:

css
/* UserCard */
width: 280px;
height: 140px;
background: #ffffff;
box-shadow: 0px 2px 8px rgba(0, 0, 0, 0.1);
border-radius: 8px;

/* Avatar */
width: 64px;
height: 64px;
border-radius: 32px;

/* Name */
font-family: 'PingFang SC';
font-style: normal;
font-weight: 600;
font-size: 16px;
line-height: 22px;
color: #333333;

/* Position */
font-family: 'PingFang SC';
font-style: normal;
font-weight: 400;
font-size: 14px;
line-height: 20px;
color: #666666;

Step 3: 给AI转换

plain
这是Figma导出的CSS,帮我转换成React组件。

【CSS】
{粘贴上面的CSS}

【要求】
1. 生成React组件和CSS Modules
2. 组件要有TypeScript类型
3. 使用Ant Design的Avatar组件
4. 布局用Flexbox

Step 4: AI生成组件

AI会根据CSS生成完整的组件代码。


五、实战案例:生成Dashboard卡片

假设有这样的设计稿

一个数据卡片,包含:

  • 图标(左上角)
  • 标题(大号文字)
  • 数值(超大号文字)
  • 增长百分比(绿色/红色,带箭头)
  • 对比昨日文字(小号灰色)

方式1:截图给AI

上传截图,输入:

plain
这是一个数据卡片组件,生成代码。

【功能需求】
1. 支持不同类型的数据(用户数、订单数、营收等)
2. 增长/下降用不同颜色(绿色上涨、红色下跌)
3. 支持点击事件
4. 支持loading状态

【技术要求】
- React + TypeScript
- Ant Design Icons
- CSS Modules
- 响应式(移动端自适应)

AI生成代码

StatCard.tsx

typescript
import { ArrowUpOutlined, ArrowDownOutlined } from '@ant-design/icons';
import { Spin } from 'antd';
import styles from './StatCard.module.css';

interface StatCardProps {
  icon: React.ReactNode;
  title: string;
  value: number | string;
  change: number;
  compareText?: string;
  loading?: boolean;
  onClick?: () => void;
}

export default function StatCard({
  icon,
  title,
  value,
  change,
  compareText = '较昨日',
  loading = false,
  onClick,
}: StatCardProps) {
  const isPositive = change >= 0;

  if (loading) {
    return (
      <div className={styles.card}>
        <Spin />
      </div>
    );
  }

  return (
    <div className={styles.card} onClick={onClick}>
      <div className={styles.header}>
        <span className={styles.icon}>{icon}</span>
        <span className={styles.title}>{title}</span>
      </div>

      <div className={styles.value}>{value}</div>

      <div className={styles.footer}>
        <span className={`${styles.change} ${isPositive ? styles.positive : styles.negative}`}>
          {isPositive ? <ArrowUpOutlined /> : <ArrowDownOutlined />}
          {Math.abs(change)}%
        </span>
        <span className={styles.compareText}>{compareText}</span>
      </div>
    </div>
  );
}
StatCard.module.css
css
.card {
    padding: 24px;
    background: #ffffff;
    border-radius: 8px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
    cursor: pointer;
    transition: all 0.3s;
    min-height: 160px;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
}

.card:hover {
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
    transform: translateY(-2px);
}

.header {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 16px;
}

.icon {
    font-size: 24px;
    color: #1890ff;
}

.title {
    font-size: 14px;
    color: #666666;
}

.value {
    font-size: 32px;
    font-weight: 600;
    color: #333333;
    margin-bottom: 12px;
}

.footer {
    display: flex;
    align-items: center;
    gap: 8px;
}

.change {
    display: flex;
    align-items: center;
    gap: 4px;
    font-size: 14px;
    font-weight: 500;
}

.positive {
    color: #52c41a;
}

.negative {
    color: #ff4d4f;
}

.compareText {
    font-size: 12px;
    color: #999999;
}

/* 响应式 */
@media (max-width: 768px) {
    .card {
        padding: 16px;
        min-height: 140px;
    }

    .value {
        font-size: 24px;
    }

    .icon {
        font-size: 20px;
    }
}
使用示例
typescript
import { UserOutlined, ShoppingOutlined, DollarOutlined } from '@ant-design/icons';
import StatCard from '@/components/StatCard';

export default function Dashboard() {
  return (
    <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 16 }}>
      <StatCard
        icon={<UserOutlined />}
        title="总用户数"
        value="12,345"
        change={8.5}
        onClick={() => console.log('点击用户卡片')}
      />

      <StatCard
        icon={<ShoppingOutlined />}
        title="今日订单"
        value="234"
        change={-3.2}
      />

      <StatCard
        icon={<DollarOutlined />}
        title="今日营收"
        value="¥45,678"
        change={12.3}
      />
    </div>
  );
}

六、设计Token提取

从Figma提取颜色规范

方式1:手动整理

在Figma中,左侧有 "Local styles",可以看到所有颜色。

记录下来:

plain
Primary: #1890FF
Success: #52C41A
Warning: #FAAD14
Error: #FF4D4F
Text/Primary: #333333
Text/Secondary: #666666
Text/Disabled: #999999
Border: #D9D9D9
Background: #F0F2F5
方式2:用Figma API提取
javascript
// 在MCP Server里添加
async function getStylesFromFile(fileKey) {
    const response = await axios.get(
        `https://api.figma.com/v1/files/${fileKey}/styles`,
        {
            headers: { 'X-Figma-Token': FIGMA_TOKEN },
        }
    )

    const styles = response.data.meta.styles
    const colors = {}

    for (const style of styles) {
        if (style.style_type === 'FILL') {
            colors[style.name] = await getStyleColor(fileKey, style.node_id)
        }
    }

    return colors
}

生成Design Tokens文件

拿到颜色后,让AI生成:

plain
根据这些颜色生成Design Tokens文件。

【颜色】
Primary: #1890FF
Success: #52C41A
...

【要求】
1. 生成CSS变量版本
2. 生成TypeScript对象版本
3. 生成Tailwind配置版本

AI会生成:

tokens.css

css
:root {
    --color-primary: #1890ff;
    --color-success: #52c41a;
    --color-warning: #faad14;
    --color-error: #ff4d4f;
    --color-text-primary: #333333;
    --color-text-secondary: #666666;
    --color-border: #d9d9d9;
    --color-background: #f0f2f5;
}
tokens.ts
typescript
export const colors = {
    primary: '#1890FF',
    success: '#52C41A',
    warning: '#FAAD14',
    error: '#FF4D4F',
    text: {
        primary: '#333333',
        secondary: '#666666',
        disabled: '#999999',
    },
    border: '#D9D9D9',
    background: '#F0F2F5',
} as const
tailwind.config.js
javascript
module.exports = {
    theme: {
        extend: {
            colors: {
                primary: '#1890FF',
                success: '#52C41A',
                warning: '#FAAD14',
                error: '#FF4D4F',
            },
        },
    },
}

七、常见问题

Q1: AI识别的尺寸不准确怎么办?

方法1: 在截图上标注尺寸

用Figma的测量工具(Alt/Option拖动),显示间距,然后截图。

方法2: 明确告诉AI

plain
卡片宽度不是280px,是300px。内边距是20px不是16px。

Q2: 复杂布局AI生成不对怎么办?

分步骤

plain
第一步:先生成整体布局,不要细节
第二步:逐个生成内部组件
第三步:组合起来

Q3: 设计稿有动画效果,AI能生成吗?

可以,但需要你描述:

plain
这个卡片hover时有动画:
1. 阴影从2px变成4px
2. 整体向上移动2px
3. 过渡时间0.3秒

请加上这些动画。

Q4: 响应式怎么处理?

告诉AI断点:

plain
断点:
- 移动端:< 768px
- 平板:768px - 1024px
- 桌面:> 1024px

移动端:
- 卡片宽度100%
- 字体缩小2px
- 内边距减少

八、实用技巧

技巧1:建立设计规范库

把常用的设计元素整理成文档:

markdown
# 设计规范

## 颜色

- Primary: #1890FF
- ...

## 间距

- xs: 4px
- sm: 8px
- md: 12px
- lg: 16px
- xl: 24px

## 圆角

- small: 4px
- medium: 8px
- large: 12px

## 字体

- h1: 32px, 600
- h2: 24px, 600
- body: 14px, 400
- small: 12px, 400

每次让AI生成组件时,附上这个规范。

技巧2:截图带标注

Figma有个功能叫 "Redlines"(标注线),开启后截图,AI能看到精确尺寸。

技巧3:先低保真,再高保真

第一步:用简单的线框图生成基础结构 第二步:加上设计稿细节

这样AI生成的代码结构更清晰。


九、总结

Figma转代码的三种方案对比:

方案 优点 缺点 适合场景
截图+AI 最简单,马上能用 尺寸可能不准 快速原型
Figma API 数据最准确 需要开发MCP 大型项目
Inspect导出 有官方支持 需要手动操作 中小项目

建议

  • 新手从截图开始
  • 熟练后用Inspect
  • 团队项目考虑开发MCP

核心思路:让AI看到设计,AI就能生成代码。 不管用哪种方式,把设计给AI看,比你自己对着设计稿敲代码快得多。