devup-mcp 가 Figma 를 읽을 때 공식 MCP 의 요금 한도를 쓰지 않게 해 주는 플러그인입니다.
devup-mcp 의 수집은 읽기 스크립트를 공식 MCP 의 use_figma 로 보내는 방식이고,
한도가 걸리는 곳이 바로 그 도구입니다. 화면 하나를 받는 데 snapshot 만 십수 회가
들어가므로 한도는 금방 바닥납니다.
그 스크립트들이 건드리는 것은 문서화된 Plugin API 뿐입니다.
getNodeByIdAsync getStyleByIdAsync variables root fileKey
getLocalPaintStylesAsync / TextStylesAsync / EffectStylesAsync / GridStylesAsync
전부 평범한 플러그인이 쓸 수 있는 것들이라, 우리가 돌리는 플러그인이 같은 일을 대신할 수 있습니다. 그 경로에는 한도가 없습니다.
빌드할 필요 없습니다. dist/ 가 저장소에 들어 있습니다.
Figma 데스크톱 앱에서 Plugins → Development → Import plugin from manifest 를
고르고 plugin/manifest.json 을 선택하면 끝입니다.
브라우저판에서는 개발 플러그인을 불러올 수 없으므로 데스크톱 앱이 필요합니다.
cd plugin
npm install
npm run build # dist/ 를 다시 만든다 — 함께 커밋해야 한다
node --test tests/withdraw.test.mjs # 커밋할 dist/code.js 가 취소를 지키는지dist/ 는 의도적으로 커밋합니다. 받는 사람이 Node 없이 곧장 import 할 수 있게
하려는 것이고, 그 대가로 원본과 어긋날 위험이 생기므로 CI 가 매번 다시 빌드해
git diff --exit-code -- dist 로 대조합니다. 빌드는 재현 가능합니다 — 같은 입력에서
같은 바이트가 나옵니다. 어긋난 채로는 병합되지 않습니다.
읽으려는 파일을 열고 플러그인을 실행한 뒤, 창을 열어 둔 채로 devup-mcp 를 쓰면 됩니다. 창을 닫으면 연결이 끊기고 그 파일의 읽기는 다시 공식 MCP 로 갑니다.
창에 표시되는 점이 상태입니다.
| 표시 | 뜻 |
|---|---|
| 초록 | devup-mcp 에 연결됨. 이 파일의 읽기는 한도를 쓰지 않습니다 |
| 빨강 | devup-mcp 를 찾지 못함. 2초마다 다시 시도합니다 |
devup-mcp 쪽은 아무 설정도 필요 없습니다. 플러그인이 붙어 있으면 그 파일의 스크립트 읽기를 브리지로 보내고, 안 붙어 있으면 호출마다 곧장 공식 MCP 로 넘어갑니다. 켜 두어서 잃는 것은 없습니다.
플러그인은 붙을 때와 그 뒤 페이지·선택이 바뀔 때마다 보고 있는 페이지와
선택한 노드(앞 20개와 전체 수)를 devup-mcp 에 알립니다. 그래서 플러그인이 하나만
붙어 있으면 devup_figma_export·devup_figma_search·devup_figma_explore 에
url 을 주지 않아도 됩니다 — 이 파일의, Figma 에서 선택한 노드가 대상입니다.
링크 자리가 꼭 필요하면 figma-bridge://current(?node-id=1-2 로 노드 지정)를
씁니다. 지금 무엇이 붙어 있고 무엇을 선택했는지는
devup_figma_auth { "action": "status" } 의 paths.bridge.attachedFiles 에 나옵니다.
이 보고가 없는 예전 빌드는 selection 이 null 로 보이며, 이때는 frameIds 로
노드를 직접 지정해야 합니다. 저장소를 받은 뒤 플러그인을 다시 실행하면 새 빌드가
쓰입니다.
포트를 바꾸려면 세 곳을 함께 고쳐야 합니다. Figma 는 플러그인이 접속할 수 있는
주소를 manifest 에 미리 적어 두게 하며, 이 목록은 실행 중에 바뀌지 않습니다.
그래서 DEVUP_FIGMA_BRIDGE_PORT 만 바꾸면 devup-mcp 는 새 포트에서 기다리는데
플러그인은 여전히 1993 을 두드리게 되고, 아무 오류 없이 공식 MCP 로 폴백합니다.
아끼려던 한도가 그대로 나가므로 눈치채기 어렵습니다.
바꿔야 한다면 manifest.json 의 allowedDomains, src/code.ts 의 PORT,
그리고 환경 변수를 모두 같은 값으로 맞춘 뒤 플러그인을 다시 빌드·설치하십시오.
끄려면 DEVUP_FIGMA_BRIDGE_PORT=off 를 주면 됩니다.
읽기 전용입니다. 실행되는 스크립트는 devup-mcp 의 읽기 스크립트에서 생성되며
문서를 바꾸는 호출을 포함하지 않습니다. 데이터는 같은 기기의 devup-mcp 로만
나갑니다(127.0.0.1).
한 기기에서 devup-mcp 를 여러 개 띄우면 모두가 같은 플러그인을 씁니다.
MCP 클라이언트나 세션마다 devup-mcp 가 하나씩 뜨는 것은 정상입니다. 플러그인이
붙을 수 있는 포트는 manifest 에 적힌 하나뿐이라 먼저 뜬 쪽(호스트)이 포트를 잡고
플러그인을 받으며, 나머지는 호스트를 통해 읽습니다(중계). 플러그인 창은 하나면
됩니다. devup_figma_auth { "action": "status" } 의 paths.bridge.role 이 이
프로세스가 host 인지 relay 인지, host 가 포트를 쥔 프로세스(pid·버전·빌드)를
알려 주며, attachedFiles 는 어느 프로세스에서 보든 같습니다.
호스트를 띄운 세션이 끝나면 남은 devup-mcp 가운데 하나가 곧바로 포트를 이어받고,
플러그인은 2초마다 다시 붙기를 시도하므로 그 새 호스트에 저절로 붙습니다. 사람이
프로세스를 죽이거나 세션을 다시 띄울 필요가 없습니다. 이어받는 동안 status 는
role: "connecting" 으로 그 상태를 그대로 보고합니다.
그때 진행 중이던 수집도 끊기지 않습니다. 플러그인이 돌리고 있던 읽기는 새 호스트에
다시 붙는 대로 다시 보내집니다(읽기는 문서를 바꾸지 않으므로 두 번 돌아도 해가
없습니다). 파일 키를 보고하지 못하는 창(Dev Mode)도 이어서 찾을 수 있도록,
플러그인은 창을 열 때 한 번 정한 sessionId 를 hello 에 싣습니다 — 소켓이 바뀌어도
같은 창이면 같은 이름입니다.
devup-mcp 가 더는 기다리지 않는 읽기 — 요청한 프로세스가 떠났거나 시간이 다 됐다 —
에는 devup-cancel 이 옵니다. 차례를 기다리던 작업이면 돌리지 않고, 이미 돌고 있던
작업이면 스크립트는 멈출 수 없으니 답만 보내지 않습니다. 이 메시지와 sessionId 를
모르는 예전 빌드도 그대로 붙어 동작합니다.
중계는 같은 사용자의 devup-mcp 끼리만 이어집니다. 두 프로세스는 그 사용자만 읽을
수 있는 비밀값(Windows %USERPROFILE%\AppData\Local\devup-mcp\bridge-relay.key,
macOS·Linux /tmp/devup-mcp-<uid>/bridge-relay.key)으로 서로를 증명합니다. Unix 는
MCP 클라이언트마다 넘기는 HOME 이 달라도 같은 자리를 보도록 사용자 번호로 자리를
정합니다. 브라우저 페이지는 붙을 수 없습니다 — 중계 문은 Origin 이 붙은 요청을
모두, 플러그인 문은 Figma 플러그인 창(Origin: null)과 figma.com 이 아닌 요청을
거절합니다.
이 기능 이전의 devup-mcp 가 포트를 잡고 있으면 그 프로세스만 플러그인을 씁니다.
나중에 뜬 새 devup-mcp 는 그 사실을 알아보고 status 의 paths.bridge 에
issue: "legacy-host" 와 할 일 — 그 devup-mcp 를 띄운 클라이언트를 재시작하거나
갱신하기 — 을 적으며, 그 프로세스가 끝나면 스스로 포트를 이어받습니다. 예전
devup-mcp 는 자신을 밝히지 않으므로 운영체제에 물어 그 pid 와 실행 파일 경로를
host 에 적습니다. devup-mcp 가 아닌 프로그램이 포트를 잡은 경우는
issue: "foreign-program" 으로 따로 알리고, 그 프로그램을 holder 에 적습니다.
ws://127.0.0.1 은 쓸 수 없습니다. Figma 는 allowedDomains 에 그 주소를 적으면
"유효한 URL 이 아니다"라며 매니페스트 자체를 거부해 플러그인이 실행되지 않습니다.
localhost 로만 적어야 하고, UI 도 같은 이름으로 접속합니다. 실제 설치에서 확인한
동작입니다.
파일을 하나만 열어 두십시오. 플러그인은 붙을 때 figma.fileKey 로 자기 파일을
알리는데, 이 값이 비어 오는 경우가 있습니다(Dev Mode 에서 확인). 그때도 연결은
등록되지만, 어느 파일인지 모르므로 혼자 붙어 있을 때만 읽기를 맡습니다. 키 없는
플러그인이 둘 이상이면 어느 쪽이 대상 파일인지 가릴 수 없어 공식 MCP 로 넘어갑니다 —
엉뚱한 파일을 읽어 주는 것보다 낫기 때문입니다.
src/generated/ 는 npm run build 가 devup-mcp 의 원본에서 만들어 냅니다.
crates/devup-mcp-figma/src/scripts/*.js ← 원본 (공식 MCP 경로도 이것을 씀)
↓ plugin/scripts/gen-scripts.mjs
plugin/src/generated/scripts.js ← 중간 산출물 (커밋하지 않음)
↓ rspack + scripts/inline-ui.mjs
plugin/dist/{code.js,ui.html} ← 최종 번들 (커밋함, CI 가 대조)
두 경로가 같은 소스를 쓰므로 봉투가 갈라질 수 없습니다. 원본의 플레이스홀더는 함수 인자로 바뀌고, 치환되지 않은 것이 남으면 빌드가 실패합니다.
__DEVUP_SNAPSHOT_CURSOR__ 만 예외로 그대로 남습니다. 값이 아니라 Rust 디코더와
공유하는 마커 노드 id 라서, 치환하면 페이지네이션이 조용히 깨집니다.