Lchiki-Lab
arrow_backBack to Archive

Google ColabとMCPで画像生成サーバーを自作!手元のAIから透過素材を量産する方法

calendar_today2026.09.23folderTechnology
Cookie Cat

やっほー!今回はGoogle Colab ProとFastMCPを組み合わせて、手元のAIエディタから直接「透過画像」を生成する自前サーバーの作り方を紹介するよ!

LINEスタンプやWeb素材のような制作を例に、背景の自動切り抜きやリポジトリ連携の仕組みを解説するから、AIエージェントに専用の画像生成ツールを持たせたい人はぜひ参考にしてみてね!

普段からClaude CodeやAntigravityといったAIエージェント環境で作業していると、テキストの生成だけでなく、背景透過済みのイラスト画像やWeb用アセットをリポジトリ内に直接出力したい場面が増えてきました。

ただ、LINEスタンプやアイコンなどの素材制作においては、単なる画像生成の手軽さだけでなく、透過処理の精度やプロジェクト管理(Git)との連携でつまずきがちです。

そこでGoogle Colabをバックエンドに据え、手元のAIエージェントから指示するだけで、背景がきれいに抜けたPNG画像がGitリポジトリ内の指定フォルダへ直接保存されるMCPパイプラインを構築しました。

この記事では、なぜこの構成に至ったのか、どんなトレードオフを検討したのかという設計思想を中心に、Colab上でそのまま動く実装コードとともに残しておきます。

手元のAIエージェントによるMCPツール認識と画像生成の実行画面

▲ 手元のAIエージェントからFastMCPツールが認識され、指示通りに透過スタンプの生成が実行されている様子


1. 開発の​背景と​課題

Google AI Proプランで​Colab Pro枠が​付帯したことに​よる​転換点

すべてのきっかけは、Google Developers BlogおよびGoogle Colab公式による発表で、Googleの有料AIプラン(Google AI Pro)にColab Pro相当の特典が含まれるようになったと知ったことでした。

これまでのクラウド上でのGPU実験は、無料枠のGoogle Colab(T4 GPU / VRAM 16GB)を中心に行っていましたが、メモリ制限が厳しく、長時間の処理やセッション維持に制約がありました。

しかしGoogle AI Proプランの付帯特典により、毎月200コンピューティングユニット(CU)が付与され、追加費用なしでL4 GPU(VRAM 24GB)やA100といった強力なGPU環境が利用可能になりました。L4環境であれば毎月かなりの時間(80〜100時間程度)を稼働させられます。

普段契約しているAIプランの枠内で、強力なGPUリソースが毎月手元にある状態です。常時起動のサーバーを契約するのではなく、必要なタイミングでオンデマンドに手元のエージェントからGPUパワーを呼び出す構成が一気に現実的になりました。

Google AI Proプラン付帯のColabスペック
  • 利用可能GPU L4 GPU(VRAM 24GB)やA100などハイメモリ環境を選択可能
  • 付与リソース 月200コンピューティングユニット(CU)の付与(L4環境なら月80〜100時間程度の稼働目安)
  • 運用の特徴 サーバー常時稼働ではなく、必要な作業時だけ立ち上げるオンデマンド運用

なぜWebチャットでの​単発生成ではなく​リポジトリ連携の​MCPなのか?

開発ワークフローの対比

単に透過イラストを1〜2枚作りたいだけであれば、ChatGPTなどのWebチャットで画像生成を依頼し、ダウンロードするだけで事足ります。近年のChatGPTであれば背景透過(透明PNG)の直接出力にも対応しています。

しかし私自身の開発スタイルでは、LINEスタンプの制作に限らず、Webサービスやブログ用の素材、各種スクリプト類をすべてGitリポジトリ上でコードとともに素材を一元管理しています。

制作のフロー上、画像を出力したら終わりではありません。リポジトリ内のアセットディレクトリ(assets/images/やassets/stamps/など)に直接配置し、メタデータ生成やビルドスクリプト、Gitコミットへと途切れずにつなげる必要があります。

Webブラウザを開いてプロンプトを打ち、ダウンロードフォルダから画像を拾い、ファイル名を変更して該当ディレクトリへ移動するといった手作業の反復は、開発全体のテンポを大きく削いでしまいます。

手元のAI開発環境(Claude CodeやAntigravity)から直接ツールを呼び出せれば、エージェントがプロンプトの調整から画像生成、リポジトリ内への直接保存まで自律的に実行してくれます。ビルドやコミットまで含めてリポジトリ中心の開発フローへ統合することこそが、MCP化を目指した最大の狙いでした。

画像生成に​おける​背景透過の​課題と​Geminiの​制約

もうひとつの大きな課題が背景透過の処理です。

日常的に開発環境やAPIで利用しているGeminiは、現時点では背景透過(Alphaチャンネル付きPNG)の直接出力に対応しておらず、必ず不透明な背景色を含む画像が出力されます。

そこでGeminiに対してプロンプトで背景を白やグリーンバックにするよう指示しても、キャラクターの輪郭に影や反射光(環境光の照り返し)が入り込み、きれいな単色にはなりません。これをペイントツールの塗りつぶしや単純なカラーキー(特定色の引き算)で抜こうとすると、キャラクターの主線に白や緑のフリンジ(色残り)がこびりついてしまいます。

また、手元の開発エージェントの内蔵画像生成ツールをそのまま使う場合も、APIの利用上限(クォータ制限)があるため、ポーズや表情を変えて何十枚も連続で試作したい場面では制約を受けます。

「Google AI Proで付与されたColabのGPU枠を使い、Geminiでは難しい高精度の背景透過PNGを、上限を気にせずリポジトリへ直接出力する」——これが、今回のパイプラインを組んだ一番の理由です。

既存の​固定トンネル基盤の​流用

ネットワーク構成については、ゼロから設計したわけではありません。以前構築した以下の記事の仕組みをそのまま活かしています。

この環境では、Google Colab上のサーバーをCloudflare Named Tunnel経由で固定サブドメイン(例: https://[your-tunnel-domain])の指定ポート([your-port] 例: 11434 や 8000 など任意の待機ポート)に紐付けています。Colabのインスタンスが再起動してもURLは変わりません。

このトンネリング基盤があったため、Cloudflare側のルーティング設定を変更することなく、同じポートでFastMCPを起動するだけで固定のMCPエンドポイントを用意できました。

手元エージェント×固定トンネル×Colab GPUの3層アーキテクチャ

2. 検討した​3つの​アプローチ

今回の仕組みを実現するにあたり、3つの構成を比較・検討しました。

案アーキテクチャメリット見送った理由 / 課題
案1ComfyUI + LayerDiffusion(Colab上で直接RGBA透過生成)境界面が自然で、最初から透過付きで出力できる。ノードの配線やパラメータ設定の手数が多く、自然言語で手軽に素材を作りたい対話型の用途には重すぎる。
案2Gradioによる専用WebUI立ち上げブラウザからプロンプトを入力して透過PNGを確認・保存できる。毎回ブラウザを開いて画像を保存する手間が残り、手元の開発環境(Claude CodeやCLI)の作業と分断される。
案3 (採用)Colab FastMCPサーバー + 手元IDE連携画面操作が一切不要。チャット上で指示するだけで、ローカルの指定フォルダに透過PNGが直接保存される。Colabの動的IP問題や、Python 3.13環境でのライブラリ競合を解決する必要がある。

最終的に案3(FastMCPサーバー化)を採用しました。

手元の開発環境(Claude CodeやAntigravity)から、チャット上のエージェントにプロンプト英訳やAPI実行、Base64デコード、ローカル保存までの一連の処理を任せられる点が最大の決め手です。

FastMCP構成を採用した決め手
  • 画面遷移のない作業環境 WebUIを開かず、チャット画面への指示だけで画像生成からローカル保存まで完結
  • AIエージェントとの高い親和性 プロンプト英訳、生成API呼び出し、Base64デコード、ファイル書き出しを一任可能
  • GPUリソースの効率利用 普段は軽量な手元環境を使い、生成処理のみColabのGPUへ逃がす役割分担

3. システム構成と​技術選定

システムは、手元のAIクライアントからCloudflare Tunnelを経由して、Google Colab上のFastMCPサーバーを呼び出す構成です。

システム連携・データフロー構成図
[ 手元のAI環境 ] (Claude Code / Antigravity)
      │
      │  プロンプト送信:画像生成指示と保存先を指定
      │  Streamable HTTP (/mcp) / JSON-RPC
      ▼
[ Cloudflare Named Tunnel ] (https://[your-tunnel-domain]/mcp)
      │  指定ポートに転送([your-port] 例: 11434・外部非公開)
      ▼
[ Google Colab (L4 GPU / VRAM 24GB) ]
  ├── FastMCP Server (指定ポート / /mcp待機)
  │     ├── Tool: generate_transparent_stamp
  │     └── Tool: server_status
  │
  ├── 1. SDXL-turbo (約1.5秒)
  │     └── 512x512, 4 steps, CFG 0.0 による高速イラスト生成
  │
  ├── 2. BiRefNet-general (約1.0秒)
  │     └── 高解像度バイナリ境界抽出によるエッジ透過
  │
  └── 3. PNG Buffer (Base64エンコードしてJSON返却)
エンドポイントの非公開とセキュリティ

今回構築したMCPエンドポイントは、手元の作業環境からのみ利用する前提で運用しています。インターネット上に認証なしでエンドポイントをそのまま公開すると、第三者から自由に画像生成リクエストを送り込まれ、Colabのコンピューティングユニット(GPUリソース)が消費されてしまうリスクがあります。そのためURLは外部に公開せず、Cloudflare Zero Trustのアクセス制御やトークン認証を併用して運用します。

各コンポーネントの​選定理由

SDXL-turbo:約1.5秒で​出力できる​高速推論

チャットでの対話中に画像生成で待たされる時間が長くなると、作業のリズムが途切れてしまいます。SDXL-turboはわずか4ステップで実用的なイラストを出力できるため、後段の透過処理を含めてもトータル3〜4秒で手元に画像が届きます。

SDXL-turboの推論パラメータ設計
  • 解像度 512×​512px(スタンプやWeb素材として十分かつ生成速度が最速)
  • ステップ数 4 steps(蒸留モデルのため少ないステップでも崩れにくい)
  • ガイダンススケール guidance_scale=0.0(高速化とモデル特性に合わせた最適値)

BiRefNet-general:黒い​主線も​きれいに​残すエッジ抽出

背景の切り抜きには、高精度なセグメンテーションモデルであるBiRefNetを採用しました。従来のrembg(u2netなど)に比べて輪郭の判定精度が高く、イラスト特有の黒い主線やお腹の境界もきれいに維持できます。ダークモードのUIやチャット画面に配置しても、不自然なフチが目立ちません。

Cloudflare Named Tunnel:再起動しても​変わらない​接続先

Colabは再起動のたびにグローバルIPが変わりますが、Cloudflare Tunnel経由で固定サブドメイン(例: https://[your-tunnel-domain])の指定ポート([your-port] 例: 11434)へ直結できます。過去に整備したトンネル設定を流用したため、新たなネットワーク構築の手間をかけずに手元の開発環境から常時接続できるエンドポイントを確保できました。

Cookie Cat

単に色を指定して透明にするだけだと、キャラクターの服まで透けたり輪郭に緑が残ったりしちゃうんだよね。
BiRefNetを使うと、AIが被写体の形をしっかり認識して切り抜いてくれるから、イラストやスタンプの主線がとても綺麗に残るよ!

単純な​色差透過​(カラーキー)との​違い

背景をグリーンバックにして抜くのであれば、Python(PIL/OpenCV)などのスクリプトで特定色を透明化する処理と何が違うのか、という疑問も浮かびます。

決定的な違いは、ピクセルのRGB値だけで機械的に判定しているか、それとも画像全体の構造から物体としての輪郭を認識しているかという点にあります。

比較項目単純なカラーキー(RGB色差判定)本構成のAI境界分離(BiRefNet)
判定原理ピクセルのRGB値のみで判定
(例: 緑色の閾値を超えたら透明化)
画像全体の構造から物体を認識
(キャラクターと背景の境界を判別)
輪郭の仕上がり境界に緑のフリンジが残るか、削れすぎてギザギザになりやすい輪郭の主線や中間色を保ちながら滑らかに透過
キャラクター内部の同色キャラクター内に緑(服や瞳など)があると、その部分まで透明化される緑の服や小物を身につけていても被写体として維持
調整の手間画像ごとに閾値(Threshold)の手動調整が必要パラメータ調整なしで一貫して高品質処理

グリーンバックを​指定する​理由

プロンプトにあえてグリーンバックを指定するのは、RGB値を機械的に引き算するためではありません。被写体と背景の明度差・色差を際立たせ、BiRefNetが境界をより正確に判別できるようにするための補助として指定しています。

一般的な色差処理では、キャラクターの主線と背景色が交わる中間ピクセル(アンチエイリアス部分)から背景色だけを取り除くことが難しく、輪郭に薄い色が残りがちです。BiRefNetは被写体の輪郭線の連続性を捉えて処理するため、線の質感を保った自然な透過マスクを生成できます。

外部​APIや​ブラウザ完結型​AIではなく​自前GPUモデルを​動かす理由

透過技術の選定とトレードオフ

背景を透過するアプローチとしては、他にも外部SaaSのAPI(remove.bgなど)や、WebAssembly(Wasm)を活用したブラウザ完結型のAI背景削除ツールが存在します。これらと今回のColab上でBiRefNetを稼働させる構成には、明確な設計思想の違いとトレードオフがあります。

ブラウザ完結型​AI​(Wasm / ONNX Web)との​住み分け

近年は、ブラウザ側(クライアントの端末内)でAI推論し、画像を一切外部送信せずに背景を透過できる優れたWebツールも普及しています。これらはサーバー代がかからず、プライバシーの面でも非常に優れた仕組みです。

しかし、一般ユーザーのブラウザ上で安定して動作させるためには、次のような技術的な制約も伴います。

  • モデルサイズの制限 初回アクセス時のダウンロード待ちを避けるため、数十MB程度に圧縮・量子化された軽量モデルを採用せざるを得ない。
  • 端末負荷とメモリ制約 スマートフォンや一般的なPCのCPU/メモリで動かすため、推論に時間がかかったり、高解像度画像の処理でメモリ不足になりやすい。
  • イラスト特有の主線保持 軽量化モデルは実写の人物切り抜きには十分な性能を発揮するものの、イラスト特有の黒い輪郭線やキャラクターの内側にある同系色の判定が甘くなり、境界が削れてしまうケースがある。

Colabの​GPUで​BiRefNetを​動かす強み

今回の構成では、Colab Proの強力なGPU(L4 / VRAM 24GB等)を利用できるため、モデルサイズが1GB近くある重量級のセグメンテーションモデルBiRefNetを無劣化のフルスペックで稼働させています。

高解像度のバイラテラル参照(双方向の特徴マップ比較)により、キャラクターの細かな毛先や手足、黒い主線を削ることなく、1秒前後できれいに背景のみを分離できます。

比較項目ブラウザ完結型AI(Wasm)外部API(SaaS)自前Colab × BiRefNet
主な実行場所クライアント端末(ブラウザ内)サービス事業者のクラウド自前のGPUインスタンス(Colab)
コスト0円従量課金(枚数に応じて課金)Colab Pro利用料の範囲内(追加費ゼロ)
モデル規模軽量・圧縮版(〜数十MB)非公開(クラウド側で処理)重量級・フルスペック(〜1GB超)
イラスト主線の精度細部が削れる場合がある実写寄り(イラスト線は甘いことも)輪郭の主線や手足を極めて精密に保持
処理速度端末スペック依存(数秒〜十数秒)ネットワーク往復の待機が発生GPU直結で約1秒(高速バッチ可能)
最適なユースケース手軽な単発編集・プライベート処理実写写真の単発切り抜き大量試作・イラスト専用の高品質パイプライン

普段使いの単発編集やプライバシーを最優先する場面では、ブラウザ完結型が理想的です。一方、イラストやスタンプの大量生成において主線を保護し、生成から数秒で手元に届ける目的においては、自前GPU環境で重量級モデルを動かす構成が適しています。


4. 実装時に​発生した​エラーと​対処

Cookie Cat

Colabの最新環境はPython 3.13だから、古い記事のコードをそのまま動かそうとするとエラーになりやすいんだよね。
特にFastMCPの起動引数やNumPyの互換性には気をつけて設定しようね!

Colabの環境(Python 3.13 / CUDA 13.0)でパイプラインを動かすにあたり、ライブラリの依存関係でいくつか引っかかった点がありました。

Python 3.13 と​古い​NumPyの​C拡張エラー

古い情報を参考に numpy==1.26.4 を入れたところ、numpy._core.umath の読み込み時に cannot import name '_slice' が発生。DiffusersやPyTorchが動作しなくなりました。

Python 3.13環境ではNumPyを無理にダウングレードせず、numpy>=2.0,<3.0 を前提に環境を整えます。PyTorch 2.xと最新版Diffusersの構成に揃えることで解消しました。

ONNX Runtime (rembg[gpu])の​ビルド競合

Python 3.13とCUDA 13.0の組み合わせでは、rembg[gpu] が依存するONNX Runtime GPU関連の共有ライブラリで不整合が起きました。

今回はONNX Runtimeを経由せず、PyTorch上で直接動作するモデル定義(birefnet-general)を利用しました。これによりライブラリ競合を回避し、GPUメモリの管理もシンプルに収まりました。

SSEから​最新の​Streamable HTTPへの​移行

MCP公式SDKの現行仕様に合わせて、トランスポート方式を従来のSSE(Server-Sent Events)からStreamable HTTP(エンドポイント /mcp)へ正式採用しました。公式ドキュメントでも外部デプロイ用途はStreamable HTTPが標準とされ、SSEは旧方式という位置付けになっています。

起動時には以下のように FastMCPの引数でホストとポートを指定し、mcp_server.run(transport="streamable-http") を呼び出します(※MCP 1.30.0以降におけるTypeError対策)。

mcp_server = FastMCP(
    "LINE Sticker Generator",
    host=HOST,
    port=PORT,
)

mcp_server.run(
    transport="streamable-http",
)

今回は画像生成サーバーとしての拡張性を残すため、将来的なセッション管理やストリーミング機能の余地を残せる通常(ステートフル)のStreamable HTTPを採用しました。接続先も https://[your-tunnel-domain]/mcp(※ご自身で設定したトンネルURL)となり、よりモダンで安定した通信が可能です。

Hugging Faceトークン​(HF_TOKEN)の​任意化と​シークレット管理

SDXL Turboなどの公開モデルは匿名アクセスでもダウンロード可能なため、HF_TOKEN は必須ではなく「登録されていれば認証付きで利用し、なければ匿名アクセスで取得する」任意設定としました。

そのため、Google Colab左メニューのシークレット機能(鍵アイコン)には CLOUDFLARE_TOKEN のみ登録しておけばセルを実行するだけで全自動で立ち上がります。トークンをコードに直書きせず、google.colab.userdata.get("CLOUDFLARE_TOKEN") で安全に読み込む構成に統一しました。


5. 完成した​実装コード​(Colab 1セル完結)

以下のコードをGoogle Colabのセルに貼り付けて実行するだけで、モデルのダウンロードからサーバー立ち上げ、Cloudflare Tunnel経由での公開までが全自動で完了します。

Google Colabでの実行画面(Python 3.13 / L4 GPU / FastMCP起動完了ログ)

▲ Google Colabでのセル実行画面。L4 GPU環境でFastMCPサーバーが正常にREADY状態となった様子

code 完成した実装コードを表示する(Colab 1セル完結)

expand_more
# ============================================================
# LINE Sticker MCP Server
# Google Colab / ONE CELL
#
# MCP 1.30.0
# FastMCP
# Streamable HTTP
# Cloudflare Tunnel
#
# HF_TOKEN       : optional
# CLOUDFLARE_TOKEN: required
# ============================================================


# ============================================================
# 0. Imports
# ============================================================

import os
import sys
import subprocess
import time
import importlib.util
import importlib.metadata
import threading
import io
import base64
import socket
import warnings


# ============================================================
# 1. Configuration(★ユーザー設定エリア★)
# ============================================================

# --- 【要編集】Cloudflare Tunnel & 待機設定 -------------------
# Cloudflare Tunnelで設定したご自身の公開URL
PUBLIC_URL = "https://[your-tunnel-domain]"  # [your-tunnel-domain] 例: https://ai-mcp.your-domain.com

# Cloudflare Tunnelの転送先(Service)に指定したポート
PORT = 11434  # [your-port] 例: 11434 や 8000 など(Tunnel側の設定と一致させる)

# --- 【任意設定】MCP・モデル・画像生成パラメータ -------------
HOST = "0.0.0.0"
MCP_PATH = "/mcp"

MODEL_ID = "stabilityai/sdxl-turbo"

IMAGE_WIDTH = 512
IMAGE_HEIGHT = 512
INFERENCE_STEPS = 4
GUIDANCE_SCALE = 0.0

MCP_VERSION_REQUIRED = "1.30.0"

CLOUDFLARED_PATH = "/usr/local/bin/cloudflared"

SERVER_START_TIMEOUT = 30
SERVER_CHECK_INTERVAL = 0.5


# ============================================================
# 2. Utility
# ============================================================

def print_section(title: str):
    print()
    print("=" * 70)
    print(title)
    print("=" * 70)


def get_colab_secret(name: str) -> str:
    """
    Google Colab Secretを取得。
    Colab以外では空文字を返す。
    """
    try:
        from google.colab import userdata

        value = userdata.get(name)

        if value:
            return str(value).strip()

    except Exception:
        pass

    return ""


def get_secret_or_env(name: str) -> str:
    """
    Colab Secret → 環境変数の順で取得。
    """
    value = get_colab_secret(name)

    if not value:
        value = os.environ.get(name, "")

    return str(value).strip()


def package_version(package_name: str) -> str:
    """
    importlib.metadataから安全にバージョン取得。
    """
    try:
        value = importlib.metadata.version(package_name)

        if value:
            return str(value)

    except Exception:
        pass

    return "unknown"


def is_port_open(host: str, port: int) -> bool:
    """
    TCPポートがLISTEN状態か確認。
    """
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    try:
        sock.settimeout(0.5)
        result = sock.connect_ex((host, port))
        return result == 0

    except Exception:
        return False

    finally:
        sock.close()


# ============================================================
# 3. Initial settings / Secrets
# ============================================================

print("=" * 70)
print("LINE Sticker MCP Server")
print("=" * 70)
print("初期設定")
print()


# ------------------------------------------------------------
# HF_TOKEN
# ------------------------------------------------------------
#
# 公開モデルのため必須ではない。
# 設定されていればHugging Face Hubのレート制限緩和等に利用。
#

HF_TOKEN = get_secret_or_env("HF_TOKEN")

if HF_TOKEN:
    os.environ["HF_TOKEN"] = HF_TOKEN
    print("HF_TOKEN        : configured")
else:
    print("HF_TOKEN        : not configured (optional)")


# ------------------------------------------------------------
# CLOUDFLARE_TOKEN
# ------------------------------------------------------------

CLOUDFLARE_TOKEN = get_secret_or_env("CLOUDFLARE_TOKEN")

if CLOUDFLARE_TOKEN:
    print("CLOUDFLARE_TOKEN: configured")
else:
    print("CLOUDFLARE_TOKEN: NOT CONFIGURED")


if not CLOUDFLARE_TOKEN:
    raise RuntimeError(
        "\n"
        "CLOUDFLARE_TOKEN が設定されていません。\n\n"
        "Google Colab 左側の「シークレット(鍵アイコン)」に、\n"
        "以下を登録してください。\n\n"
        "名前:\n"
        "  CLOUDFLARE_TOKEN\n\n"
        "値:\n"
        "  Cloudflare Tunnel Token\n\n"
        "さらに「ノートブック アクセス」をONにしてください。"
    )


# ============================================================
# 4. Dependency check
# ============================================================

print_section("依存パッケージ確認")

# ------------------------------------------------------------
# MCPは1.30.0を固定
# ------------------------------------------------------------

required_modules = [
    "mcp",
    "diffusers",
    "transformers",
    "accelerate",
    "rembg",
    "uvicorn",
    "timm",
]


missing_modules = [
    module
    for module in required_modules
    if importlib.util.find_spec(module) is None
]


# ------------------------------------------------------------
# MCP version check
# ------------------------------------------------------------

installed_mcp_version = package_version("mcp")

mcp_needs_install = (
    installed_mcp_version != MCP_VERSION_REQUIRED
)


if missing_modules or mcp_needs_install:

    print("必要パッケージの追加・調整を行います。")

    if missing_modules:
        print(f"未検出: {missing_modules}")

    if mcp_needs_install:
        print(
            f"MCP version: {installed_mcp_version}"
            f" → {MCP_VERSION_REQUIRED}"
        )

    print()

    install_packages = [
        f"mcp=={MCP_VERSION_REQUIRED}",
        "diffusers",
        "transformers",
        "accelerate",
        "rembg[gpu]",
        "uvicorn",
        "timm",
    ]

    subprocess.run(
        [
            sys.executable,
            "-m",
            "pip",
            "install",
            "-q",
            *install_packages,
        ],
        check=True,
    )

    print("✅ パッケージ調整完了")

    # pipでMCPのバージョンを変更した場合、
    # 既にロードされたPythonモジュールとの混在を避ける。
    if mcp_needs_install:

        print()
        print("⚠️ MCPパッケージを変更しました。")

        # Google Colabではsys.modulesに旧MCPが残る可能性があるため、
        # 必要な場合はランタイム再起動を促す。
        current_mcp = sys.modules.get("mcp")

        if current_mcp is not None:
            raise RuntimeError(
                "\n"
                "MCPのバージョンを変更しました。\n"
                "Colabのランタイムを再起動してから、"
                "このセルをもう一度実行してください。\n\n"
                f"Required MCP: {MCP_VERSION_REQUIRED}\n"
                f"Previous MCP: {installed_mcp_version}"
            )

else:

    print("✅ 必要パッケージはすべてインストール済み")


# ============================================================
# 5. NumPy compatibility patch
# ============================================================

print_section("NumPy互換性確認")

import numpy as np


numpy_patch_applied = False

try:

    import numpy._core._multiarray_umath as _umath

    if not hasattr(_umath, "_blas_supports_fpe"):

        _umath._blas_supports_fpe = (
            lambda x=None: False
        )

        numpy_patch_applied = True

        print("⚠️ NumPy互換性パッチを適用しました")

    else:

        print("✅ NumPy互換性パッチは不要です")

except Exception as e:

    print(
        "⚠️ NumPy互換性確認中に例外が発生しました:"
    )
    print(type(e).__name__, e)


# ============================================================
# 6. GPU / CUDA
# ============================================================

print_section("GPU / CUDA確認")

import torch


print(f"Python        : {sys.version.split()[0]}")
print(f"NumPy         : {np.__version__}")
print(f"PyTorch       : {torch.__version__}")
print(f"CUDA          : {torch.version.cuda}")
print(f"CUDA available: {torch.cuda.is_available()}")


if not torch.cuda.is_available():

    raise RuntimeError(
        "\n"
        "CUDAが利用できません。\n\n"
        "Google Colabで\n"
        "「ランタイム」→「ランタイムのタイプを変更」\n"
        "からGPUを有効にしてください。\n\n"
        "推奨GPU: NVIDIA L4"
    )


GPU_NAME = torch.cuda.get_device_name(0)

print(f"GPU           : {GPU_NAME}")


# ============================================================
# 7. MCP
# ============================================================

print_section("MCP確認")


import mcp

MCP_VERSION = package_version("mcp")

print(f"MCP version   : {MCP_VERSION}")


if MCP_VERSION != MCP_VERSION_REQUIRED:

    raise RuntimeError(
        "\n"
        f"MCP {MCP_VERSION_REQUIRED} が必要ですが、"
        f"現在は {MCP_VERSION} です。\n\n"
        "Colabランタイムを再起動してから、"
        "このセルをもう一度実行してください。"
    )


try:

    from mcp.server.fastmcp import FastMCP

except Exception as e:

    raise RuntimeError(
        "\n"
        "MCP 1.x / FastMCP を読み込めませんでした。\n\n"
        f"MCP version: {MCP_VERSION}\n"
        f"Original error: {type(e).__name__}: {e}\n"
    )


print("✅ FastMCP import OK")


# ============================================================
# 8. Diffusers
# ============================================================

print_section("Diffusers")


from diffusers import AutoPipelineForText2Image


DIFFUSERS_VERSION = package_version("diffusers")

print(f"Diffusers     : {DIFFUSERS_VERSION}")
print("✅ Diffusers import OK")


# ============================================================
# 9. SDXL Turbo
# ============================================================

print_section("SDXL Turbo ロード")

print(f"Model         : {MODEL_ID}")
print(f"Resolution    : {IMAGE_WIDTH}x{IMAGE_HEIGHT}")
print(f"Steps         : {INFERENCE_STEPS}")
print(f"Guidance      : {GUIDANCE_SCALE}")


# ------------------------------------------------------------
# HF Token
# ------------------------------------------------------------

pipeline_kwargs = {
    "dtype": torch.float16,
    "variant": "fp16",
}

if HF_TOKEN:
    pipeline_kwargs["token"] = HF_TOKEN


pipe = AutoPipelineForText2Image.from_pretrained(
    MODEL_ID,
    **pipeline_kwargs,
)


pipe = pipe.to("cuda")


# Attention slicing
pipe.enable_attention_slicing()


print("✅ SDXL Turbo ロード成功")


# ============================================================
# 10. BiRefNet
# ============================================================

print_section("BiRefNet ロード")


from rembg import new_session, remove


rembg_session = new_session(
    "birefnet-general"
)


print("✅ BiRefNet ロード成功")


# ============================================================
# 11. MCP Server
# ============================================================

print_section("MCP Server 構築")


# IMPORTANT:
#
# MCP 1.30.0ではhost / portをFastMCP()に渡す。
#
# run()側にはhost / portを渡さない。
#
# これが今回のTypeError対策。
#

mcp_server = FastMCP(
    "LINE Sticker Generator",
    host=HOST,
    port=PORT,
)


print("Transport     : Streamable HTTP")
print(f"MCP endpoint  : {MCP_PATH}")
print("MCP host      :", HOST)
print("MCP port      :", PORT)
print("✅ FastMCP Server 作成完了")


# ============================================================
# 12. Image generation lock
# ============================================================

# GPU上のSDXL / BiRefNet処理を同時実行しない。
#
# MCPクライアントから複数リクエストが来ても、
# GPUメモリの急増を避けるため1件ずつ処理する。

generation_lock = threading.Lock()


# ============================================================
# 13. Image generation
# ============================================================

def create_transparent_sticker(
    prompt: str,
    extra_style: str = "",
    seed: int | None = None,
):
    """
    LINEスタンプ用の透明PNGを生成。
    """

    if not prompt or not prompt.strip():

        raise ValueError(
            "promptが空です。"
        )


    # --------------------------------------------------------
    # Prompt
    # --------------------------------------------------------

    final_prompt = f"""
A high-quality Japanese LINE sticker illustration.

{prompt}

{extra_style}

Cute expressive cartoon character,
clear facial expression,
clear body pose,
thick clean outline,
simple polished illustration,
vibrant but soft colors,
centered composition,
full character visible,
isolated character,
sticker artwork,
no background,
no scenery,
no frame,
no border,
no text,
no letters,
no typography,
no watermark,
no logo.
"""


    # --------------------------------------------------------
    # Seed
    # --------------------------------------------------------

    if seed is None:

        seed = torch.randint(
            0,
            2**31 - 1,
            (1,),
            device="cuda",
        ).item()


    generator = torch.Generator(
        device="cuda"
    ).manual_seed(seed)


    # --------------------------------------------------------
    # GPU処理
    # --------------------------------------------------------

    with generation_lock:

        # ----------------------------------------------------
        # SDXL Turbo
        # ----------------------------------------------------

        with torch.inference_mode():

            result = pipe(
                prompt=final_prompt,
                num_inference_steps=INFERENCE_STEPS,
                guidance_scale=GUIDANCE_SCALE,
                height=IMAGE_HEIGHT,
                width=IMAGE_WIDTH,
                generator=generator,
            )


        image = result.images[0]


        # ----------------------------------------------------
        # RGBA
        # ----------------------------------------------------

        image = image.convert("RGBA")


        # ----------------------------------------------------
        # Background Removal
        # ----------------------------------------------------

        transparent_image = remove(
            image,
            session=rembg_session,
        )


        # ----------------------------------------------------
        # PNG → Base64
        # ----------------------------------------------------

        buffer = io.BytesIO()

        transparent_image.save(
            buffer,
            format="PNG",
            optimize=True,
        )

        image_base64 = base64.b64encode(
            buffer.getvalue()
        ).decode("utf-8")


        # ----------------------------------------------------
        # GPU memory cleanup
        # ----------------------------------------------------

        del result


    return {
        "success": True,
        "format": "PNG",
        "width": transparent_image.width,
        "height": transparent_image.height,
        "seed": seed,
        "mime_type": "image/png",
        "data": image_base64,
    }


# ============================================================
# 14. MCP Tool: generate
# ============================================================

@mcp_server.tool()
def generate_transparent_stamp(
    prompt: str,
    extra_style: str = "",
    seed: int | None = None,
) -> dict:
    """
    LINEスタンプ用の透明PNG画像を生成します。

    prompt:
        キャラクター、表情、ポーズ、シチュエーション。

    extra_style:
        絵柄や追加スタイル。

    seed:
        再現性が必要な場合に指定。
    """

    try:

        return create_transparent_sticker(
            prompt=prompt,
            extra_style=extra_style,
            seed=seed,
        )

    except Exception as e:

        return {
            "success": False,
            "error": type(e).__name__,
            "message": str(e),
        }


# ============================================================
# 15. MCP Tool: status
# ============================================================

@mcp_server.tool()
def server_status() -> dict:
    """
    MCPサーバーとGPUの状態を返します。
    """

    return {
        "status": "ok",
        "server": "LINE Sticker Generator",
        "model": MODEL_ID,
        "transport": "streamable-http",
        "endpoint": f"{PUBLIC_URL}{MCP_PATH}",
        "device": "cuda",
        "gpu": torch.cuda.get_device_name(0),
        "cuda": torch.version.cuda,
        "torch": torch.__version__,
        "mcp": MCP_VERSION,
        "diffusers": DIFFUSERS_VERSION,
    }


# ============================================================
# 16. Local MCP server startup
# ============================================================

print_section("MCP Server 起動")


mcp_server_error = None


def run_mcp_server():

    global mcp_server_error

    try:

        print(
            "MCP Server starting..."
        )

        # ====================================================
        # IMPORTANT:
        #
        # MCP 1.30.0
        #
        # host / port:
        #     FastMCP(...)
        #
        # transport:
        #     run(...)
        #
        # ====================================================

        mcp_server.run(
            transport="streamable-http",
        )

    except Exception as e:

        mcp_server_error = e

        print()
        print("❌ MCP Serverでエラーが発生しました。")
        print(
            f"{type(e).__name__}: {e}"
        )


server_thread = threading.Thread(
    target=run_mcp_server,
    daemon=True,
    name="mcp-server",
)


server_thread.start()


print("MCP Server起動待機中...")


# ============================================================
# 17. Wait for local server
# ============================================================

deadline = (
    time.time()
    + SERVER_START_TIMEOUT
)


server_started = False


while time.time() < deadline:

    if is_port_open(
        "127.0.0.1",
        PORT,
    ):

        server_started = True
        break


    if not server_thread.is_alive():

        break


    time.sleep(
        SERVER_CHECK_INTERVAL
    )


# ------------------------------------------------------------
# Startup failure
# ------------------------------------------------------------

if not server_started:

    if mcp_server_error is not None:

        raise RuntimeError(
            "\n"
            "MCP Serverの起動に失敗しました。\n\n"
            f"{type(mcp_server_error).__name__}: "
            f"{mcp_server_error}"
        )

    if not server_thread.is_alive():

        raise RuntimeError(
            "\n"
            "MCP Serverスレッドが終了しました。\n"
            "上のMCP Server起動ログを確認してください。"
        )

    raise RuntimeError(
        "\n"
        f"MCP Serverが"
        f"{SERVER_START_TIMEOUT}秒以内に"
        f"Port {PORT} で起動しませんでした。\n\n"
        "MCP Serverの起動ログを確認してください。"
    )


print("✅ MCP Server起動成功")
print(
    f"Local MCP URL : "
    f"http://127.0.0.1:{PORT}{MCP_PATH}"
)


# ============================================================
# 18. cloudflared
# ============================================================

print_section("cloudflared確認")


if not os.path.exists(CLOUDFLARED_PATH):

    print(
        "cloudflaredをインストールします..."
    )

    subprocess.run(
        [
            "wget",
            "-q",
            "https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64",
            "-O",
            CLOUDFLARED_PATH,
        ],
        check=True,
    )

    subprocess.run(
        [
            "chmod",
            "+x",
            CLOUDFLARED_PATH,
        ],
        check=True,
    )

    print(
        "✅ cloudflared インストール完了"
    )

else:

    print(
        "✅ cloudflared はインストール済み"
    )


# ============================================================
# 19. Stop previous Cloudflare Tunnel
# ============================================================

print_section("既存Cloudflare Tunnel確認")


subprocess.run(
    ["pkill", "-f", "cloudflared"],
    stdout=subprocess.DEVNULL,
    stderr=subprocess.DEVNULL,
)


time.sleep(2)


# ============================================================
# 20. Start Cloudflare Tunnel
# ============================================================

print_section("Cloudflare Tunnel 起動")


cloudflared_process = subprocess.Popen(
    [
        CLOUDFLARED_PATH,
        "tunnel",
        "--no-autoupdate",
        "run",
        "--token",
        CLOUDFLARE_TOKEN,
    ],
    stdout=subprocess.DEVNULL,
    stderr=subprocess.DEVNULL,
)


# 少しだけプロセス状態を確認
time.sleep(2)


if cloudflared_process.poll() is not None:

    raise RuntimeError(
        "\n"
        "Cloudflare Tunnelが起動直後に終了しました。\n\n"
        "Cloudflare Tunnel Tokenが正しいか確認してください。"
    )


print(
    "✅ Cloudflare Tunnel 起動"
)


# ============================================================
# 21. Final status
# ============================================================

print()
print("=" * 70)
print("🚀 LINE STICKER MCP SERVER READY")
print("=" * 70)

print()
print(f"Server        : LINE Sticker Generator")
print(f"MCP           : {MCP_VERSION} / FastMCP")
print(f"Transport     : Streamable HTTP")
print(f"Model         : {MODEL_ID}")
print(f"Resolution    : {IMAGE_WIDTH}x{IMAGE_HEIGHT}")
print(f"Steps         : {INFERENCE_STEPS}")
print(f"GPU           : {GPU_NAME}")
print(f"PyTorch       : {torch.__version__}")
print(f"CUDA          : {torch.version.cuda}")
print(f"Diffusers     : {DIFFUSERS_VERSION}")
print(f"Port          : {PORT}")
print(f"Public URL    : {PUBLIC_URL}")
print(f"MCP URL       : {PUBLIC_URL}{MCP_PATH}")

print()
print("MCP Tools:")
print("  generate_transparent_stamp")
print("  server_status")

print()
print("HF_TOKEN      :", "configured" if HF_TOKEN else "not configured (optional)")
print("Cloudflare    : configured")
print()
print("Status        : ✅ READY")
print("=" * 70)

MCPクライアント側の​接続設定

Google Colab側でセルを実行して Status: READY になったら、手元のAIエディタやクライアント(Claude Desktop / Antigravity / Claude Codeなど)のMCP設定ファイルにエンドポイントを登録します。

Streamable HTTPの接続先は https://[your-tunnel-domain]/mcp です(※旧方式の /sse ではなく /mcp を指定します)。

{
  "mcpServers": {
    "line-stamp": {
      "serverUrl": "https://[your-tunnel-domain]/mcp"
    }
  }
}

設定後にクライアントを再起動(またはMCP再読み込み)すると、generate_transparent_stamp および server_status の2つのツールがAIから認識され、直接プロンプトによる透過画像生成が利用できるようになります。


6. 実例から​得られた​知見と​運用の​改善策

この最小構成でも十分実用的に動作しますが、LINEスタンプ制作やWeb素材を実際に生成する中で、いくつか改善すべきポイントも見えてきました。

実運用を支える品質制御

白い​被写体​(ペンギンや​白猫の​お腹)が​抜ける​問題への​対策

背景が白になりやすいプロンプトでは、背景とお腹や白いパーツの色が重なり、BiRefNetが被写体の内側まで背景と誤認して切り抜いてしまうケースがありました。

被写体の白いパーツが背景と誤認されて透過してしまった例

▲ 【補正前】白いパーツや機体の内側まで背景として判定され、ポッカリと透過(穴あき)してしまった失敗例

対策として、次の2つを組み合わせました。1つ目は、生成時のプロンプトに chroma key green background を指定してあらかじめ色の明度差をつける工夫。2つ目は、切り抜き後のアルファマスクを解析し、輪郭の内側に生じた意図しない空白領域を元画像のRGB値で復元する穴埋め後処理(Hole Filling)です。この合わせ技により、安定して被写体だけを切り抜けるようになりました。

透過同化問題への2つのアプローチ
  • プロンプト側の工夫 背景にグリーンバック(chroma key green background)を指定し、被写体の白い部分と明度差をつける
  • 後処理による補正 切り抜き後のアルファマスクを解析し、輪郭の内側に生じた意図しない空白領域を元画像のRGB値で穴埋め(Hole Filling)復元する
穴埋め処理とクロマキー指定により機体の白いパーツが正常に保持された補正後の生成結果

▲ 【補正後】穴埋め処理(Hole Filling)により、白い機体が透過されず綺麗に復元された状態

白フチ​(ステッカー枠)の​切り​替え

LINEスタンプなどではダークモード対策として白フチが役立つ一方、Webアイコンやイラストのタッチによっては枠線なしの方が自然に見えます。そのため一律で白フチをつけるのではなく、プロンプトに指定があったときだけフチ取り処理を適用するオプトイン形式に落ち着きました。

LINE公式規格等の​ユースケースに​応じた​自動リサイズ

また、生成された512×​512pxの画像をそのまま置くのではなく、描画領域(Bounding Box)を自動検知して余白を詰める処理も組み込みました。LINE Creators Marketの規定サイズ(最大370×​320px、余白10px)に合わせて中央配置すれば、手動でトリミングすることなくそのままスタンプ申請用の素材として使えます。


7. まとめ

Google Colab ProのGPUリソースとFastMCPを組み合わせることで、手元のエージェント環境から直接呼び出せる専用の画像生成パイプラインを構築できました。

正直なところ、Webブラウザを開いて画像を生成し、ダウンロードフォルダからリネームして移動していた頃の作業にはもう戻れません。チャット上で欲しい画像の特徴を伝えるだけで、高解像度に透過されたPNG画像がリポジトリ内の指定ディレクトリに直接配置される体験は、制作のテンポを大きく変えてくれます。BiRefNetによる境界抽出も強力で、手作業での輪郭修正はほぼゼロになりました。

Google AI Proを契約していて「毎月付与される200CUのColab枠を持て余している」という方は、ぜひ自分専用の特化型MCPサーバーを仕立ててみてください。ブログのアイキャッチやWebアセット、LINEスタンプの生成など、手元の開発ワークフローを一気に加速させる強力な武器になります。

Cookie Cat

お疲れ様!手元のチャットから指示するだけで、背景を切り抜いた画像が直接フォルダへ届くのは本当に快適だよ!
画像生成に限らず、自分専用の特化型MCPサーバーを作ってみたい人はぜひ試してみてね!

関連記事