みなさんこんにちは。最近渋谷でクレカを落としたところ、偶然弊社の人事が拾って届けてくれたことで事なきを得たラッキーボーイ、柳(@pagu0602)です。 本記事は 2025 年 6 月 6 日に公開されたSpindle MCP で変わるデザインシステムの開発 ~ Figma 連携で実現する超高速開発 ~で触れられていた、Figma Dev Mode MCP Server について、具体的な利用方法や特徴を紹介します。

サードパーティ製 Figma MCP Server

デザインツール「Figma」のデザインデータをテキストデータとしてコンテキストに追加できる MCP ツールが登場しました。 有名なものだと Figma-Context-MCPが挙げられます。

これを受けて、Ameba でも Figma MCP Server を利用して開発がどのように変化するか検証しました。

機能とその実現方法・メリット

サードパーティ製の Figma MCP Server は Figma API を利用し、Figma のデザインデータから「色」や「サイズ」等の値を参照して返却できます。以下の機能を提供しています。

  • Figma API を利用してデザインファイルの情報を JSON 形式で取得
  • 複雑な API レスポンスを簡素化し、AI が理解しやすい形式に変換
  • レイアウト、コンポーネント、スタイリング情報を構造化して提供

デザインのスクリーンショットを元にコーディングをしてもらうことも可能ですが、これには以下の課題がありました。

  1. 視覚情報のみ:色やサイズなど表面的な情報のみ
  2. コンポーネント情報の欠如:どのコンポーネントが使われているか不明
  3. デザインシステムとの連携不足:変数やトークンの情報が取得できない
  4. 精度の低さ:AI が推測に頼る部分が多い

Figma API を通じて直接情報を取得することで、以下を正確に取得できるため、より精度の高い出力を期待できるようになりました。

  • メタデータ:コンポーネント名、変数、スタイル情報
  • 構造情報:オートレイアウト設定、制約条件
  • デザインシステム連携:デザイントークン
  • 正確性:推測ではなく実際のデータに基づく実装

Figma 公式 Dev Mode MCP Server の登場

2025 年 6 月 4 日、Figma 公式から「Dev Mode MCP Server」がベータ版として発表されました。

Available on a Dev or Full seat on the Professional, Organization, or Enterprise plans

Professional、Organization、または Enterprise プランにおいて、Dev または Full シートで利用可能な機能です。

機能と使い方

Dev Mode MCP Server は以下の機能を提供しており、Figma API からは取得できない Code Connect の情報などを取得できます。

  • get_code: コード生成(デフォルトは React + Tailwind、XML での返却も可能)
  • get_image: 選択したデザイン要素やフレーム全体のスクリーンショット取得
  • get_variable_defs: Figma で定義されたデザイン変数やトークンの値・名前・コードシンタックスを取得
  • get_code_connect_map: Code Connect 設定に基づく既存コードベースとの連携情報を取得

1. サーバー有効化

  • Figma デスクトップアプリケーションを最新にします。
  • Figma デスクトップアプリのメニュー → Preferences → “Enable Dev Mode MCP Server” で有効化

2. ツールでの設定(Cursor での設定方法)

{
  "mcpServers": {
    "Figma": {
      "url": "http://127.0.0.1:3845/sse"
    }
  }
}

3. 利用方法

Figma で要素を選択し、 “Copy link to selection” などで要素のリンクを取得し、プロンプトに渡す。

この際、「Figma のリンクを開くことができません」等の返答が返ってくることがあります。

URLを添付しFigmaデザインについての説明を求めたところ、Figmaにアクセス出来ない旨が表示されているスクリーンショット。

rule に以下のように MCP Server を参照する旨を追加することで改善する可能性があります。

Figmaのリンクを受け取った場合はFigma MCPを利用して情報を取得してください。

実装検証と出力結果の分析

実際に以下のボタンを入力し、レスポンスを確認してみます。 ボタンは Spindle の Component と Code Connect で連携されています。 プロジェクトには CSS Modules を採用したシンプルな React プロジェクトを利用し、その他の依存は持たない状態で実行します。

「お問い合わせ」と書かれた見出し、テキストエリアとそのラベル、注意事項、丸みを帯びた送信ボタンが表示されている。

以下のようなプロンプトで実行しました。

@https://www.figma.com/design/xxxxxx

このFigmaデザインを実装してください。

実行結果は以下のようになりました。(詳細なコードは折りたたんでます。)

実行結果のコード
import Button from "https://github.com/openameba/spindle/blob/main/packages/spindle-ui/src/Button/Button.tsx"

export default function ContactFormResponsive() {
  return (
    <div
      className="bg-[#ffffff] box-border content-stretch flex flex-col items-center justify-start p-0 relative size-full"
      data-name="Contact Form / Responsive"
      id="node-3405_144"
    >
      <div
        className="bg-[#ffffff] min-h-14 relative shrink-0 w-full"
        data-name="heading"
        id="node-3405_145"
      >
        <div className="flex flex-row items-center min-h-inherit relative size-full">
          <div className="box-border content-stretch flex flex-row gap-4 items-center justify-start min-h-inherit px-4 py-3 relative w-full">
            <div
              className="basis-0 font-['Hiragino_Sans:W6',_sans-serif] grow leading-[0] min-h-px min-w-px not-italic relative shrink-0 text-[#08121a] text-[20px] text-left"
              id="node-I3405_145-402_35731"
            >
              <p className="block leading-[1.4]">お問い合わせ</p>
            </div>
            <div
              className="absolute bg-[rgba(8,18,26,0.08)] bottom-0 h-px left-0 right-0"
              data-name="05 Border / Full"
              id="node-I3405_145-592_33527"
            />
          </div>
        </div>
      </div>
      <div
        className="bg-[#ffffff] relative shrink-0 w-full"
        data-name="main"
        id="node-3405_147"
      >
        <div className="flex flex-col items-center relative size-full">
          <div className="box-border content-stretch flex flex-col gap-8 items-center justify-start p-[16px] relative w-full">
            <div
              className="box-border content-stretch flex flex-col gap-3 items-start justify-start max-w-[640px] p-0 relative shrink-0 w-full"
              data-name="detail"
              id="node-3405_151"
            >
              <div
                className="box-border content-stretch flex flex-row gap-[9px] items-end justify-start p-0 relative shrink-0 w-full"
                data-name="Title Group + Visible"
                id="node-I3405_151-373_35822"
              >
                <div
                  className="basis-0 box-border content-stretch flex flex-col gap-1 grow items-start justify-start min-h-px min-w-px p-0 relative shrink-0"
                  data-name="Title Group"
                  id="node-I3405_151-373_35823"
                >
                  <div
                    className="box-border content-stretch flex flex-row gap-[11px] items-start justify-start p-0 relative shrink-0 w-full"
                    data-name="Title + Required"
                    id="node-I3405_151-373_35824"
                  >
                    <div
                      className="font-['Hiragino_Sans:W6',_sans-serif] leading-[0] not-italic relative shrink-0 text-[14px] text-[rgba(8,18,26,0.74)] text-left text-nowrap"
                      id="node-I3405_151-373_35825"
                    >
                      <p className="block leading-[1.4] whitespace-pre">
                        お問い合わせ詳細を入力
                      </p>
                    </div>
                  </div>
                </div>
              </div>
              <div
                className="box-border content-stretch flex flex-col gap-2 h-28 items-start justify-start min-h-[70px] p-0 relative shrink-0 w-full"
                data-name="Field / Area"
                id="node-I3405_151-373_35831"
              >
                <div
                  className="basis-0 bg-[#ffffff] grow min-h-12 min-w-px relative rounded-lg shrink-0 w-full"
                  data-name="Field"
                  id="node-I3405_151-373_35831-373_35762"
                >
                  <div className="absolute border border-[rgba(8,18,26,0.09)] border-solid inset-0 pointer-events-none rounded-lg" />
                  <div className="flex flex-row items-center min-h-inherit relative size-full">
                    <div className="box-border content-stretch flex flex-row items-center justify-start min-h-inherit px-[17px] py-[13px] relative size-full">
                      <div
                        className="basis-0 font-['Hiragino_Sans:W3',_sans-serif] grow h-full leading-[0] min-h-px min-w-px not-italic overflow-ellipsis overflow-hidden relative shrink-0 text-[16px] text-[rgba(8,18,26,0.3)] text-left text-nowrap"
                        id="node-I3405_151-373_35831-373_35763"
                      >
                        <p className="[text-overflow:inherit] [text-wrap-mode:inherit]\' [white-space-collapse:inherit] block leading-[1.4] overflow-inherit">
                          &nbsp;
                        </p>
                      </div>
                    </div>
                  </div>
                </div>
              </div>
            </div>
            <div
              className="font-['Hiragino_Sans:W3',_sans-serif] leading-[0] max-w-[640px] not-italic relative shrink-0 text-[12px] text-[rgba(8,18,26,0.74)] text-left w-full"
              id="node-3405_152"
            >
              <p className="leading-[1.4]">
                <span className>
                  当社は、お客様より取得する個人情報を、お問い合わせいただいたご質問やご要望へ回答する目的で利用いたします。なお、当社の個人情報保護に関する基本方針については、
                </span>
                <span className="[text-decoration-line:underline] [text-decoration-skip-ink:none] [text-decoration-style:solid] [text-underline-position:from-font] text-[#237b31]">
                  プライバシーポリシー
                </span>
                <span className>をご確認ください。</span>
              </p>
            </div>
            <Button
              size="large"
              variant="contained"
              layout="fullWidth"
              iconPosition="start"
            >
              送信する
            </Button>
          </div>
        </div>
      </div>
    </div>
  );
}

また、レスポンスを返す際に、以下のようなプロンプトも返されていることが確認できました。

Convert the Tailwind to vanilla CSS if not already used in the codebase. Do not install any dependencies.
These variables are contained in the design: var(--color-surface-accent-primary): #298737.
If the node is connected to a code component in the users codebase via Code Connect, the connected component will be included in the markup as a JSX component, with imports at the top of the file.The source of the code component will be in the import path, e.g. import {ComponentName } from 'path/to/component'; IMPORTANT: the import path may not be relative/may point to a URL, so you should search for the component in the codebase and MUST use it if possible.

実装したコンポーネントは以下のようになりました。(詳細なコードは折りたたんでます。)

「お問い合わせ」と書かれた見出し、テキストエリアとそのラベル、注意事項、幅いっぱいに広がって少しだけ角が丸みを帯びた長方形の送信ボタンが表示されている。
ContactForm.tsxの内容
import React, { useState } from "react";
import "./ContactForm.css";

export default function ContactForm() {
  const [message, setMessage] = useState("");

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    // フォーム送信のロジックをここに実装
    console.log("送信されたメッセージ:", message);
  };

  const handleTextareaChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
    setMessage(e.target.value);
  };

  return (
    <div className="contact-form-container">
      <div className="contact-form-heading">
        <div className="contact-form-heading-inner">
          <div className="contact-form-heading-content">
            <h1 className="contact-form-title">お問い合わせ</h1>
            <div className="contact-form-border" />
          </div>
        </div>
      </div>

      <div className="contact-form-main">
        <div className="contact-form-main-inner">
          <form className="contact-form-main-content" onSubmit={handleSubmit}>
            <div className="contact-form-field-group">
              <div className="contact-form-label-group">
                <div className="contact-form-label-container">
                  <div className="contact-form-label-title">
                    <label
                      className="contact-form-label-text"
                      htmlFor="message"
                    >
                      お問い合わせ詳細を入力
                    </label>
                  </div>
                </div>
              </div>

              <div className="contact-form-textarea-container">
                <div className="contact-form-textarea-field">
                  <div className="contact-form-textarea-inner">
                    <div className="contact-form-textarea-content">
                      <textarea
                        id="message"
                        className="contact-form-textarea"
                        value={message}
                        onChange={handleTextareaChange}
                        placeholder=""
                        rows={4}
                      />
                    </div>
                  </div>
                </div>
              </div>
            </div>

            <p className="contact-form-description">
              当社は、お客様より取得する個人情報を、お問い合わせいただいたご質問やご要望へ回答する目的で利用いたします。なお、当社の個人情報保護に関する基本方針については、
              <a href="#" className="contact-form-description-link">
                プライバシーポリシー
              </a>
              をご確認ください。
            </p>

            <button type="submit" className="contact-form-submit-button">
              送信する
            </button>
          </form>
        </div>
      </div>
    </div>
  );
}
ContactForm.cssの内容
/* CSS Custom Properties from Figma design */
:root {
  --color-text-high-emphasis: #08121a;
  --color-text-medium-emphasis: #08121abd;
  --color-text-disable: #08121a4d;
  --color-text-accent-primary: #237b31;
  --color-surface-primary: #ffffff;
  --color-surface-accent-primary: #298737;
  --color-border-low-emphasis: #08121a14;
  --color-border-medium-emphasis: #08121a4d;
  --padding-base: 16px;
}

.contact-form-container {
  background-color: var(--color-surface-primary);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  padding: 0;
  width: 100%;
  height: 100%;
  box-sizing: border-box;
}

.contact-form-heading {
  background-color: var(--color-surface-primary);
  min-height: 56px;
  width: 100%;
  position: relative;
  flex-shrink: 0;
}

.contact-form-heading-inner {
  display: flex;
  flex-direction: row;
  align-items: center;
  min-height: inherit;
  width: 100%;
  position: relative;
}

.contact-form-heading-content {
  display: flex;
  flex-direction: row;
  gap: 16px;
  align-items: center;
  justify-content: flex-start;
  min-height: inherit;
  padding: 12px 16px;
  width: 100%;
  box-sizing: border-box;
  position: relative;
}

.contact-form-title {
  flex: 1;
  font-family: "Hiragino Sans", sans-serif;
  font-weight: 600;
  font-size: 20px;
  line-height: 1.4;
  color: var(--color-text-high-emphasis);
  text-align: left;
  min-height: 1px;
  min-width: 1px;
  position: relative;
  flex-shrink: 0;
}

.contact-form-border {
  position: absolute;
  background-color: var(--color-border-low-emphasis);
  bottom: 0;
  height: 1px;
  left: 0;
  right: 0;
}

.contact-form-main {
  background-color: var(--color-surface-primary);
  width: 100%;
  position: relative;
  flex-shrink: 0;
}

.contact-form-main-inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 100%;
  position: relative;
}

.contact-form-main-content {
  display: flex;
  flex-direction: column;
  gap: 32px;
  align-items: center;
  justify-content: flex-start;
  padding: var(--padding-base);
  width: 100%;
  box-sizing: border-box;
}

.contact-form-field-group {
  display: flex;
  flex-direction: column;
  gap: 12px;
  align-items: flex-start;
  justify-content: flex-start;
  max-width: 640px;
  width: 100%;
  padding: 0;
  box-sizing: border-box;
  flex-shrink: 0;
}

.contact-form-label-group {
  display: flex;
  flex-direction: row;
  gap: 9px;
  align-items: flex-end;
  justify-content: flex-start;
  width: 100%;
  padding: 0;
  box-sizing: border-box;
  flex-shrink: 0;
}

.contact-form-label-container {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 4px;
  align-items: flex-start;
  justify-content: flex-start;
  min-height: 1px;
  min-width: 1px;
  padding: 0;
  box-sizing: border-box;
  flex-shrink: 0;
}

.contact-form-label-title {
  display: flex;
  flex-direction: row;
  gap: 11px;
  align-items: flex-start;
  justify-content: flex-start;
  width: 100%;
  padding: 0;
  box-sizing: border-box;
  flex-shrink: 0;
}

.contact-form-label-text {
  font-family: "Hiragino Sans", sans-serif;
  font-weight: 600;
  font-size: 14px;
  line-height: 1.4;
  color: var(--color-text-medium-emphasis);
  text-align: left;
  white-space: nowrap;
  flex-shrink: 0;
}

.contact-form-textarea-container {
  display: flex;
  flex-direction: column;
  gap: 8px;
  height: 112px;
  align-items: flex-start;
  justify-content: flex-start;
  min-height: 70px;
  width: 100%;
  padding: 0;
  box-sizing: border-box;
  flex-shrink: 0;
}

.contact-form-textarea-field {
  flex: 1;
  background-color: var(--color-surface-primary);
  min-height: 48px;
  min-width: 1px;
  border-radius: 8px;
  width: 100%;
  flex-shrink: 0;
  position: relative;
}

.contact-form-textarea-field::before {
  content: "";
  position: absolute;
  border: 1px solid var(--color-border-low-emphasis);
  border-radius: 8px;
  inset: 0;
  pointer-events: none;
}

.contact-form-textarea-inner {
  display: flex;
  flex-direction: row;
  align-items: center;
  min-height: inherit;
  width: 100%;
  height: 100%;
  position: relative;
}

.contact-form-textarea-content {
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: flex-start;
  min-height: inherit;
  padding: 13px 17px;
  width: 100%;
  height: 100%;
  box-sizing: border-box;
}

.contact-form-textarea {
  flex: 1;
  font-family: "Hiragino Sans", sans-serif;
  font-weight: 300;
  font-size: 16px;
  line-height: 1.4;
  color: var(--color-text-high-emphasis);
  text-align: left;
  min-height: 1px;
  min-width: 1px;
  flex-shrink: 0;
  width: 100%;
  height: 100%;
  border: none;
  outline: none;
  background: transparent;
  resize: none;
  overflow: hidden;
}

.contact-form-textarea::placeholder {
  color: var(--color-text-disable);
}

.contact-form-description {
  font-family: "Hiragino Sans", sans-serif;
  font-weight: 300;
  font-size: 12px;
  line-height: 1.4;
  color: var(--color-text-medium-emphasis);
  text-align: left;
  max-width: 640px;
  width: 100%;
  flex-shrink: 0;
}

.contact-form-description-link {
  text-decoration: underline;
  text-decoration-skip-ink: none;
  text-decoration-style: solid;
  text-underline-position: from-font;
  color: var(--color-text-accent-primary);
}

.contact-form-submit-button {
  background-color: var(--color-surface-accent-primary);
  color: var(--color-surface-primary);
  font-family: "Hiragino Sans", sans-serif;
  font-weight: 600;
  font-size: 16px;
  line-height: 1.4;
  border: none;
  border-radius: 8px;
  padding: 16px 24px;
  width: 100%;
  max-width: 640px;
  min-height: 56px;
  cursor: pointer;
  transition: background-color 0.2s ease;
  display: flex;
  align-items: center;
  justify-content: center;
}

.contact-form-submit-button:hover {
  background-color: var(--color-text-accent-primary);
}

.contact-form-submit-button:focus {
  outline: 2px solid var(--color-text-accent-primary);
  outline-offset: 2px;
}

.contact-form-submit-button:active {
  background-color: var(--color-text-accent-primary);
  transform: translateY(1px);
}

先程のデザインはオートレイアウト等を駆使して綺麗に作られたデザインでしたが、Figma に慣れていない人(筆者)が作ったデザインの場合でも試してみます。

全体的に左寄りに「お問い合わせ」と書かれた見出し、テキストエリアとそのラベル、注意事項、少しだけ角が丸みを帯びた長方形の送信ボタンが表示されている。

このように、オートレイアウトが適切に設定されていない場合には、余白や位置等の出力結果の精度は低くなります。

出力結果についての解説

MCP Server から返ってきたレスポンスを確認すると、以下の特徴が見られます。

  • Tailwind が利用されていない場合は依存を追加するのではなく、CSS に変換することが指示されている。
  • 変数化された色の名前と値が返ってきており、カスタムプロパティで定義されている。
  • Code Connect を利用している場合の利用方法についての説明が含まれている。
  • Code Connect されたコンポーネントの詳細なスタイルは返ってこない。
  • オートレイアウトを利用している場合は、適切に余白や位置が計算されている。

今回、Code Connect された Spindle の依存を持たないプロジェクトに追加したところ、CSS Modules でのアウトプットが行われました。 レスポンスは React + Tailwind 形式ですが、プロジェクトに合わせた出力結果になるようにチューニングされていることがわかりました。 また、Code Connect を利用しているコンポーネントが存在する場合、そのコンポーネントの詳細なスタイルは返ってこないので精度が低くなることがわかります。

※様々なプロパティにおいて、設定値がそのまま反映されるため不要なプロパティが多くなることがありますが、これは reset.css 等の情報を渡したり rule 等で振る舞いが変わらない値を削除する指示を与えると改善される可能性があります。

サードパーティ製の MCP Server との違い

サードパーティ製の MCP Server はあくまでも API から返却された JSON を最適化するにとどまっており、Figma API から返却されない情報を補完できません。 特に、Figma API は Code Connect の情報を返却しないため、Code Connect を利用したデザインを行っている場合は大きな差になり得ます。

開発ワークフローへの影響

Dev Mode MCP Server の導入により、従来の開発ワークフローが大きく改善されました。

これまでは Figma のデザインを元に余白や色、フォントサイズの調整等を行っていたのですが、どうしても値の設定ミス等が発生しており、コードレビュー時や動作確認時に発覚するという手戻りが発生していました。 その問題に対して、Dev Mode MCP Server を利用することで、実装の大枠を作ってもらい一部マークアップを手動修正するというプロセスを取ることで、先述した問題を解決できました。 また、筆者はセレクタの命名が苦手で時間がかかるタイプなのですが、デザインのコンテキストを汲み取ってセレクタの命名なども行ってくれるため、作業効率が向上しました。

今後期待したいこと

Figma の作り込み不足を AI が補完

今回筆者が作ったオートレイアウト等を利用しないデザインでは、要素間の余白や位置等が意図した出力になりませんでした。 AI フレンドリーなデザインを用意するという方向で解決も可能ですが、余白自体は Figma デザインから得ることができる情報ではあるため、作り込みが不足している部分を補完するという未来にも期待したいです。

アノテーションの利用

記事の執筆時点ではアノテーションがコンテキストとして渡っていなかったのですが、6 月 26 日の発表で渡るようになるアップデートが発表されました。

https://x.com/figma/status/1937879218668609889

これにより Figma からアニメーションやブレイクポイント等の動作仕様を参照できるので、Figma に情報を集約することでコードの質がより上がることに期待したいです。

まとめ

以上、「Dev Mode MCP Server」の検証について紹介させていただきました。現段階ではデザインを作り込む必要はあるものの、ほんの 1 ヶ月前には物足りなかった精度がここまでアップデートされたことに驚いています。 より速く、高品質なデリバリーを行うためにこういった仕組みを引き続き取り入れ、開発生産性を高めていくことが重要だと感じました。

アバター画像
2017年にサイバーエージェントに入社。 Ameba関連の開発に従事しているWebエンジニアです。