はじめに

こんにちは。and factory フロントエンドエンジニアの坂内です。

施策バナーなどの画像をFigmaから書き出す作業が、地味に手間だと感じていました。1枚ずつ選択してエクスポートし、書き出したPNGを別のツールでWebPに変換して圧縮する。作業自体は難しくありませんが、工程が分断されているぶん、都度の切り替えコストがかかります

直近の施策では15枚ほどを書き出しました。枚数だけ見れば多くありませんが、1枚ごとに書き出しと変換を往復すると、それなりの時間を取られます。

きっかけは、会社で契約しているツールをもっと使い倒せないか、コーディングまわりをもう少し楽にできないか、と考えたことでした。調べるうちにFigmaのDev Mode MCPサーバーに行き着き、Claude Codeとつないで検証しています。この記事はその記録です。

やってみると、事前の想像と違う結果がいくつも出てきました。想定していたより権限のハードルが低かった一方、単発なら手動のほうが速いという結果も出ています。「圧縮したのにファイルが大きくなる」という予想外の挙動もありました。

注意: 本記事に登場するFigmaのレイヤー名とファイル名のうち、施策名や機能名を含むものは仮名に置き換えています。スクリーンショットでも該当箇所を伏せています。数値はすべて実測値です。


結論

先に結果を3点にまとめます。

  • 指定する範囲が速度をほぼ決める。ページ全体に近い範囲を渡すと68点を一括で取得できたが、時間がかかった
  • 1枚だけなら手動のほうが速い。AIを挟む価値は書き出し単体ではなく、書き出しから変換までを1つの指示で通せる点にある
  • WebP変換は画像の種類で品質を分ける必要がある。70ファイルで合計38.2%削減できた一方、4点は逆にサイズが増えた

検証環境

項目内容
実施時期2026年7月
OSmacOS
Figmaデスクトップアプリ(Dev Mode MCPサーバーはデスクトップ版のみ対応)
Figmaのプラン有料プラン
AIクライアントClaude Code
変換ツールsharp 0.35.3(npx 経由で実行。Node 20.9以上が必要)
Node.jsv20.9.0以上(sharp 0.35.3の要件)
対象ある施策ページのデザイン(最終的に70ファイルを取得)

その前に:URLを渡すだけでは足りなかった

MCPの設定を始める前に、claude.aiにFigmaのデザインURLをそのまま渡して画像を取得できないか試しました。結果は Site blocked the request (bot detection) でブロックされ、ページ自体を読み取れません。Figmaは認証が必要なサービスなので、汎用のWeb閲覧機能では中身に到達できないためです。

画像を扱うには専用の接続が要ると分かったので、MCPの設定に進みます。


権限は思ったより障害にならなかった

次に気にしていたのが権限です。Figma MCPサーバーのガイドには、デスクトップ版サーバーの利用にDev/Fullシートを含む有料プランが必要と書かれています。この記述を読んで、管理者にシートのアップグレードを相談する必要があるかもしれない、と身構えていました。

ところが実際に画面を開いてみると、特別な手続きなしでMCPサーバーを有効化できました。管理者への確認は不要でした。

調べ直すと、別ページのRate limits & accessに補足がありました。View / Collabシートにも月6回までのツール呼び出し枠が用意されており、有料プランでもこの上限が適用されます。アクセスはプランとシート種別の両方で決まります。View / Collabシートはプランに関わらず月6回で頭打ちです。一方、Dev / Fullシートの上限はプランで変わります(Starter・Professionalは1日200回、Organizationは1日600回)。

つまり、Dev/Fullシートでなければ一切使えない、というわけではありません。回数は多くないものの、検証や小さく試す段階であれば足ります。

同じところで足踏みしている方もいそうなので書いておくと、権限の相談より先に、自分の環境で有効化できるかを試すほうが早いです。シート種別はチームや組織の管理画面から確認できます。ただ、まず動かしてみたほうが話の早い場面もあります。仮に権限が足りなかった場合も、デザイナーの環境で有効化してもらい、そこへ接続する方法が残っています。


Claude Codeにつなぐ

接続までの手順はこれだけです。

  1. Figmaデスクトップアプリでファイルを開き、Dev Modeに切り替える(ショートカットは Shift + D
  2. 右サイドバーのインスペクトパネルにあるMCPセクションで、Server statusをEnabledにする
  3. Claude Code側でMCPサーバーを登録する

Claude Codeへの登録はコマンド1行です。

1
claude mcp add --transport sse figma-mcp http://127.0.0.1:3845/sse

ポート番号はFigma側の表示に従います。すでに登録済みで動作が怪しい場合は、claude mcp remove figma-mcp で一度削除してから登録し直すと解決しました。

有効化するとMCPセクションに Image source: Local server と表示されます。ここが重要な注意点です。画像のパスはFigmaアプリが起動している間だけ有効で、アプリを閉じるとリンクが切れます。取得したらすぐローカルに保存させる指示を出す必要があります。

Estimated tokensという目安

Figma上でレイヤーやフレームを選択すると、MCPパネルに Estimated tokens という項目が出てきます。そのノードをAIに渡した場合の推定トークン数です。

ページ全体にあたるフレームを選ぶと、この値は17.6kでした。

FigmaのMCPパネル。ページ全体のフレームを選択した状態で、Estimated tokensが17.6kと表示されている

一方、小さなノードを1つだけ選ぶと63まで下がります。

FigmaのMCPパネル。小さなノードを選択した状態で、Estimated tokensが63と表示されている

同じファイル内でも280倍近い差が出ます。この数値は後で効いてきます。範囲が広いほどここが大きくなり、処理時間にそのまま跳ね返るためです。どこまでを一度に渡すか迷ったときは、ここを見てから決めると外しにくくなります。

パネルには Copy example prompt というボタンもあり、選択中のノード向けのプロンプト例をそのままコピーできます。最初の1回はこれを使うと迷いません。


広い範囲を指定したら68点まとめて取れた

まず、ページ全体に近い大きめのノードを指定して試しました。指示はこう出しています。

1
2
3
@https://www.figma.com/design/xxxxx...
このデザインの画像アセットだけ取得して、./figma-exports フォルダに
わかりやすいファイル名で保存してください。実装はまだしないでください。

結果として、そのノード配下の画像を全68点取得できました。ヘッダーのアイコン、タブバーのアイコン、キャンペーンバナー、フォーム、ステータスバーまで、取りこぼしなく拾えています。

書き出された画像の一覧。campaign-section-background.png や divider-line.svg など用途の分かる名前が並んでいる。施策名・機能名にあたる部分は伏せている

ファイル名も期待以上でした。campaign-submit-button.pngdivider-line.svg のように、用途の分かる名前が自動で付いています。Figmaが返すURLはハッシュ値なので、そのまま保存されると何の画像か分かりません。ここを自動で解決してくれるのは助かる部分です。

一方で、処理にはかなり時間がかかりました。Estimated tokensで見えていたとおり、範囲が広いほど時間もトークン消費も増えます。

この特性を踏まえると、使い分けが見えてきます。

  • デザインシステムの棚卸しや全アセットの洗い出しをしたいとき:広い範囲を指定してよい。時間はかかるが一度で拾いきれる
  • 特定のバナーやアイコンだけが欲しいとき:Figma上で対象を選択して「選択範囲へのリンクをコピー」を使い、範囲を絞ったリンクを渡す

1枚だけなら手動のほうが速い

範囲を絞って1枚だけ取得する検証もしました。体感でも明確に速くなります。

ただし、ここで素朴な疑問が出てきました。1枚だけなら、手作業と比べてどうなのか。

方法所要時間の目安
手動(選択 → 右クリック → エクスポート → 保存)約10秒
AI経由(MCP呼び出し → 命名 → ダウンロード)数十秒〜1分

手動のほうが速いという結果でした。 1枚だけをすぐ欲しい場面なら、AIを挟む意味は薄いと言えます。

とはいえ、これは比較の仕方がずれているとも考えています。今回やりたかったのは書き出し単体の高速化ではなく、書き出しからWebP変換までを1つの指示で連続して回すことです。工程が分断されていることが元々の課題でした。

判断基準としてはこう整理しました。

ケース向いている方法
この1枚だけ欲しい手動。AIを挟む意味は薄い
複数枚まとめて欲しい、または書き出しから変換まで一気にやりたいAI経由。工程を切り替える手間がなくなる

レイヤー名の質がそのまま出力に出た

検証中に、精度を左右する要因がはっきり見える場面に出会いました。

shindan_kekka_02 というレイヤー名の画像を取得したところ、AIは shindan-result-02.png というファイル名で保存しました。日本語のローマ字表記から意味を汲み取り、英語に変換しています。

最初は精度が良かった例として受け止めましたが、よく考えるとそうではありません。元のレイヤー名がきちんと付けられていたから成立した結果です。これが Group 5Rectangle 23 のような自動生成名だったら、AIには手がかりがありません。

つまり、出力の質は「AIの賢さ」よりも「入力の質」に依存します。デザイン側のレイヤー命名がそのまま結果に出る、という当たり前の事実を実感しました。

この観点でいえば、日本語のままでも問題ありません。英語に直す必要はなく、中身の分かる名前さえ付いていれば読み取れます。


WebPに変換して圧縮した

一括取得した68点に、範囲を絞って個別に取得した分を加えた70ファイルを、sharpでWebPに一括変換しました。sharpはプロジェクトの依存に追加せず、npx から実行しています。

ここで1つ詰まりどころがありました。**対象のプロジェクトは.nvmrcでNode 16を指定していますが、sharp 0.35.3はNode 20.9.0以上を要求します。**今回はnpxで実行したため、プロジェクトとは別のバージョンのNodeが使われました。依存に追加する形だと、この段階で止まります。フォルダ内をループで処理する形にしておけば、前工程で何枚取得しても修正が要りません。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const sharp = require('sharp');
const fs = require('fs');
const path = require('path');

const inputDir = './figma-exports';
const outputDir = './output';

(async () => {
  await fs.promises.mkdir(outputDir, { recursive: true });
  const files = fs.readdirSync(inputDir).filter(f => /\.(png|jpe?g)$/i.test(f));

  for (const file of files) {
    const src = path.join(inputDir, file);
    const dest = path.join(outputDir, `${path.parse(file).name}.webp`);
    try {
      await sharp(src).webp({ quality: 80 }).toFile(dest);
      const before = fs.statSync(src).size;
      const after = fs.statSync(dest).size;
      if (after > before) console.warn(`増加: ${file} ${before} -> ${after}`);
    } catch (e) {
      console.error(`skip ${file}: ${e.message}`);
    }
  }
})();

出力先のフォルダはmkdirで先に作ります。ここを省くと1枚も書き出せずに終わります。またforEachではなくfor...ofで回している点も重要です。forEachはコールバックの完了を待たないため、全ファイルの変換が終わったかを判定できず、後述するサイズ比較を挟む余地がなくなります

品質は80で実行しています。結果は次のとおりです。

項目数値
変換前の合計1,469.1KB(1,504,330 bytes)
変換後の合計907.9KB(929,652 bytes)
削減561.2KB(-38.2%)

アイコン系やシンプルな図形は、50〜97%削減できたものもありました。ここは期待どおりです。


4点だけサイズが増えた

全体では削減できたものの、内訳を見ると逆にサイズの増えたファイルが4点ありました。

ファイル変換前変換後増減
header-background-texture.png12.0KB18.7KB+56.9%
photo-banner-01.png80.5KB97.7KB+21.3%
photo-banner-02.png75.8KB89.8KB+18.4%
shindan-result-02.png140.5KB164.3KB+16.9%

WebP変換後のサイズ比較。増加した4点の一覧と、合計1,469.1KBから907.9KBへ38.2%削減された結果が表示されている。ファイル名の一部は伏せている

圧縮したのに大きくなる、という結果は予想していませんでした。

最初は書き出し倍率を疑いました。@2xで書き出していたためです。しかしこれは原因ではありません。@2xはRetina対応として標準的なやり方で、そのまま続けて問題ない部分です。

増加した4点を並べてみると、いずれも写真やノイズ・テクスチャ系の画像でした。この種類の画像は元のPNGの時点で効率よく圧縮されていることが多く、品質80のWebP変換ではむしろ非効率になる場合があります。


画像の種類でqualityを分ける方針にした

対策として、リサイズではなく画像の種類によって品質値を分ける運用に切り替えました。

1
2
3
4
5
// 写真・テクスチャ系は品質を上げる
await sharp('photo.png').webp({ quality: 90 }).toFile('photo.webp');

// アイコン・シンプルな図形は品質を下げても十分
await sharp('icon.png').webp({ quality: 70 }).toFile('icon.webp');

ただし、この70と90という値は一般的な目安から置いたものです。何パターンか比較して求めた数値ではありません。実際にどこが最適かは、扱う画像の傾向によって変わるはずなので、運用しながら詰めていく予定です。

そのため、数値そのものより変換後に必ずサイズを比較する手順のほうを運用に組み込みました。増えてしまったファイルは、品質値を個別に調整するか、元のPNGのまま使う判断をします。

一括変換して終わりにせず、結果を確認する工程を挟みます。地味ですが、今回いちばん実務に効きそうな学びでした。「圧縮すれば小さくなる」という前提は必ずしも成り立たない、という発見が収穫です。


現時点での使いどころ

ここまでの結果を踏まえると、向き不向きがはっきりしています。

向いている場面

  • 複数枚の画像をまとめて書き出したいとき
  • デザインシステムの棚卸しなど、全アセットを一度に洗い出したいとき
  • 書き出しからWebP変換までを続けて処理したいとき
  • ハッシュ値のファイル名を、用途の分かる名前に整理したいとき

向いていない場面

  • 1枚だけ欲しいとき。手動のほうが速い
  • レイヤー名が自動生成のまま整理されていないとき。手がかりを欠いて精度が落ちる
  • 変換結果を確認せず任せきりにしたいとき。サイズの増えるケースを見逃す

まとめ

  • 範囲の指定が速度を決める。広く取れば一度に拾えるが時間がかかり、絞れば速い。用途で使い分ける
  • 単発の書き出しなら手動が速い。AIを挟む価値は、書き出しから変換までを一気通貫で回せる点にある
  • 精度はデザイン側の入力の質に依存する。レイヤー名が整っていれば、日本語でも意味を汲み取って命名してくれる
  • WebP変換は種類ごとに品質を分ける。一律の設定では、写真・テクスチャ系でサイズが増える
  • 権限は先に試してから相談する。シートのアップグレードを待たずに有効化できた

今後試したいこと

今回は書き出しとWebP変換までを検証しました。当初考えていた構想には続きがあり、次の3つはまだ手を付けられていません。

既存のコーディングルールに沿った実装の自動化

命名規則やコンポーネント設計のパターン、デザイントークンとコード側変数の対応をAIに渡したうえで、実装まで任せられるかを試したいと考えています。ルールをドキュメントとして整理するところから始める必要があります。

デザイナーとの連携フロー

今回の検証で、レイヤー命名の質が結果に直結すると分かりました。この点をどう共有し、無理なく続けられる形にするかは、これから相談していく部分です。

品質値の詰め

70と90という値は目安のまま置いています。画像の種類ごとに何パターンか比較し、サイズと見た目のバランスが取れる値を実測で決めたいと考えています。

いずれも試したら、あらためて書きます。


参考リンク