Skip to main content
Glama
martin-delivered

Figma Storybook Component Matching MCP Server

Figma → Storybook Komponenten-Matching MCP-Server

Ziel

Erstellung eines Remote-MCP-Servers. Das Ziel ist es, Figma-Designknoten als Eingabe zu erhalten, sie mit den React-Komponenten unseres Teams (registriert in Storybook) abzugleichen und Anwendungsbeispiele für den Code zu generieren.

Die LLM (Claude) sollte über dieses MCP folgende Anfragen verarbeiten können:

  • "Analysiere diese Figma-URL"

  • "Sag mir, wie ich diesen Figma-Knoten mit unseren Komponenten implementieren kann"

  • "Zeige mir 3 Komponenten-Kandidaten"

Tech-Stack

  • Runtime: Cloudflare Workers

  • Sprache: TypeScript (strict mode)

  • MCP: Verwendung der Pakete @modelcontextprotocol/sdk + agents

  • Transport: Streamable HTTP, Endpunkt ist /mcp

  • Validierung: zod

  • Build/Deployment: wrangler

  • Ziel-Framework: React (JSX-Ausgabe bei der Codegenerierung)

Authentifizierung (Option A: Bearer-Token)

  • Alle MCP-Anfragen erfordern den Header Authorization: Bearer <token>

  • Abgleich mit env.MCP_AUTH_TOKEN, bei Nichtübereinstimmung Rückgabe von 401

  • Authentifizierungsfehler liefern eine klare Fehlermeldung ({"error": "invalid_token"})

Umgebungsvariablen (definiert in wrangler)

  • FIGMA_TOKEN: Figma Personal Access Token (wird vom Server gespeichert)

  • STORYBOOK_URL: Storybook-Basis-URL (z. B. https://storybook.example.com)

  • MCP_AUTH_TOKEN: Token für die Client-Authentifizierung

  • COMPONENT_IMPORT_PREFIX: Import-Pfad bei der Codegenerierung (Standard @/components)

Für die lokale Entwicklung .dev.vars verwenden, für die Produktion wrangler secret put. In wrangler.toml nur Dummy-Platzhalter belassen.

Bereitzustellende Tools

1. get_figma_node

Beschreibung: Empfängt eine Figma-URL und gibt die Kerninformationen des Knotens in bereinigter Form zurück

Eingabe (zod):

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

Ablauf:

  1. Parsen von fileKey und nodeId aus der URL (?node-id=1%3A2 → dekodiert zu 1:2)

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

    • Header: X-Figma-Token: {env.FIGMA_TOKEN}

  3. Extraktion nur der folgenden Daten (die Figma-Antwort ist zu ausführlich, daher Bereinigung):

    • Knotenname (name)

    • Knotentyp (type: FRAME, INSTANCE, TEXT, ...)

    • Bei Komponenten: Komponentenname (componentId → componentName)

    • Stil: Hintergrundfarbe, Rahmen, Border-Radius, Padding, Layout-Modus (Auto-Layout-Richtung), Gap

    • Bei Text: characters und Schriftinformationen

    • Kind-Struktur: Nur Name/Typ der Kindknoten bis zu einer Tiefe von 1 (keine Rekursion, wird sonst zu lang)

    • Informationen zu Komponenten-Variablen/Varianten (falls vorhanden)

Ausgabe: Ein bereinigtes JSON-Objekt mit den oben genannten Informationen

Fehler: Unterscheidung zwischen URL-Parsing-Fehler, Figma-API 4xx/5xx, Token-Ablauf usw. mit entsprechenden Fehlermeldungen

2. get_figma_subtree

Beschreibung: Ruft den gesamten Baum eines Knotens rekursiv ab (zur Analyse ganzer Seiten/Frames)

Eingabe:

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

Ablauf: Ähnlich wie get_figma_node, aber rekursives Abrufen der Kinder bis maxDepth. Jedes Kind ebenfalls im bereinigten Format.

3. list_stories

Beschreibung: Gibt die Komponentenliste unseres Storybooks zurück

Eingabe: Keine (oder { filter?: string } für die Suche)

Ablauf:

  1. Fetch von ${env.STORYBOOK_URL}/index.json

  2. (Fallback bei Fehler) Versuch mit ${env.STORYBOOK_URL}/stories.json

  3. Extraktion nur der Einträge mit type: "story" aus dem entries-Objekt (Docs-Seiten ausschließen)

  4. Konvertierung in folgendes Format:

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

Caching: In-Memory-Caching der Antwort für 5 Minuten (KV nicht erforderlich, einfache Variable). Da Workers-Instanzen kurzlebig sind, nicht zu lange ansetzen.

4. get_story_details

Beschreibung: Detaillierte Informationen zu einer bestimmten Story (Props, Args)

Eingabe:

{ storyId: string }

Ablauf:

  1. Suche der entsprechenden ID in index.json

  2. Falls möglich, Extraktion der argTypes aus ${STORYBOOK_URL}/stories.json oder ID-basierten Metadaten

  3. Bereinigung der Prop-Signatur:

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

Falls argTypes nicht abgerufen werden können, Props als leeres Array zurückgeben und stattdessen note: "argTypes unavailable" hinzufügen.

5. match_figma_to_components

Beschreibung: Gibt Komponenten-Kandidaten, die mit den Figma-Knotendaten übereinstimmen, zusammen mit einem Score zurück (Kern-Tool)

Eingabe:

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

Ablauf:

  1. Abrufen aller Komponenten über list_stories

  2. Berechnung des Matching-Scores für jede Komponente:

    • Namensähnlichkeit (Gewichtung 0.5): Figma-Knotenname vs. componentName

      • Exakte Übereinstimmung: 1.0

      • Übereinstimmung ohne Berücksichtigung von Groß-/Kleinschreibung: 0.9

      • Teilmenge: 0.6

      • Basierend auf Levenshtein-Distanz: 0~0.5

    • Struktur-Matching (Gewichtung 0.3): Ableitung von Kind-Mustern

      • Figma-Kind ist nur Text → Kandidaten "Button", "Label" +

      • Icon + Text → Kandidaten "Button", "Tag", "Chip" +

      • Mehrere Karten-ähnliche Kinder → Kandidaten "List", "Grid" +

    • Tag-Übereinstimmung (Gewichtung 0.2): Storybook-Story-Tags enthalten Schlüsselwörter aus dem Figma-Knotennamen

  3. Rückgabe der Top K (Standard 3):

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

Matching-Scores unter 0.3 werden ausgeschlossen (Filterung irrelevanter Übereinstimmungen).

6. generate_component_usage

Beschreibung: Generiert ein React-JSX-Codebeispiel mit der gematchten Komponente + Figma-Knoteninformationen

Eingabe:

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

Ablauf:

  1. Abrufen der Prop-Signatur über get_story_details

  2. Versuch, Text, Stil und Varianteninformationen des Figma-Knotens auf Props abzubilden

    • Figma-Text → children oder label Prop

    • Figma-Variantenname → passender Prop-Wert

  3. Generierung des JSX-Code-Strings

Ausgabe:

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

Der Import-Pfad basiert auf der Umgebungsvariable env.COMPONENT_IMPORT_PREFIX (Standardwert "@/components").

Projektstruktur

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

Implementierungsanforderungen

  1. Typsicherheit: Alle Tool-Eingabe-Zod-Schemata und Ausgabe-Typen müssen explizit definiert sein

  2. Fehlerbehandlung:

    • Figma 401 → "Figma-Token abgelaufen/ungültig"

    • Figma 404 → "Knoten nicht gefunden"

    • Storybook-Fetch fehlgeschlagen → Klare Nachricht

    • Alle Fehler müssen in einem für MCP verständlichen Format zurückgegeben werden

  3. Logging: console.log für Start/Ende von Tool-Aufrufen, Fehler über console.error. Sichtbar im Workers-Dashboard

  4. Tests: Unit-Tests für Kernlogik mit vitest (URL-Parsing, Matching-Score, Normalisierungslogik)

  5. README-Aktualisierung:

    • Was das Tool macht

    • Erklärung der Umgebungsvariablen

    • Lokale Ausführung (npm run dev)

    • Deployment (npm run deploy)

    • Anleitung zur Verbindung mit Claude Desktop / Claude.ai

    • Beispiele für Ein-/Ausgabe jedes Tools

Arbeitsablauf (Berichterstattung in Phasen)

Phase 1: Setup

  • Projektinitialisierung, Installation der Abhängigkeiten

  • Erstellung von wrangler.toml, tsconfig.json

  • Überprüfung, ob der leere MCP-Server auf /mcp antwortet (auch mit 0 Tools OK)

Phase 2: Authentifizierung

  • Middleware zur Überprüfung des Bearer-Tokens

  • Überprüfung von 401 bei Aufrufen mit ungültigem Token

Phase 3: Figma-Tools

  • figma/url-parser.ts + Unit-Tests

  • figma/client.ts (tatsächliche API-Aufrufe)

  • figma/normalizer.ts (Bereinigung der Antwort)

  • Registrierung des get_figma_node-Tools

  • Überprüfung der Funktion mit einer echten Figma-URL

Phase 4: Storybook-Tools

  • storybook/client.ts (index.json fetch + Caching)

  • Registrierung von list_stories, get_story_details

Phase 5: Matching

  • matching/scorer.ts + Unit-Tests

  • Registrierung von match_figma_to_components

Phase 6: Codegenerierung

  • codegen/react.ts

  • Registrierung von generate_component_usage

Phase 7: Abschluss

  • Hinzufügen von get_figma_subtree

  • Erstellung der README

  • Bereitstellung von .dev.vars.example

Nach jeder Phase kurz berichten: "Das habe ich erledigt, als Nächstes mache ich das."

Hinweise

  • Cloudflare Workers unterstützen nur einen Teil der Node.js-API. fs, child_process etc. funktionieren nicht. Auf Basis von fetch schreiben

  • Aktuelle stabile Version von @modelcontextprotocol/sdk verwenden

  • Da sich der MCP-Standard schnell ändert, den neuesten Mustern des agents-Pakets folgen

  • Nicht alles auf einmal bauen, sondern phasenweise validieren

  • Code klar halten, Kommentare nur für Geschäftslogik (wie Matching-Scores)

Start

Bitte beginne mit Phase 1.

Related MCP Connectors