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