0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

この記事は前回、および前々回の記事を読んでいることを前提に作られています。
まだ読んでいない方は下のリンクからどうぞ。

まえがき

皆さんどうもこんにちは、ECN技術部所属、Fuseです。
本記事でもまた引き続き、Shin-sibainu氏によって開発されたClaude Code で仮想組織を構築・運営するプラグインcc-companyを拡張していきます。
Claude Codeの弱点として、単体では画像生成ができないというものがあります。
そんな弱点を補うべく、今回は自作MCP1サーバーを使ってOpenAI(gpt-image-2)と連携する「デザイン部署」を作り、秘書に話しかけるだけで画像生成・i2i2編集ができる環境を整えます。
デザイン部署イメージ図
(イメージ図はChatGPTに生成してもらいました)

動作環境

  • Claude Code: 2.1.144
  • cc-company: 2.1.0
  • python 3.12.6

gpt-image-2って?

gpt-image-2は2026年4月に登場した最新の画像生成モデルです。
旧モデルと比較するとコスパも性能も強化されており、かなりおすすめのモデルです。

モデル名 性能・特徴 テキスト入力コスト (1Mあたり) 画像出力コスト (1Mあたり)
GPT Image 2 最先端モデル。レイアウトの信頼性が高く、画像内のテキスト描画や構造化された実用的な生成に優れる。 $5.00 (キャッシュ時 $1.25) $30.00
GPT Image 1.5 優れたクリエイティブ性能を持つが、複雑なプロンプトではやや不安定になることがある。 $5.00 (キャッシュ時 $1.25) $32.00
GPT Image 1 初期の特化型モデル。高品質モードでは高いトークンを消費し、現行モデルより生成コストが高め。 $5.00 (キャッシュ時 $1.25) $40.00
出典:[Pricing OpenAI API](https://developers.openai.com/api/docs/pricing)

ディレクトリを作成する

mkdir -p .company/design/projects

でディレクトリを作成し、

touch .company/design/CLAUDE.md

でCLAUDE.mdを作成します。

.company/
└── design/
    ├── CLAUDE.md
    └── projects/
        └── [プロジェクト名]/
            ├── [プロジェクト名].md   ← 概要・ステータス管理(秘書が作成)
            ├── images/               ← 生成した画像
            └── prompts/              ← 使用したプロンプトの記録

.company/design/CLAUDE.md を作成します。

# デザイン部署

## 役割
仮デザイン用の画像生成と、生成した画像・プロンプトの管理を担当する。
MCPやAPIを通じて画像生成サービスと連携し、コンセプトアートやサムネイル案を素早く量産する。

## ルール
- プロジェクトごとに `projects/[プロジェクト名]/` フォルダを作成する
- プロジェクト情報・ステータスは `projects/[プロジェクト名]/[プロジェクト名].md` で管理する(秘書が作成)
- 生成画像は `projects/[プロジェクト名]/images/YYYY-MM-DD-[連番].png` で保存(MCPサーバーが自動保存)
- 使用したプロンプトは `projects/[プロジェクト名]/prompts/YYYY-MM-DD.md` に記録する(MCPサーバーが自動記録)
- 仮デザインが採用されたらクリエイティブ部署に引き渡す

## 役割分担
- **MCPサーバー(image-gen)**: 画像生成・保存、プロンプト記録のみ担当
- **秘書**: プロジェクト新規作成時に `[プロジェクト名].md` を作成し、概要・ステータスを管理する

## プロジェクトmdの形式
```markdown
# [プロジェクト名]

## 概要
(目的・用途の説明)

## ステータス
draft / in-progress / delivered

## 生成画像一覧
- YYYY-MM-DD-01.png - (説明)

## 採用画像
- (採用された画像とその理由)
```

## フォルダ構成
- `projects/[プロジェクト名]/[プロジェクト名].md` - プロジェクト情報・ステータス管理
- `projects/[プロジェクト名]/images/` - 生成した画像ファイル
- `projects/[プロジェクト名]/prompts/` - 使用したプロンプトの記録

MCPサーバーを作成する

プロジェクトルートに mcp-servers/image-gen/ フォルダを作り、以下の2ファイルを作成します。

requirements.txt

requirements.txt
mcp
openai

server.py

server.py
"""
image-gen MCP Server
gpt-image-2 で画像生成・編集(i2i)を行い、.company/design/projects/ に保存する

【ツール一覧】
- generate_image : テキストプロンプトから新規画像を生成
- edit_image     : 既存画像をベースにi2i編集(マスク指定も可)

【前提】
- 環境変数 OPENAI_API_KEY にOpenAIのAPIキーを設定すること
- .mcp.json の env セクションで渡すのが簡単(.gitignore 推奨)
"""

import asyncio
import base64
from datetime import date
from pathlib import Path

from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import TextContent, Tool
from openai import OpenAI

# server.py → image-gen/ → mcp-servers/ → プロジェクトルート
DESIGN_BASE = Path(__file__).resolve().parents[2] / ".company" / "design" / "projects"

# MCPサーバーのインスタンスを作成
server = Server("image-gen")

# OpenAIクライアント(OPENAI_API_KEY 環境変数から自動取得)
client = OpenAI()


def _next_image_path(project: str) -> tuple[Path, Path, str, str]:
    """プロジェクトフォルダを準備し、次に保存すべきパス情報を返す"""
    project_dir = DESIGN_BASE / project
    images_dir = project_dir / "images"
    prompts_dir = project_dir / "prompts"
    images_dir.mkdir(parents=True, exist_ok=True)
    prompts_dir.mkdir(parents=True, exist_ok=True)
    today = date.today().strftime("%Y-%m-%d")
    # 同日のファイル数をカウントして連番を決める
    index = len(sorted(images_dir.glob(f"{today}-*.png"))) + 1
    filename = f"{today}-{index:02d}.png"
    return images_dir / filename, prompts_dir / f"{today}.md", today, filename


def _save_prompt(prompts_file: Path, project: str, today: str, filename: str, prompt: str) -> None:
    write_header = not prompts_file.exists()  # open前に確認
    entry = f"\n## {filename}\n\n```\n{prompt}\n```\n"
    with open(prompts_file, "a", encoding="utf-8") as f:
        if write_header:
            f.write(f"# {project} プロンプト記録 {today}\n")
        f.write(entry)


@server.list_tools()
async def list_tools() -> list[Tool]:
    """Claude に公開するツールの一覧を返す"""
    return [
        Tool(
            name="generate_image",
            description="gpt-image-2でテキストから画像を生成し、デザイン部署のプロジェクトフォルダに保存する",
            inputSchema={
                "type": "object",
                "properties": {
                    "prompt": {"type": "string", "description": "画像生成プロンプト(英語推奨、英語の方が精度が高い傾向があります)"},
                    "project": {"type": "string", "description": "プロジェクト名(例: my-project)"},
                    "size": {
                        "type": "string",
                        "enum": ["1024x1024", "1536x1024", "1024x1536"],
                        "default": "1024x1024",
                        "description": "画像サイズ",
                    },
                },
                "required": ["prompt", "project"],
            },
        ),
        Tool(
            name="edit_image",
            description="既存画像をベースにgpt-image-2でi2i編集し、デザイン部署のプロジェクトフォルダに保存する",
            inputSchema={
                "type": "object",
                "properties": {
                    "image_path": {"type": "string", "description": "編集元画像のパス"},
                    "prompt": {"type": "string", "description": "編集内容のプロンプト(英語推奨)"},
                    "project": {"type": "string", "description": "プロジェクト名(保存先)"},
                    "mask_path": {
                        "type": "string",
                        "description": "マスク画像のパス(省略可)。透明部分が編集対象になる",
                    },
                    "size": {
                        "type": "string",
                        "enum": ["1024x1024", "1536x1024", "1024x1536"],
                        "default": "1024x1024",
                        "description": "出力サイズ",
                    },
                },
                "required": ["image_path", "prompt", "project"],
            },
        ),
    ]


@server.call_tool()
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
    """ツール呼び出しのエントリーポイント"""

    # ── テキスト→画像生成 ──────────────────────────────────────
    if name == "generate_image":
        prompt = arguments["prompt"]
        project = arguments["project"]
        size = arguments.get("size", "1024x1024")

        image_path, prompts_file, today, filename = _next_image_path(project)

        # gpt-image-2はresponse_formatをサポートしておらず、デフォルトでb64_jsonで返される
        response = client.images.generate(
            model="gpt-image-2",
            prompt=prompt,
            size=size,
            n=1,
        )
        if not (response.data and response.data[0].b64_json):
            return [TextContent(type="text", text="画像を生成できませんでした")]

        image_path.write_bytes(base64.b64decode(response.data[0].b64_json))
        _save_prompt(prompts_file, project, today, filename, prompt)

        return [TextContent(type="text", text=f"生成完了。\n保存先: {image_path}\nサイズ: {size}")]

    # ── 画像→画像編集(i2i) ───────────────────────────────────
    elif name == "edit_image":
        src = arguments["image_path"]
        prompt = arguments["prompt"]
        project = arguments["project"]
        mask_path = arguments.get("mask_path")
        size = arguments.get("size", "1024x1024")

        src_path = Path(src) if Path(src).is_absolute() else DESIGN_BASE / project / "images" / src
        if not src_path.exists():
            return [TextContent(type="text", text=f"画像が見つかりません: {src_path}")]

        image_path, prompts_file, today, filename = _next_image_path(project)

        with open(src_path, "rb") as img_f:
            if mask_path:
                # マスクあり: 透明部分だけを編集対象にして部分編集
                with open(mask_path, "rb") as mask_f:
                    response = client.images.edit(
                        model="gpt-image-2",
                        image=img_f,
                        mask=mask_f,
                        prompt=prompt,
                        size=size,
                        n=1,
                    )
            else:
                # マスクなし: 画像全体をプロンプトに従って再構成
                response = client.images.edit(
                    model="gpt-image-2",
                    image=img_f,
                    prompt=prompt,
                    size=size,
                    n=1,
                )

        if not (response.data and response.data[0].b64_json):
            return [TextContent(type="text", text="画像を編集できませんでした")]

        image_path.write_bytes(base64.b64decode(response.data[0].b64_json))
        # プロンプト記録に元画像名も残す(どの画像を元にしたか追跡できる)
        _save_prompt(prompts_file, project, today, filename, f"[i2i] base={src_path.name}\n{prompt}")

        return [TextContent(type="text", text=f"編集完了。\n保存先: {image_path}\nサイズ: {size}")]

    raise ValueError(f"Unknown tool: {name}")


async def main():
    # stdio 経由でMCPプロトコルを処理するメインループ
    async with stdio_server() as streams:
        await server.run(streams[0], streams[1], server.create_initialization_options())


if __name__ == "__main__":
    asyncio.run(main())

パッケージをインストールする

この記事では省略しますが pip install 前には仮想環境を作って適用することを推奨します

pip install -r mcp-servers/image-gen/requirements.txt

.mcp.json に登録する

プロジェクトルートに .mcp.json を作成します。

APIキーが含まれるため必ず.mcp.json を .gitignore に追加してください。

{
  "mcpServers": {
    "image-gen": {
      "command": "py",
      "args": ["mcp-servers/image-gen/server.py"],
      "env": {
        "OPENAI_API_KEY": "your-api-key-here"
      }
    }
  }
}

※ Windowsの場合は py、macOS/Linuxの場合は python3 に変更してください。

部署を追加したときは、忘れずにClaude Codeを再起動してください。

実際に使ってみる

以下は著者が実際に行った会話です。
会話の内容は人によって変わります。

私:my-projectというプロジェクトで、眠たげな猫の画像を生成して

秘書(デザイン部):
生成完了。
保存先: .company\design\projects\my-project\images\2026-06-25-02.png
サイズ: 1024x1024

2026-06-25-02.png

生成された画像は .company/design/projects/ 以下に自動保存され、使用したプロンプトも prompts/ に記録されます。

MCPサーバーは画像生成・保存のみ担当します。プロジェクトの概要やステータスを管理する [プロジェクト名].md は、秘書が初回作成します。

私:my-projectというプロジェクトを作って。動作確認用。

秘書(デザイン部):
プロジェクト「my-project」を作成しました。

.company/design/projects/my-project/my-project.md

i2i(画像→画像編集)も使えます。

私:2026-06-25-02.png をベースに猫を黒猫にして

秘書(デザイン部):
編集完了。
保存先: .company/design/projects/my-project/images/2026-06-25-03.png

2026-06-25-03.png


あとがき

全3回を通じて、3種類の部署拡張パターンを紹介しました。

パターン 例
外部サービス不要・完全内部完結 モチベーション部署
公式MCPで外部サービスと連携 ナレッジ部署(Notion)
自作MCPでAPIと連携 デザイン部署(gpt-image-2)

このシリーズを最後まで読んだあなたなら、いろんな使い方が思いつくはずです。
皆さんはどんな部署を作りましたか?よければ、コメント欄に書いてみてくださいね。


株式会社ECN
株式会社ECNはPHP、JavaScriptを中心にお客様のご要望に合わせたwebサービス、システム開発を承っております。 ビジネスの最初から最後までサポートを行い、お客様のイメージに合わせたWebサービス、システム開発、デザインを行います。

  1. MCP: Model Context Protocol、外部サービスとAIを接続する仕組み ↩

  2. i2i: image to image、元の画像や写真(ベース画像)をAIに読み込ませることで、新しい画像を作成する技術や機能の総称 ↩

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?