FigmaでAIを強化する | 第3章：デザインデータ分析：AIによるUIインサイト

## はじめに

第2章では、**Figma**のコメントを読み書きするMCPサーバーを構築し、フィードバックを自動化するエージェントを実現しました。これにより、AIがデザインコメントを処理・生成できるようになり、デザインプロセスの効率が向上しました。今回は、この基盤を活用して、Figmaのデザインデータを分析し、UIの品質や一貫性に関するインサイトを生成する**UI分析エージェント**を構築します。

この第3章では、Figmaのファイルからノードデータ（例：色、フォント、レイアウト）を抽出し、アクセシビリティやデザインの整合性を評価するMCPサーバーを実装します。たとえば、AIが色のコントラスト不足を検出したり、フォントサイズのばらつきを指摘したりできます。コード例とステップごとのガイドで、UI分析AIの構築を体験しましょう。さあ、始めましょう！

## UI分析エージェントとは？

UI分析エージェントは、Figmaのデザインデータを解析し、UIの品質やユーザー体験に関するインサイトを提供するAIです。MCPサーバーを介して、以下のような機能を実現できます：
- **アクセシビリティチェック**：色のコントラスト比やフォントサイズを評価。
- **一貫性分析**：デザイン全体での色やフォントのばらつきを検出。
- **提案生成**：分析結果に基づいて改善案をコメントとして投稿。

### ユースケース
- デザインレビュー：AIがアクセシビリティの問題を自動検出し、修正案を提案。
- 品質管理：デザインシステムの一貫性を維持し、ブランドガイドラインを遵守。
- チームコラボレーション：分析結果をチームと共有し、フィードバックを効率化。

## 開発環境の準備

第2章の環境を基に、以下の追加準備を行います：
- **Python 3.8以降**、**mcpライブラリ**、**requestsライブラリ**、**Claude Desktop**：これまでと同じ。
- **webcolorsライブラリ**：色の分析用。
- **Figmaファイル**：分析用のデザインデータを準備。

インストールコマンド：
```bash
pip install webcolors
```

### Figmaファイルの準備
1. **テストデザインの作成**：
   - 第2章のFigmaファイル（例：`MCP-Design`）を使用。
   - 以下の要素を含むデザインを追加：
     - ボタン（背景色：青 `#007BFF`、テキスト色：白 `#FFFFFF`）
     - テキスト（フォントサイズ：16px、色：黒 `#000000`）
     - 別のテキスト（フォントサイズ：14px、色：グレー `#666666`）
   - コメントでノードID（例：`1:2`）を確認し、記録。
2. **APIトークンの確認**：
   - 第2章のPersonal Access Tokenを再利用（ファイルデータとコメントの読み書きに必要）。
3. **環境変数**：
   第2章の`.env`ファイル（`FIGMA_TOKEN`、`FIGMA_FILE_ID`）を再利用。

## コード例：UI分析用MCPサーバー

以下のMCPサーバーは、Figmaのノードデータから色やフォント情報を抽出し、アクセシビリティと一貫性を分析します。さらに、分析結果をコメントとして投稿します。

```python
from mcp import MCPServer
import os
from dotenv import load_dotenv
import requests
import webcolors
from math import sqrt

class FigmaAnalysisServer(MCPServer):
    def __init__(self, host, port, figma_token, file_id):
        super().__init__(host, port)
        self.figma_token = figma_token
        self.file_id = file_id
        self.headers = {"X-Figma-Token": figma_token}
        self.register_resource("analyze_ui", self.analyze_ui)
        self.register_tool("add_comment", self.add_comment)

    def calculate_contrast(self, color1, color2):
        # 簡易的なコントラスト比計算（WCAG基準の近似）
        def luminance(rgb):
            r, g, b = [c / 255.0 for c in rgb]
            for c in (r, g, b):
                c = c / 12.92 if c <= 0.03928 else ((c + 0.055) / 1.055) ** 2.4
            return 0.2126 * r + 0.7152 * g + 0.0722 * b
        rgb1 = webcolors.hex_to_rgb(color1)
        rgb2 = webcolors.hex_to_rgb(color2)
        l1, l2 = luminance(rgb1), luminance(rgb2)
        if l1 < l2:
            l1, l2 = l2, l1
        return (l1 + 0.05) / (l2 + 0.05)

    def analyze_ui(self, params):
        try:
            url = f"https://api.figma.com/v1/files/{self.file_id}"
            response = requests.get(url, headers=self.headers)
            response.raise_for_status()
            data = response.json()
            nodes = data.get("document", {}).get("children", [])

            issues = []
            colors = []
            font_sizes = []

            for node in nodes:
                if "fills" in node and node["fills"]:
                    color = node["fills"][0].get("color", {})
                    if color:
                        hex_color = webcolors.rgb_to_hex((
                            int(color["r"] * 255),
                            int(color["g"] * 255),
                            int(color["b"] * 255)
                        ))
                        colors.append(hex_color)
                if "style" in node and "fontSize" in node["style"]:
                    font_sizes.append(node["style"]["fontSize"])

                # コントラストチェック（例：背景とテキスト）
                if "fills" in node and "characters" in node:
                    bg_color = node["fills"][0].get("color", {})
                    text_color = node.get("style", {}).get("fill", {}).get("color", {})
                    if bg_color and text_color:
                        bg_hex = webcolors.rgb_to_hex((
                            int(bg_color["r"] * 255),
                            int(bg_color["g"] * 255),
                            int(bg_color["b"] * 255)
                        ))
                        text_hex = webcolors.rgb_to_hex((
                            int(text_color["r"] * 255),
                            int(text_color["g"] * 255),
                            int(text_color["b"] * 255)
                        ))
                        contrast = self.calculate_contrast(bg_hex, text_hex)
                        if contrast < 4.5:  # WCAG AA基準
                            issues.append({
                                "node_id": node["id"],
                                "issue": f"コントラスト比が低すぎます（{contrast:.2f}）。WCAG基準の4.5以上が必要です。"
                            })

            # フォントサイズの一貫性チェック
            if font_sizes:
                avg_font_size = sum(font_sizes) / len(font_sizes)
                for size in font_sizes:
                    if abs(size - avg_font_size) > 2:
                        issues.append({
                            "node_id": "",
                            "issue": f"フォントサイズ（{size}px）が平均（{avg_font_size:.1f}px）から大きく乖離しています。"
                        })

            return {"status": "success", "issues": issues}
        except Exception as e:
            return {"status": "error", "message": str(e)}

    def add_comment(self, params):
        try:
            message = params.get("message", "")
            node_id = params.get("node_id", "")
            if not message:
                return {"status": "error", "message": "コメントが必要です"}
            
            url = f"https://api.figma.com/v1/files/{self.file_id}/comments"
            payload = {
                "message": message,
                "client_meta": {"node_id": node_id} if node_id else None
            }
            response = requests.post(url, headers=self.headers, json=payload)
            response.raise_for_status()
            comment = response.json()
            return {"status": "success", "comment_id": comment["id"]}
        except Exception as e:
            return {"status": "error", "message": str(e)}

if __name__ == "__main__":
    load_dotenv()
    server = FigmaAnalysisServer(
        host="localhost",
        port=8107,
        figma_token=os.getenv("FIGMA_TOKEN"),
        file_id=os.getenv("FIGMA_FILE_ID")
    )
    print("Figma分析MCPサーバーを起動中: http://localhost:8107")
    server.start()
```

### コードの説明
- **analyze_ui**：Figmaファイルのノードを解析し、色やフォントサイズを抽出。コントラスト比（WCAG基準）とフォントサイズの一貫性を評価。
- **calculate_contrast**：簡易的なコントラスト比を計算し、WCAG AA基準（4.5以上）をチェック。
- **add_comment**：第2章から再利用し、分析結果をコメントとして投稿可能。
- **register_resource/tool**：UI分析をリソース、コメント追加をツールとして登録。

### 前提条件
- Figmaファイルに色やフォントを含むノードが存在。
- `.env`ファイルに正しい`FIGMA_TOKEN`と`FIGMA_FILE_ID`が設定済み。
- APIトークンにファイルデータとコメントの読み書き権限がある。

## サーバーのテスト

サーバーが正しく動作するか確認します：

1. **サーバー起動**：
   ```bash
   python figma_analysis_server.py
   ```
   コンソールに「Figma分析MCPサーバーを起動中: http://localhost:8107」と表示。

2. **UI分析のテスト**：
   Pythonでリクエストを送信：
   ```python
   import requests
   import json

   url = "http://localhost:8107"
   payload = {
       "jsonrpc": "2.0",
       "method": "analyze_ui",
       "params": {},
       "id": 1
   }
   response = requests.post(url, json=payload)
   print(json.dumps(response.json(), indent=2, ensure_ascii=False))
   ```
   期待されるレスポンス：
   ```json
   {
     "jsonrpc": "2.0",
     "result": {
       "status": "success",
       "issues": [
         {
           "node_id": "1:2",
           "issue": "コントラスト比が低すぎます（3.50）。WCAG基準の4.5以上が必要です。"
         },
         {
           "node_id": "",
           "issue": "フォントサイズ（14px）が平均（15px）から大きく乖離しています。"
         }
       ]
     },
     "id": 1
   }
   ```

3. **コメント追加のテスト**：
   ```python
   payload = {
       "jsonrpc": "2.0",
       "method": "add_comment",
       "params": {
           "message": "コントラスト比を改善してください",
           "node_id": "1:2"
       },
       "id": 2
   }
   response = requests.post(url, json=payload)
   print(json.dumps(response.json(), indent=2, ensure_ascii=False))
   ```

## Claude Desktopとの接続

サーバーをClaude Desktopに接続します：

1. **設定ファイルの編集**：
   Claude Desktopの設定ファイル（例：`claude_desktop_config.json`）に以下を追加：
   ```json
   {
     "mcp_servers": [
       {
         "name": "FigmaAnalysisServer",
         "url": "http://localhost:8107",
         "auth": "none"
       }
     ]
   }
   ```

2. **Claudeでテスト**：
   Claude Desktopを起動し、プロンプトを入力：
   ```
   デザインのUI問題を教えてください。
   ```
   レスポンス例：
   ```
   デザインのUI問題：
   - ボタン（ノード1:2）：コントラスト比が3.50で、WCAG基準の4.5未満です。
   - フォントサイズ：14pxが平均15pxから乖離しています。
   ```

   別のプロンプト：
   ```
   ボタンのコントラスト問題をコメントしてください。
   ```
   レスポンス例：
   ```
   ボタンに「コントラスト比を改善してください」をコメントしました。
   ```

## 実装のコツと注意点

- **パフォーマンス**：大量のノードを処理する場合、Figma APIのリクエストを最適化（例：特定ノードのみ取得）。
- **エラーハンドリング**：ノードに色やフォント情報がない場合に対応。
- **レートリミティング**：Figma APIの制限（通常30リクエスト/分）に注意。
- **セキュリティ**：本番環境では、HTTPSとトークン認証を導入。
- **拡張性**：複雑な分析の場合、デザインシステムのルールを事前定義。

## 試してみよう：挑戦課題
以下の機能を追加して、エージェントを強化してみてください：
- 色の多様性を分析し、ブランドカラーからの逸脱を検出。
- レイアウトの整列（例：マージンやパディング）をチェック。
- 分析結果を新しいFigmaファイルにエクスポートするツール。

## まとめと次のステップ

この第3章では、Figmaのデザインデータを活用してUI分析エージェントを構築しました。色のコントラストやフォントサイズの分析により、AIがUIの品質を評価し、改善提案を生成できるようになりました。

次の第4章では、Figmaのバージョン管理を強化する**バージョン管理AI**を構築します。たとえば、AIがデザインの変更履歴を追跡し、変更点をコメントとして記録します。バージョン管理AIに興味がある方は、ぜひお楽しみに！

---

役に立ったと思ったら、「いいね」や「ストック」をしていただけると嬉しいです！次の章でまたお会いしましょう！