OCaml 5.x 네이티브 MCP 서버 - Figma 디자인을 정확도 우선 Fidelity DSL로 변환
Note: This is a personal project.
eval $(opam env)
opam pin add grpc-direct https://github.com/jeong-sik/grpc-direct.git -y
opam install . --deps-only
dune build
export FIGMA_TOKEN="YOUR_TOKEN"
./start-figma-mcp-http.sh --port 8940{
"mcpServers": {
"figma": {
"type": "http",
"url": "http://127.0.0.1:8940/mcp"
}
}
}- MCP 2025-11-25 스펙 준수 - JSON-RPC 2.0 over stdio
- 정확도 우선 - 레이아웃/페인트/보더/타이포를 최대한 그대로 전달
- 타입 안전 - OCaml Variant/ADT 기반 파싱 (HTML 모드)
- 빠른 실행 - 네이티브 바이너리 (5.5MB)
Capabilities: tools ✅ · resources ✅ · prompts ✅
| Capability | 상태 | 설명 |
|---|---|---|
| tools | ✅ 지원 | 51개 도구 (아래 목록 참조) |
| resources | ✅ 지원 | figma://docs/* 가이드 |
| prompts | ✅ 지원 | Fidelity 리뷰 프롬프트 |
figma://docs/fidelity # Fidelity DSL 키 설명
figma://docs/usage # 정확도 우선 호출 패턴
# 리스트 조회
echo '{"jsonrpc":"2.0","id":4,"method":"prompts/list","params":{}}' | ./start-figma-mcp.sh
# 단일 프롬프트 조회 (text 포함)
echo '{"jsonrpc":"2.0","id":5,"method":"prompts/get","params":{"name":"figma_fidelity_review"}}' | ./start-figma-mcp.shdocs/RECIPES.md- end-to-end usage patterns (quickstart, high fidelity, large nodes)docs/SETUP.md- 설치/실행/연동 요약docs/MCP-TEMPLATE.md- ~/.mcp.json 템플릿docs/INSTALL-CHECKLIST.md- 설치 후 확인
| Tool | 설명 | 테스트 |
|---|---|---|
figma_codegen |
Figma JSON → Fidelity DSL 변환 | ✅ |
figma_get_file |
Figma API에서 파일 가져와 DSL 변환 | ✅ |
figma_get_file_meta |
파일 메타(components/componentSets/styles) 반환 | ✅ |
figma_list_screens |
파일 내 화면(Frame/Component) 목록 | ✅ |
figma_get_node |
특정 노드 ID만 가져와 DSL 변환 | ✅ |
figma_get_node_bundle |
정확도 극대화 번들(DSL+렌더+메타+변수+fills) | ✅ |
figma_get_node_summary |
경량 구조 요약 | ✅ |
figma_select_nodes |
후보 노드 점수화/선별 + 노트 텍스트 분리 | ✅ |
figma_get_node_chunk |
깊이 범위별 노드 청크 로드 | ✅ |
figma_fidelity_loop |
fidelity 점수 미달 시 depth/geometry 자동 상향 | ✅ |
figma_image_similarity |
렌더 이미지 SSIM/PSNR 비교 | ✅ |
figma_export_image |
노드를 PNG/JPG/SVG/PDF URL로 내보내기 | ✅ |
figma_get_image_fills |
파일 내 이미지 채움(image fills) 원본 URL | ✅ |
figma_get_nodes |
여러 노드 ID를 한번에 조회 | ✅ |
figma_get_file_versions |
파일 버전 목록 조회 | ✅ |
figma_get_file_comments |
파일 코멘트 목록 조회 | ✅ |
figma_post_comment |
파일 코멘트 생성 | ✅ |
figma_get_file_components |
파일 컴포넌트 목록 | ✅ |
figma_get_team_components |
팀 컴포넌트 목록 | ✅ |
figma_get_file_component_sets |
파일 컴포넌트 셋 목록 | ✅ |
figma_get_team_component_sets |
팀 컴포넌트 셋 목록 | ✅ |
figma_get_file_styles |
파일 스타일 목록 | ✅ |
figma_get_team_styles |
팀 스타일 목록 | ✅ |
figma_get_component |
컴포넌트 상세 조회 | ✅ |
figma_get_component_set |
컴포넌트 셋 상세 조회 | ✅ |
figma_get_style |
스타일 상세 조회 | ✅ |
figma_plugin_connect |
플러그인 채널 생성/연결 | ✅ |
figma_plugin_use_channel |
기본 채널 설정 | ✅ |
figma_plugin_status |
플러그인 채널 상태 | ✅ |
figma_plugin_read_selection |
플러그인에서 선택 노드 읽기 | ✅ |
figma_plugin_get_node |
플러그인에서 특정 노드 읽기 | ✅ |
figma_plugin_export_node_image |
플러그인 exportAsync 이미지(base64) | ✅ |
figma_plugin_get_variables |
플러그인 Variables API 추출 | ✅ |
figma_plugin_apply_ops |
플러그인으로 create/update/delete | ✅ |
| Tool | 설명 | 테스트 |
|---|---|---|
figma_verify_visual |
HTML 생성 + Playwright 렌더 + SSIM 비교 → 자동 조정 | ✅ |
figma_verify_visual은 진화 과정을 자동 저장합니다:
/tmp/figma-evolution/run_1705123456789/
├── figma_original.png # Figma 원본 렌더
├── step1_render.png # 1차 시도 렌더
├── step2_render.png # 2차 시도 렌더 (조정 후)
├── final_render.png # 최종 렌더
├── html/
│ ├── step1.html # 1차 시도 HTML
│ ├── step2.html # 2차 시도 HTML
│ └── final.html # 최종 HTML
└── evolution.json # 메타데이터
| 접근 방식 | SSIM | 문제점 |
|---|---|---|
| Nested (Figma 계층 복제) | 72% | padding/gap 누적으로 위치 틀어짐 |
| Flat 2-level | 99% | 중앙 정렬 + typography 완전 재현 |
<!-- ❌ 실패: 중첩 div -->
<div style="display:flex;flex-direction:column;padding:0px 16px;gap:10px">
<div style="padding:12px 20px;gap:8px">
<div style="gap:4px"><span>더보기</span></div>
</div>
</div>
<!-- ✅ 성공: Flat 2-level -->
<div style="display:flex;align-items:center;justify-content:center;width:343px;height:48px">
<div style="display:flex;align-items:center;justify-content:center;background:rgb(32,141,249);border-radius:10px">
<span style="letter-spacing:-0.32px;line-height:24px">더보기</span>
</div>
</div>- Figma URL에서는
node-id=2089-11127(하이픈)처럼 보이지만, API는2089:11127(콜론) 형식만 받습니다. - MCP 도구(
figma_get_node,figma_get_node_bundle)의node_id는 콜론 형식이 권장입니다. - 변환 규칙:
2089-11127->2089:11127 - MCP 도구/gRPC는 하이픈 형식도 자동 정규화합니다.
- 팁:
figma_parse_url로 URL에서node_id를 추출하면 바로 사용할 수 있습니다.
예시:
figma_get_node_bundle
file_key: "YOUR_FIGMA_FILE_KEY"
node_id: "2089:11127"
format: "html"
depth: 15
download: true
figma_get_node_bundle권장: DSL + 렌더 + 메타/변수/이미지 fills를 한번에 수집- 플러그인 스냅샷이 필요한 경우
include_plugin=true+plugin_channel_id사용 (텍스트 세그먼트/범위 포함) - fidelity 점수 기반 자동 재조회:
figma_fidelity_loop+include_variables=true+include_image_fills=true+include_plugin=true - 렌더 기준 정밀 비교:
figma_image_similarity(SSIM/PSNR) format=raw사용 시 원본 JSON 그대로 반환 (출력 큼)figma_get_variables는format=resolved로 기본 모드 값 포함
예시:
figma_fidelity_loop
file_key: "YOUR_FIGMA_FILE_KEY"
node_id: "2089:11127"
target_score: 0.95
include_variables: true
include_image_fills: true
include_plugin: true
렌더 비교는 PNG/JPG를 PPM으로 변환하기 위해 sips(macOS) 또는 ImageMagick(magick/convert)이 필요합니다.
Figma MCP 결과를 정량화된 리포트로 저장합니다.
./scripts/figma-accuracy-eval.py \
--file-key "FILE_KEY" \
--node-id "123:456" \
--token "$FIGMA_TOKEN" \
--include-plugin \
--plugin-channel-id "ch-..." \
--out "$HOME/me/logs/figma-accuracy-123_456.json"- 기본 MCP URL:
http://localhost:8940/mcp(FIGMA_MCP_URL로 변경 가능) - 비교 노드가 있으면
--compare-node-id로 SSIM/PSNR도 기록
| Tool | 설명 | 테스트 |
|---|---|---|
figma_parse_url |
URL에서 team/project/file/node ID 추출 (API 호출 없음) | ✅ |
figma_get_me |
현재 인증된 사용자 정보 | ✅ |
figma_list_projects |
팀의 프로젝트 목록 | ✅ |
figma_list_files |
프로젝트의 파일 목록 | ✅ |
figma_get_variables |
디자인 토큰/변수 조회 | ❌ Enterprise 또는 file_variables:read 스코프 필요 |
| Tool | 설명 | 테스트 |
|---|---|---|
figma_search |
텍스트/노드 이름 검색 | ✅ |
figma_query |
SQL-like 조건부 필터링 (type, size, color 등) | ✅ |
figma_tree |
노드 트리 시각화 (ASCII/indent/compact) | ✅ |
figma_stats |
디자인 통계 (색상, 폰트, 크기 분포) | ✅ |
figma_compare |
두 노드 비교 (Web/Mobile 일관성 검사) | ✅ |
figma_export_tokens |
CSS/Tailwind/JSON 디자인 토큰 추출 | ✅ |
| Tool | 설명 | 테스트 |
|---|---|---|
figma_doctor |
로컬 의존성/스크립트 경로 점검 | ✅ |
figma_read_large_result |
large_result 파일 chunk 읽기 | ✅ |
figma_cache_stats |
L1/L2 캐시 통계 조회 | ✅ |
figma_cache_invalidate |
캐시 무효화 | ✅ |
- 성공: 15/16 도구 (Core 기준)
- 제한:
figma_get_variables- Figma Variables API는 Enterprise 플랜 또는file_variables:readOAuth 스코프 필요 - 신규 도구: REST Parity/Plugin Bridge는 샘플 파일 기준 추가 검증 예정
# opam 환경
eval $(opam env)
# 외부 의존성 pin (opam에 없음)
opam pin add grpc-direct https://github.com/jeong-sik/grpc-direct.git -y
# 의존성 설치
opam install . --deps-only
# 빌드
dune build
# 실행 (로컬 빌드)
dune exec figma-mcpstart-figma-mcp.sh와 start-figma-mcp-http.sh는 Keychain에서 FIGMA_TOKEN을 읽습니다.
# 1) 환경변수로 실행 (일회성)
export FIGMA_TOKEN="YOUR_TOKEN"
# 2) Keychain 저장 (권장)
security add-generic-password -s "figma-mcp" -a "FIGMA_TOKEN" -w "YOUR_TOKEN"~/.mcp.json 또는 프로젝트 .mcp.json에 추가:
{
"mcpServers": {
"figma": {
"command": "/path/to/figma-mcp/start-figma-mcp.sh",
"args": []
}
}
}REST API만으로 부족한 레이아웃/스타일 정보를 보강하려면 플러그인 브릿지를 함께 사용하세요.
- HTTP 모드 서버 실행 (예: 8940)
./start-figma-mcp-http.sh --port 8940- Figma 플러그인 설치
- Figma → Plugins → Development → Import plugin from manifest…
plugin/manifest.json선택- Import 실패 시: Figma → Plugins → Development → New Plugin으로 생성 후,
생성된
manifest.json의 숫자 ID로plugin/manifest.json의id교체 allowedDomains에http://localhost:...넣으면 오류가 날 수 있으니, 로컬은devAllowedDomains에만 넣고allowedDomains는 https 도메인만 유지- Figma는
devAllowedDomains에서 IP(예:127.0.0.1)를 거부할 수 있으니localhost만 사용 plugin/manifest.loopback.json은 placeholder id이므로 import 실패 시 새 플러그인을 만들고 생성된id로 교체하세요- Dev Mode 패널에서 실행하려면
capabilities: ["inspect", "codegen"]+codegenLanguages가 필요
- 플러그인 실행 후 채널 연결
- 플러그인 UI에서 Server URL 확인/수정 → Connect
- 표시된 Channel ID를 복사
연결 문제 해결:
POST /plugin/connect또는/plugin/poll이net::ERR_CONNECTION_REFUSED면 서버가 꺼져 있거나 포트가 다릅니다.curl http://localhost:8940/health로 먼저 확인하세요.devAllowedDomains에127.0.0.1를 넣으면 Figma가 거부할 수 있습니다. 기본plugin/manifest.json은localhost만 허용합니다.- 로컬 IP가 꼭 필요하면
plugin/manifest.loopback.json을 따로 import하세요. (Figma가 IP를 거부하면localhost로 되돌리세요.) - Channel ID가 안 뜨면 플러그인 창을 닫지 말고, 서버 로그/
/plugin/status를 확인하세요.
- MCP 도구로 채널 설정
figma_plugin_use_channel
channel_id: "ch-..."
- 번들에 플러그인 스냅샷 포함
figma_get_node_bundle
file_key: "..."
node_id: "123:456"
include_plugin: true
plugin_channel_id: "ch-..."
plugin_depth: 0
plugin_include_geometry: false
include_plugin_variables: true
include_plugin_image: true
URL만으로 호출 (선택 없이 node_id 사용):
figma_get_node_bundle
url: "https://www.figma.com/design/...?...node-id=123-456"
token: "$FIGMA_TOKEN"
auto_plugin: true
plugin_channel_id: "ch-..."
주의: 플러그인 스냅샷은 해당 파일이 Figma에서 열려 있어야 합니다.
플러그인 도구 직접 호출:
figma_plugin_export_node_image
node_id: "123:456"
figma_plugin_get_variables
플러그인 스냅샷 옵션:
plugin_depth: 큰 섹션은0으로, 필요한 경우1~2로 점진 증가plugin_include_geometry: 아이콘/벡터가 필요할 때만truefigma_plugin_get_node는include_geometry로 벡터 포함 여부 제어plugin_context_mode: summary+plugin_depth: 0은 빠르지만 정밀도가 낮습니다. 최종 패스는plugin_context_mode: both+plugin_depth: 1권장 주의: 플러그인 이미지 응답은 base64이므로 출력이 커집니다. (download 옵션은 REST 이미지에만 적용)
HTTP 엔드포인트:
POST /plugin/connectPOST /plugin/pollPOST /plugin/resultGET /plugin/status/plugin/poll은wait_ms(또는timeout_ms)를 지원합니다. (long-poll, ms 단위) 최대 대기 시간은FIGMA_PLUGIN_POLL_MAX_MS로 제한됩니다. (기본 30000ms)
| 상황 | 권장 프로토콜 | 이유 |
|---|---|---|
| 7MB+ JSON 응답 | gRPC ✅ | 청크 스트리밍으로 메모리 효율적 |
| 대형 Figma 파일 (100+ 노드) | gRPC ✅ | 점진적 로딩으로 타임아웃 방지 |
| 재귀 탐색 (recursive: true) | gRPC ✅ | 실시간 진행 상황 표시 |
| 빠른 단일 노드 조회 | HTTP | 오버헤드 낮음 |
| Claude Code stdio 통합 | HTTP | MCP 프로토콜 호환 |
권장: 대용량 응답이 예상되면 HTTP + gRPC 동시 실행 모드를 사용하세요.
# ⭐ 권장: HTTP + gRPC 동시 실행 (production)
./figma-mcp --port 8940 --grpc-port 50052
# gRPC 단독 실행 (streaming-only 환경)
./figma-mcp --grpc-port 50052
# HTTP 단독 실행 (소규모 요청)
./figma-mcp --port 8940서비스/메서드:
figma.v1.FigmaService/GetNodeStream(server streaming)figma.v1.FigmaService/FidelityLoop(server streaming)figma.v1.FigmaService/GetSplitStream(server streaming)figma.v1.FigmaService/GetFileMeta(unary)
테스트 (reflection 비활성화: proto 지정 필요):
grpcurl -plaintext -import-path proto -proto figma.proto \
-d '{"file_key":"...","node_id":"...","token":"..."}' \
localhost:50052 figma.v1.FigmaService/GetNodeStream재귀 스트림(하위 노드 전체 확장):
grpcurl -plaintext -import-path proto -proto figma.proto \
-d '{"file_key":"...","node_id":"...","token":"...","recursive":true}' \
localhost:50052 figma.v1.FigmaService/GetNodeStream옵션:
recursive_max_depth(기본 20, env:FIGMA_RECURSIVE_MAX_DEPTH)recursive_max_nodes(기본 5000, env:FIGMA_RECURSIVE_MAX_NODES)recursive_depth_per_call(기본 1, env:FIGMA_RECURSIVE_DEPTH_PER_CALL)- 재귀 모드는 중복을 피하려고 각 노드를 단일 레벨(자식 제거)로 스트림합니다.
요구사항 분석 + 분할정복 플랜:
grpcurl -plaintext -import-path proto -proto figma.proto \
-d '{"file_key":"...","node_id":"...","token":"...","recursive":true}' \
localhost:50052 figma.v1.FigmaService/PlanTasksPlanTasks 응답 추가 필드:
summary: 우선순위/토큰 요약requirements_json: 노드 타입/오토레이아웃/이미지 fill 등 분석 결과
프로토콜 정의는 proto/figma.proto를 참고하세요.
format: fidelity는 JSON 기반의 구조화 출력입니다.
{
"meta": {"id":"1:2","name":"Card","type":"FRAME"},
"geometry": {"absoluteBoundingBox":{"x":0,"y":0,"width":320,"height":200}},
"layout": {"layoutMode":"VERTICAL","paddingTop":16,"itemSpacing":12},
"paint": {"fills":[...],"strokes":[...],"strokeWeight":1},
"text": {"characters":null,"style":null},
"children": [ ... ],
"layout_missing": ["layoutWrap","layoutAlign"]
}figma_export_image, figma_get_node_bundle에서 download: true와 save_dir 지정 가능.
기본 저장 경로는 $ME_ROOT/download/figma-assets 입니다. (ME_ROOT 미설정 시 $HOME/me/download/figma-assets, 없으면 /tmp/figma-assets)
# initialize
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}' | ./start-figma-mcp.sh
# tools/list
echo '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}' | ./start-figma-mcp.sh- OCaml 5.x
- yojson
- cohttp-lwt-unix
- lwt
- uri
- cmdliner
Visual Feedback Loop에서 발견된 CSS 정확도 문제를 수정했습니다.
Figma primaryAxisAlignItems/counterAxisAlignItems → CSS justify-content/align-items 매핑:
| Figma | justify-content | align-items |
|---|---|---|
| MIN | flex-start (기본값) | flex-start (기본값) |
| CENTER | center | center |
| MAX | flex-end | flex-end |
| SPACE_BETWEEN | space-between | - |
| BASELINE | - | baseline |
Before: 모든 값이 무시됨 → CENTER/MAX 레이아웃 틀어짐 After: 동적 매핑으로 정확한 정렬
4가지 Figma 효과를 CSS로 변환:
/* DropShadow → box-shadow */
box-shadow: 4px 4px 10px 2px rgba(0,0,0,0.25);
/* InnerShadow → box-shadow inset */
box-shadow: inset 2px 2px 5px 0px rgba(255,255,255,0.5);
/* LayerBlur → filter:blur */
filter: blur(8px);
/* BackgroundBlur → backdrop-filter */
backdrop-filter: blur(12px);예제 출력:
box-shadow:4px 4px 10px 2px rgba(0,0,0,0.2),inset 2px 2px 5px 0px rgba(255,255,255,0.50);filter:blur(8px);backdrop-filter:blur(12px)Figma gradientStops → CSS linear-gradient:
(* 입력: Figma gradientStops *)
[
(0.0, {r=1.0; g=0.0; b=0.0; a=1.0}); (* Red *)
(0.5, {r=0.0; g=1.0; b=0.0; a=1.0}); (* Green *)
(1.0, {r=0.0; g=0.0; b=1.0; a=1.0}); (* Blue *)
]
(* 출력: CSS *)
"linear-gradient(to right,#FF0000 0%,#00FF00 50%,#0000FF 100%)"현재 제한사항:
- 방향은
to right고정 (각도 계산은 P1) - Radial/Angular/Diamond는 linear로 fallback
gradient_to_css (5 stops) : 4 µs/iter
effects_to_css (4 effects) : 6 µs/iter
effects_to_css (all invisible): <1 µs/iter
# P0 유닛 테스트 (10개)
dune exec ./test/test_codegen_p0.exe
# P0 벤치마크
dune exec ./test/bench_p0.exe현재 figma_compare는 실용적 휴리스틱 기반입니다. 아래 학술적 기반 개선을 계획 중:
| 지표 | 공식/알고리즘 | 출처 |
|---|---|---|
| Color | CIEDE2000 (ΔE*₀₀) | CIE 표준, 인간 색지각 모델 |
| Layout | IoU (Intersection over Union) | 객체 탐지 표준 메트릭 |
| Structure | Tree Edit Distance (TED) | Zhang-Shasha 알고리즘 |
| Visual | SSIM (Structural Similarity Index) | Wang et al. 2004, IEEE TIP |
| Embedding | Cosine Similarity on UI Embedding | Rico (Google, UIST 2017) |
비교: "B2C 홈 (Web)" vs "B2C 홈 (Mobile)"
┌─────────────────┬────────┬─────────────────────────────┐
│ 지표 │ 점수 │ 설명 │
├─────────────────┼────────┼─────────────────────────────┤
│ Color (ΔE*₀₀) │ 95.2% │ 색상 차이 ΔE=2.3 (JND 이하) │
│ Layout (IoU) │ 87.4% │ 요소 위치 오버랩 │
│ Structure (TED) │ 92.0% │ 트리 편집 거리 4 │
│ Visual (SSIM) │ 89.1% │ 구조적 유사도 │
│ Embedding │ 94.7% │ Rico-style 64dim cosine │
├─────────────────┼────────┼─────────────────────────────┤
│ **종합** │ 91.7% │ 가중 평균 │
└─────────────────┴────────┴─────────────────────────────┘
- Rico: A Mobile App Dataset (UIST 2017)
- LTSim: Layout Transportation-based Similarity (2024)
- SSIM: Image Quality Assessment (IEEE TIP 2004)
- CIEDE2000 Color Difference
- ✅ 현재: 휴리스틱 가중치 (Critical/Major/Minor)
- 🔜 Phase 1: CIEDE2000 색상 거리 적용
- 🔜 Phase 2: IoU 레이아웃 유사도 추가
- 🔜 Phase 3: SSIM 시각적 유사도 (렌더링 필요)
- 🔜 Phase 4: Rico-style Embedding (ML 모델 필요)
MIT