Skip to main content
Glama
martin-delivered

Figma Storybook Component Matching MCP Server

MCP-сервер для сопоставления Figma → Storybook

Цель

Создать удаленный MCP-сервер. Цель — принимать на вход узлы дизайна Figma, сопоставлять их с React-компонентами нашей команды (зарегистрированными в Storybook) и генерировать примеры кода использования.

LLM (Claude) должна иметь возможность обрабатывать следующие запросы через этот MCP:

  • "Проанализируй этот URL Figma"

  • "Подскажи, как реализовать этот узел Figma с помощью наших компонентов"

  • "Покажи 3 подходящих компонента"

Технологический стек

  • Среда выполнения: Cloudflare Workers

  • Язык: TypeScript (strict mode)

  • MCP: использование пакетов @modelcontextprotocol/sdk + agents

  • Транспорт: Streamable HTTP, эндпоинт /mcp

  • Валидация: zod

  • Сборка/развертывание: wrangler

  • Целевой фреймворк: React (вывод JSX при генерации кода)

Аутентификация (Вариант A: Bearer-токен)

  • Все запросы MCP требуют заголовок Authorization: Bearer <token>

  • Сравнение с env.MCP_AUTH_TOKEN, при несовпадении возвращается 401

  • Ошибка аутентификации должна сопровождаться понятным сообщением об ошибке ({"error": "invalid_token"})

Переменные окружения (определяются в wrangler)

  • FIGMA_TOKEN: Персональный токен доступа Figma (хранится на сервере)

  • STORYBOOK_URL: Базовый URL Storybook (например, https://storybook.example.com)

  • MCP_AUTH_TOKEN: Токен для аутентификации клиента

  • COMPONENT_IMPORT_PREFIX: Путь импорта при генерации кода (по умолчанию @/components)

Для локальной разработки используйте .dev.vars, для продакшена — wrangler secret put. В wrangler.toml оставляйте только фиктивные плейсхолдеры.

Инструменты (Tools)

1. get_figma_node

Описание: Получает URL Figma и возвращает ключевую информацию об узле в очищенном виде

Входные данные (zod):

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

Действия:

  1. Парсинг fileKey и nodeId из URL (декодирование ?node-id=1%3A2 → 1:2)

  2. Вызов API Figma: 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), gap

    • Если это текст, characters и информация о шрифте

    • Структура дочерних элементов: только имена/типы дочерних узлов на 1 уровень глубины (без рекурсии, чтобы не перегружать)

    • Информация о переменных/вариантах компонента (если есть)

Вывод: Очищенный JSON-объект с указанной информацией

Ошибки: Разделение ошибок парсинга URL, API Figma 4xx/5xx, истечения срока действия токена и т.д.

2. get_figma_subtree

Описание: Рекурсивное получение полного дерева узла (для анализа всей страницы/фрейма)

Входные данные:

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

Действия: Аналогично get_figma_node, но с рекурсией дочерних элементов до maxDepth. Каждый дочерний элемент также в очищенном формате.

3. list_stories

Описание: Возвращает список компонентов из нашего Storybook

Входные данные: Нет (или { filter?: string } для поиска)

Действия:

  1. Fetch ${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

Описание: Подробная информация о конкретной истории (props, args)

Входные данные:

{ storyId: string }

Действия:

  1. Поиск ID в index.json

  2. По возможности извлечение argTypes из ${STORYBOOK_URL}/stories.json или метаданных на основе ID

  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

      • На основе расстояния Левенштейна: 0~0.5

    • Соответствие структуры (вес 0.3): Вывод паттернов дочерних элементов

      • Дочерние элементы Figma только текст → кандидаты "Button", "Label" +

      • Иконка + текст → кандидаты "Button", "Tag", "Chip" +

      • Несколько карточек → кандидаты "List", "Grid" +

    • Соответствие тегов (вес 0.2): Включение ключевых слов имени узла Figma в теги истории Storybook

  3. Возврат топ-K (по умолчанию 3):

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

Оценки ниже 0.3 отсеиваются (фильтрация бессмысленных совпадений).

6. generate_component_usage

Описание: Генерация примера кода React JSX на основе сопоставленного компонента и информации об узле Figma

Входные данные:

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

Действия:

  1. Получение сигнатуры props через get_story_details

  2. Попытка сопоставить текст, стили и варианты узла Figma с props

    • Текст Figma → prop children или label

    • Имя варианта 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 → "Узел не найден"

    • Ошибка fetch Storybook → понятное сообщение

    • Все ошибки возвращаются в формате, понятном MCP

  3. Логирование: console.log для начала/конца вызова инструмента, ошибки через console.error. Видно в панели управления Workers

  4. Тестирование: Unit-тесты vitest для основной логики (парсинг URL, оценка соответствия, логика нормализации)

  5. Обновление README:

    • Описание инструмента

    • Описание переменных окружения

    • Локальный запуск (npm run dev)

    • Развертывание (npm run deploy)

    • Как подключить к Claude Desktop / Claude.ai

    • Примеры ввода/вывода для каждого инструмента

Порядок работы (отчитываться по этапам)

Фаза 1: Настройка

  • Инициализация проекта, установка зависимостей

  • Создание wrangler.toml, tsconfig.json

  • Проверка, что пустой MCP-сервер отвечает на /mcp (даже с 0 инструментов)

Фаза 2: Аутентификация

  • Middleware для проверки Bearer-токена

  • Проверка 401 при вызове с неверным токеном

Фаза 3: Инструменты Figma

  • figma/url-parser.ts + unit-тесты

  • figma/client.ts (реальный вызов API)

  • figma/normalizer.ts (очистка ответа)

  • Регистрация инструмента get_figma_node

  • Проверка работы с реальным URL Figma

Фаза 4: Инструменты Storybook

  • storybook/client.ts (fetch index.json + кэширование)

  • Регистрация list_stories, get_story_details

Фаза 5: Сопоставление

  • matching/scorer.ts + unit-тесты

  • Регистрация match_figma_to_components

Фаза 6: Генерация кода

  • codegen/react.ts

  • Регистрация generate_component_usage

Фаза 7: Завершение

  • Добавление get_figma_subtree

  • Написание README

  • Предоставление .dev.vars.example

После каждой фазы кратко отчитываться: "Это сделал, приступаю к следующему".

Примечания

  • Cloudflare Workers поддерживают только часть API Node.js. fs, child_process и т.д. недоступны. Писать на основе fetch

  • Использовать последнюю стабильную версию @modelcontextprotocol/sdk

  • Стандарт MCP быстро меняется, следовать последним паттернам пакета agents

  • Не делать все сразу, проверять по фазам

  • Код должен быть понятным, комментарии только для бизнес-логики (например, оценки соответствия)

Начало

Начни с Фазы 1.

Related MCP Connectors