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 패키지 사용

  • Transport: 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 Personal Access Token (서버가 보관)

  • STORYBOOK_URL: Storybook 베이스 URL (예: https://storybook.example.com)

  • MCP_AUTH_TOKEN: 클라이언트 인증용 토큰

  • COMPONENT_IMPORT_PREFIX: 코드 생성 시 import 경로 (기본 @/components)

로컬 개발용은 .dev.vars, 프로덕션은 wrangler secret put으로 관리. wrangler.toml에는 더미 placeholder만 두기.

노출할 도구(Tools)

1. get_figma_node

설명: Figma URL을 받아서 노드의 핵심 정보를 정제된 형태로 반환

입력 (zod):

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

동작:

  1. URL에서 fileKeynodeId 파싱 (?node-id=1%3A21:2로 디코드)

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

    • 헤더: X-Figma-Token: {env.FIGMA_TOKEN}

  3. 응답에서 다음만 추출 (Figma 응답은 너무 verbose하니 정제):

    • 노드 이름 (name)

    • 노드 타입 (type: FRAME, INSTANCE, TEXT, ...)

    • 컴포넌트면 컴포넌트 이름 (componentIdcomponentName)

    • 스타일: 배경색, 테두리, 보더 반경, 패딩, 레이아웃 모드(autolayout 방향), gap

    • 텍스트면 characters와 폰트 정보

    • 자식 구조: 자식 노드들의 이름/타입만 1단계 깊이로 (재귀 X, 너무 길어짐)

    • 컴포넌트 변수/variants 정보(있으면)

출력: 위 정보를 담은 정제된 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 fetch

  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분간 in-memory 캐싱 (KV 안 써도 됨, 단순 변수로). Workers는 인스턴스가 짧게 살아있으니 너무 길게 잡지 말 것.

4. get_story_details

설명: 특정 스토리의 상세 정보 (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 스토리 태그에 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 variant 이름 → matching prop value

  3. JSX 코드 문자열 생성

출력:

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

import path는 환경변수 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 fetch 실패 → 명확한 메시지

    • 모든 에러는 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개여도 OK)

Phase 2: 인증

  • Bearer 토큰 검증 미들웨어

  • 잘못된 토큰으로 호출 시 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 fetch + 캐싱)

  • 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 제공

각 Phase 끝나면 짧게 "이거 했고 다음 이거 할게" 보고하고 진행.

주의사항

  • Cloudflare Workers는 Node.js API 일부만 지원. fs, child_process 등 안 됨. fetch 기반으로 작성

  • @modelcontextprotocol/sdk 최신 안정 버전 사용

  • MCP 표준은 빠르게 변하니 agents 패키지의 최신 패턴 따를 것

  • 한 번에 다 만들지 말고 Phase별로 검증하면서 진행

  • 코드는 명확하게, 주석은 비즈니스 로직(매칭 점수 같은 거)에만

시작

Phase 1부터 시작해줘.

A
license - permissive license
-
quality - not tested
C
maintenance

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/martin-delivered/storybook-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server