Skip to main content
Glama
martin-delivered

Figma Storybook Component Matching MCP Server

Figma → Storybook 组件匹配 MCP 服务器

目标

创建一个远程 MCP 服务器。旨在接收 Figma 设计节点输入,将其与我们团队的 React 组件(已注册在 Storybook 中)进行匹配,并生成使用代码示例。

LLM (Claude) 应能通过此 MCP 处理以下请求:

  • “分析这个 Figma URL”

  • “告诉我如何用我们的组件实现这个 Figma 节点”

  • “展示 3 个候选组件”

技术栈

  • 运行时: Cloudflare Workers

  • 语言: TypeScript (strict mode)

  • MCP: 使用 @modelcontextprotocol/sdk + agents 包

  • 传输: Streamable HTTP,端点为 /mcp

  • 验证: zod

  • 构建/部署: wrangler

  • 目标框架: React (代码生成时输出 JSX)

认证 (选项 A: Bearer Token)

  • 所有 MCP 请求都需要 Authorization: Bearer <token> 头部

  • 与 env.MCP_AUTH_TOKEN 进行比对,不匹配则返回 401

  • 认证失败需返回明确的错误消息 ({"error": "invalid_token"})

环境变量 (在 wrangler 中定义)

  • FIGMA_TOKEN: Figma 个人访问令牌 (由服务器保管)

  • STORYBOOK_URL: Storybook 基础 URL (例如: https://storybook.example.com)

  • MCP_AUTH_TOKEN: 用于客户端认证的令牌

  • COMPONENT_IMPORT_PREFIX: 代码生成时的导入路径 (默认为 @/components)

本地开发使用 .dev.vars,生产环境通过 wrangler secret put 管理。wrangler.toml 中仅保留虚拟占位符。

暴露的工具 (Tools)

1. get_figma_node

说明: 接收 Figma URL 并以精简格式返回节点的核心信息

输入 (zod):

{
  url: string  // Figma 노드 URL (예: https://www.figma.com/file/XXX/...?node-id=1%3A2)
}

操作:

  1. 从 URL 解析 fileKey 和 nodeId (?node-id=1%3A2 → 解码为 1:2)

  2. 调用 Figma API: GET https://api.figma.com/v1/files/{fileKey}/nodes?ids={nodeId}

    • 头部: X-Figma-Token: {env.FIGMA_TOKEN}

  3. 仅从响应中提取以下内容 (Figma 响应过于冗长,需精简):

    • 节点名称 (name)

    • 节点类型 (type: FRAME, INSTANCE, TEXT, ...)

    • 若为组件,则提取组件名称 (componentId → componentName)

    • 样式: 背景色、边框、圆角、内边距、布局模式 (autolayout 方向)、间距

    • 若为文本,则提取 characters 和字体信息

    • 子结构: 仅提取子节点名称/类型,深度为 1 (不递归,避免过长)

    • 组件变量/变体信息 (如有)

输出: 包含上述信息的精简 JSON 对象

错误: 区分 URL 解析失败、Figma API 4xx/5xx、令牌过期等错误消息

2. get_figma_subtree

说明: 递归获取节点的完整树结构 (用于分析整个页面/框架)

输入:

{
  url: string,
  maxDepth?: number  // 기본 3, 너무 깊으면 토큰 폭발
}

操作: 与 get_figma_node 类似,但递归子节点直到 maxDepth。每个子节点也采用精简格式。

3. list_stories

说明: 返回我们 Storybook 的组件列表

输入: 无 (或 { filter?: string } 用于搜索)

操作:

  1. 获取 ${env.STORYBOOK_URL}/index.json

  2. (失败时回退) 尝试 ${env.STORYBOOK_URL}/stories.json

  3. 从 entries 对象中仅提取 type: "story" 的项 (排除 docs 页面)

  4. 转换为以下格式:

{
  id: string,
  componentName: string,  // title에서 마지막 "/" 뒤 부분 (예: "Forms/Button" → "Button")
  storyName: string,      // name 필드
  fullTitle: string,      // 원본 title
  tags: string[]
}[]

缓存: 响应在内存中缓存 5 分钟 (无需使用 KV,使用简单变量即可)。Workers 实例生命周期较短,不宜设置过长。

4. get_story_details

说明: 特定 story 的详细信息 (props, args)

输入:

{ storyId: string }

操作:

  1. 在 index.json 中查找对应 ID

  2. 如可能,从 ${STORYBOOK_URL}/stories.json 或基于 ID 的元数据中提取 argTypes

  3. 整理 props 签名:

{
  id: string,
  componentName: string,
  description?: string,
  props: {
    name: string,
    type: string,
    required: boolean,
    description?: string,
    defaultValue?: any
  }[]
}

若无法获取 argTypes,则 props 为空数组,并添加 note: "argTypes unavailable"。

5. match_figma_to_components

说明: 返回与 Figma 节点数据匹配的组件候选及其评分 (核心工具)

输入:

{
  figmaNode: <get_figma_node 출력 형식>,
  topK?: number  // 기본 3
}

操作:

  1. 通过 list_stories 获取所有组件

  2. 计算每个组件的匹配得分:

    • 名称相似度 (权重 0.5): Figma 节点名称 vs componentName

      • 精确匹配: 1.0

      • 忽略大小写匹配: 0.9

      • 包含关系: 0.6

      • 基于 Levenshtein 距离: 0~0.5

    • 结构匹配 (权重 0.3): 推断子节点模式

      • Figma 子节点仅为文本 → “Button”, “Label” 候选 +

      • 图标 + 文本 → “Button”, “Tag”, “Chip” 候选 +

      • 多个卡片形式子节点 → “List”, “Grid” 候选 +

    • 标签匹配 (权重 0.2): Storybook story 标签中包含 Figma 节点名称的关键词

  3. 返回前 K 个 (默认为 3):

{
  storyId: string,
  componentName: string,
  score: number,  // 0~1
  reasons: string[]  // 왜 매칭됐는지 사람이 읽을 수 있게
}[]

剔除匹配得分低于 0.3 的项 (过滤无意义匹配)。

6. generate_component_usage

说明: 使用匹配的组件 + Figma 节点信息生成 React JSX 代码示例

输入:

{
  storyId: string,
  figmaNode: <get_figma_node 출력 형식>
}

操作:

  1. 通过 get_story_details 获取 props 签名

  2. 尝试将 Figma 节点的文本、样式、变体信息映射到 props

    • Figma 文本 → children 或 label prop

    • Figma 变体名称 → 匹配的 prop 值

  3. 生成 JSX 代码字符串

输出:

{
  code: string,        // <Button variant="primary">Click me</Button>
  importStatement: string,  // import { Button } from "@/components/Button"
  notes: string[]      // 매핑 추측이나 빠진 정보 안내
}

导入路径基于环境变量 env.COMPONENT_IMPORT_PREFIX (默认值 "@/components")。

项目结构

figma-storybook-mcp/
├── src/
│   ├── index.ts              # Worker 진입점, 인증 미들웨어, MCP 라우팅
│   ├── mcp.ts                # MyMCP 클래스 (도구 등록)
│   ├── auth.ts               # Bearer 토큰 검증
│   ├── figma/
│   │   ├── client.ts         # Figma REST API 호출
│   │   ├── url-parser.ts     # URL → fileKey + nodeId
│   │   └── normalizer.ts     # Figma 응답 → 정제된 형식
│   ├── storybook/
│   │   ├── client.ts         # index.json fetch + 캐싱
│   │   └── types.ts
│   ├── matching/
│   │   ├── scorer.ts         # 매칭 점수 계산
│   │   └── name-similarity.ts # Levenshtein 등
│   ├── codegen/
│   │   └── react.ts          # JSX 코드 생성
│   └── types.ts              # 공통 타입
├── tests/
│   ├── url-parser.test.ts
│   ├── normalizer.test.ts
│   └── scorer.test.ts
├── wrangler.toml
├── .dev.vars.example         # 실제 .dev.vars는 gitignore
├── package.json
├── tsconfig.json
├── vitest.config.ts
└── README.md

实现要求

  1. 类型安全: 所有工具输入 zod 模式、输出类型均需明确定义

  2. 错误处理:

    • Figma 401 → “Figma 令牌过期/无效”

    • Figma 404 → “找不到节点”

    • Storybook 获取失败 → 明确的消息

    • 所有错误均以 MCP 可理解的格式返回

  3. 日志记录: 使用 console.log 记录工具调用的开始/结束,错误使用 console.error。在 Workers 控制台中可见

  4. 测试: 使用 vitest 进行核心逻辑单元测试 (URL 解析、匹配评分、精简逻辑)

  5. 更新 README:

    • 工具用途说明

    • 环境变量说明

    • 本地运行 (npm run dev)

    • 部署 (npm run deploy)

    • 如何连接到 Claude Desktop / Claude.ai

    • 每个工具的输入输出示例

工作流程 (分阶段报告并进行)

Phase 1: 设置

  • 项目初始化,安装依赖

  • 编写 wrangler.toml, tsconfig.json

  • 确认空 MCP 服务器在 /mcp 响应 (即使工具数为 0 也可)

Phase 2: 认证

  • Bearer Token 验证中间件

  • 确认使用错误令牌调用时返回 401

Phase 3: Figma 工具

  • figma/url-parser.ts + 单元测试

  • figma/client.ts (实际 API 调用)

  • figma/normalizer.ts (响应精简)

  • 注册 get_figma_node 工具

  • 确认实际 Figma URL 的运行情况

Phase 4: Storybook 工具

  • storybook/client.ts (index.json 获取 + 缓存)

  • 注册 list_stories, get_story_details

Phase 5: 匹配

  • matching/scorer.ts + 单元测试

  • 注册 match_figma_to_components

Phase 6: 代码生成

  • codegen/react.ts

  • 注册 generate_component_usage

Phase 7: 收尾

  • 添加 get_figma_subtree

  • 编写 README

  • 提供 .dev.vars.example

每个阶段完成后,简要报告“已完成此项,接下来进行下一项”。

注意事项

  • Cloudflare Workers 仅支持部分 Node.js API。不支持 fs, child_process 等。基于 fetch 编写

  • 使用 @modelcontextprotocol/sdk 的最新稳定版本

  • MCP 标准更新较快,请遵循 agents 包的最新模式

  • 不要一次性完成,分阶段验证进行

  • 代码清晰,注释仅用于业务逻辑 (如匹配评分等)

开始

请从 Phase 1 开始。

Related MCP Connectors