# Figma MCP 이미지 삽입 가이드
## 1. 배경
- 공식 TalkToFigma에는 이미지 삽입 기능이 없음
- 기능을 커스텀하려면 GitHub에서 소스를 클론해야 함
- https://github.com/yerininin/F_MCP → 플러그인에 이미지 채우기 기능 추가해서 올려둠
---
## 2. 왜 이 구조가 필요한가
```
문제:
Figma 플러그인 샌드박스 → 외부 CDN URL 직접 접근 차단
해결:
MCP 서버(Node.js)에서 이미지 fetch ← 서버는 제한 없음
→ base64로 인코딩
→ WebSocket으로 플러그인에 전달
→ 플러그인이 Figma API로 이미지 적용
```
---
## 3. 수정한 파일 (2곳)
### A. MCP 서버 — `server.ts`
서버 사이드에서 이미지를 fetch → base64 변환 → WebSocket으로 전송
```typescript
// Set Image Fill Tool
server.tool(
"set_image_fill",
"Fetch an image from a URL and set it as the fill of a node in Figma.",
{
nodeId: z.string().describe("The ID of the node to apply the image fill to"),
imageUrl: z.string().url().describe("The URL of the image to fetch and apply"),
scaleMode: z
.enum(["FILL", "FIT", "CROP", "TILE"])
.optional()
.describe("How the image should be scaled within the node (default: FILL)"),
},
async ({ nodeId, imageUrl, scaleMode }: any) => {
try {
// 서버에서 이미지를 가져옴 (CORS 제한 없음)
const response = await fetch(imageUrl);
if (!response.ok) {
throw new Error(`Failed to fetch image: ${response.status} ${response.statusText}`);
}
const arrayBuffer = await response.arrayBuffer();
const base64 = Buffer.from(arrayBuffer).toString("base64");
// base64로 인코딩된 이미지 데이터를 Figma 플러그인에 전송
const result = await sendCommandToFigma("set_image_fill", {
nodeId,
imageBase64: base64,
scaleMode: scaleMode || "FILL",
});
const typedResult = result as { id: string; name: string };
return {
content: [
{
type: "text",
text: `Set image fill on node "${typedResult.name}" (${typedResult.id}) from URL: ${imageUrl}`,
},
],
};
} catch (error) {
return {
content: [
{
type: "text",
text: `Error setting image fill: ${error instanceof Error ? error.message : String(error)}`,
},
],
};
}
}
);
```
### B. Figma 플러그인 — `code.ts`
플러그인에서 이미지 바이너리 데이터를 받아 Figma 이미지로 변환하는 핸들러 추가
```typescript
case 'set_image_fill': {
const targetNode = figma.getNodeById(msg.nodeId) as GeometryMixin | null;
if (targetNode && 'fills' in targetNode && msg.imageData) {
const imageBytes = new Uint8Array(msg.imageData);
const image = figma.createImage(imageBytes);
targetNode.fills = [{
type: 'IMAGE',
imageHash: image.hash,
scaleMode: msg.scaleMode || 'FILL'
}];
figma.ui.postMessage({ type: 'result', success: true, nodeId: msg.nodeId, commandId: msg.commandId });
} else if (!msg.imageData) {
figma.ui.postMessage({ type: 'result', success: false, error: '이미지 데이터 없음', commandId: msg.commandId });
} else {
figma.ui.postMessage({ type: 'result', success: false, error: '노드를 찾을 수 없거나 fill을 지원하지 않음', commandId: msg.commandId });
}
break;
}
```
---
## 4. 전체 흐름
```
Claude Code
→ MCP 서버: set_image_fill(nodeId, imageUrl)
→ fetch(imageUrl) → base64 변환
→ WebSocket (port 3055) 전송
→ Figma 플러그인 UI 수신
→ code.js: base64 → Uint8Array → figma.createImage()
→ node.fills에 이미지 적용
```
---
## 5. 설정 방법
### .mcp.json (프로젝트 루트)
Claude Code가 이미지 삽입 도구를 사용할 수 있도록 MCP 서버를 등록한다.
```json
{
"mcpServers": {
"set_node_image_fill": {
"command": "node",
"args": ["D:/figma_mcp_test/mcp-ws-image/dist/index.js"]
}
}
}
```
> **참고:** `.mcp.json`은 최초 1회만 설정하면 된다. 이후 Claude Code를 재시작해도 자동으로 읽힌다. 단, 세션 도중에 새로 추가한 경우에만 재시작이 필요하다.
---
## 6. 사용법
사용자가 직접 도구를 호출할 필요 없다.
이미지 URL과 함께 요청하면 Claude Code가 내부적으로 `set_node_image_fill`을 호출한다.
**사용자 요청 예시:**
```
"이 이미지로 배너 만들어줘: https://example.com/product.png"
```
**Claude Code가 내부적으로 실행하는 도구:**
```
set_node_image_fill(
channel: "채널명", // Figma 플러그인 Connect 후 표시되는 채널
nodeId: "43:48", // 이미지를 넣을 노드 ID
imageUrl: "https://...", // 이미지 URL
scaleMode: "FILL" // 이미지 채우기 모드
)
```
### scaleMode 옵션
| 모드 | 동작 |
|------|------|
| **FILL** | 영역을 꽉 채움, 이미지 일부 잘릴 수 있음 (기본값) |
| **FIT** | 이미지 전체가 보임, 빈 공간 발생 가능 |
| **CROP** | 원하는 부분만 잘라서 표시 |
| **TILE** | 이미지를 타일 형태로 반복 |