MCP 2.0の仕様変更とエージェント設計への影響:なぜ今、再実装が必要なのか

MCP SDK 2.xのリリースは、既存のエージェント実装に対して「バージョンを上げれば動く」という前提を崩す破壊的変更を伴う。具体的には、FastMCPクラスがMCPServerに改名され、list_tools()メソッドが非同期(async)に変更された(GDEによる解説記事)。これにより、従来server.list_tools()を同期的に呼び出して結果を即座に処理していたコードは、awaitなしではエラーを送出する。単なるリネームではなく、イベントループの所有権が呼び出し側に移る設計転換であり、制御フローの再構成を余儀なくされる。

同時に、google-genai SDKの1.xから2.xへの移行に伴い、Interactions APIのレスポンススキーマが「legacy schema」から「steps」形式へ変更された。1.xのコードが2.xのSDKを参照すると、期待するフィールドが存在しないためパースエラーが発生し、APIレベルではHTTP 400エラーとして返される。これはクライアント側のバグではなく、プロトコル仕様の非互換変更である。つまり、SDKのバージョンアップとAPIスキーマの解釈ロジックの書き換えはセットで実施する必要がある。

さらに、これらの変更が適用される対象モデルであるNano Banana 2 Lite(gemini-3.1-flash-lite-image)は、2026年6月23日にGAリリースされ、2027年6月28日以降に退役予定である(Google Cloudドキュメント)。サポート期間が約1年であることを考えると、この仕様変更に適応するための再実装は「将来の保守を前提とした投資」ではなく、「限られた期間内の技術検証をどう設計するか」という判断に帰着する。非同期化とスキーマ変更を単なるバグ修正として片付けず、制御フロー全体の再設計として位置づける必要がある。

Nano Banana 2 Liteの特性とInteractions APIによるステートフル処理の設計

Nano Banana 2 Liteの技術仕様は、最大入力65,536トークン、最大出力4,096トークンで、画像生成とマルチターン画像編集をサポートする(Google Cloudドキュメント)。出力トークンが4,096と制約されている点は、画像生成のペイロードがテキストとは異なる構造を持つことを意味する。エージェント側では、この出力上限を前提としたプロンプト設計と、生成結果の検証ロジックを分けて設計する必要がある。

このモデルの設計上の特徴は、純粋な一発生成(one-shot generation)ではなく、前回の状態を参照して編集を行うステートフルな処理モデルである点にある。Interactions APIでは、store=Trueパラメータを指定することで呼び出し履歴が保存され、その返却されるprevious_interaction_idを次のリクエストに渡すことで、画像を再描画せずに編集が可能になる(GDEによる解説記事)。これは従来のstatelessなLLM呼び出し——毎回コンテキストを丸ごと送信し、サーバー側に状態を持たせない——とは根本的に異なるアーキテクチャを要求する。

ステートフルな処理では、セッションIDの永続化と期限管理がアーキテクチャ上の必須要件となる。IDが失効した場合、previous_interaction_idを参照したリクエストはエラーを返すため、エージェントは「IDの有効期限内か」を判定し、失効時は最新の画像状態を再構築してセッションを再開するフォールバック処理を組み込む必要がある。また、エージェントが並列で複数の画像編集タスクを処理する場合、各セッションのIDを混同しないための命名規則やスコープ管理も設計段階で決定すべきである。

flowchart TD
    A["エージェント"] --> B["Interactions API呼び出し store=True"]
    B --> C["セッションID発行"]
    C --> D{"編集要求があるか"}
    D -->|Yes| E["previous_interaction_idで参照"]
    E --> F["前回状態を参照し画像を編集"]
    F --> G["更新されたセッションID"]
    G --> D
    D -->|No| H["セッション終了"]

上のフローは、store=Trueによる初期保存から、previous_interaction_idを介した繰り返し編集、そしてセッション終了までの状態遷移を示している。実装上の注意点として、previous_interaction_idは各ターンで更新されるため、エージェントのメモリに「最後のID」だけでなく「IDの履歴」も保持しておくと、特定のターンにロールバックするデバッグが容易になる。

MCPサーバーの実装パターン:非同期化とスキーマ変更に伴うコード修正

MCPサーバーの再実装において最初に確定すべきは依存関係のバージョン制約である。xbill9/nb2liteリポジトリ(GitHubリポジトリ)の仕様では、Python 3.10以上、google-genai>=2,<3mcp>=2,<3の3つの制約が必須とされている。この制約は「2.x系の互換性」を保証する最小条件であり、google-genaiが3.xに到達した時点で再度の破壊的変更が想定される。要件定義の段階で、この制約がプロジェクトの他の依存関係と競合しないかを検証しておくことが重要である。

MCPServerクラスの新規実装では、list_tools()が非同期メソッドとなった影響で、イベントループの組み込みが必須になる。以下は、非同期化を前提とした最小構成の例である。

import asyncio
from mcp import MCPServer

class ImageGenServer(MCPServer):
    async def list_tools(self):
        return [
            {
                "name": "edit_image",
                "description": "Edit image using previous interaction state",
                "input_schema": {
                    "type": "object",
                    "properties": {
                        "previous_interaction_id": {
                            "type": "string",
                            "description": "ID from previous interaction"
                        },
                        "instruction": {
                            "type": "string",
                            "description": "Edit instruction"
                        }
                    },
                    "required": ["instruction"]
                }
            }
        ]

    async def call_tool(self, name, arguments):
        # Interactions API呼び出し(store=True)
        # previous_interaction_idが指定された場合は編集モード
        ...

# イベントループの所有権はサーバー側
async def main():
    server = ImageGenServer()
    # サーバーのライフサイクル管理
    ...

asyncio.run(main())

この例で設計上の分岐点になるのは、call_toolの内部でInteractions APIを呼び出す際のレスポンス解析である。google-genai SDK 2.xではレスポンスが「steps」形式で返されるため、1.x時代のようなフラットなフィールド参照では正しくパースできない。steps形式では各処理ステップが配列として返されるため、最終結果を抽出するロジックが「配列の最後の要素から取得する」形に変わる。この差分を吸収するためには、SDKのバージョン差を隠蔽するアダプター層を挟む設計が有効である。

アダプター層の設計方針は、MCPサーバーのツール定義(list_toolsの戻り値)と、内部のAPI呼び出しロジック(call_toolの実装)を分離し、APIスキーマの変化がツール定義に波及しないようにすることである。具体的には、call_toolの内部でSDKのレスポンスを中間表現(例: 辞書)に変換する正常化関数を定義し、その出力形式をツール定義側が参照する。こうすることで、SDKのバージョンアップ時に正常化関数だけを書き換えればよく、ツール定義やエージェント側のコードは変更しなくて済む。xbill9/nb2liteリポジトリの実装も、この方針でClaude Code・Codex・Antigravity CLIの3つのCLIに対応している。

以下は、steps形式のレスポンスを中間表現に正常化する関数の具体例である。SDK 2.xのレスポンスオブジェクトからsteps配列を抽出し、最終ステップの出力とエラー状態をフラットな辞書にまとめる。

def normalize_steps_response(response) -&; dict:
    """
    google-genai SDK 2.x の steps 形式レスポンスを
    中間表現(フラットな辞書)に正常化する。
    ツール定義側はこの戻り値の形のみを参照する。
    """
    steps = response.steps
    if not steps:
        raise ValueError("steps が空です。レスポンスが不正です。")

    # 最後のステップから最終結果を抽出
    last_step = steps[-1]

    normalized = {
        "interaction_id": response.id,
        "status": last_step.get("status", "unknown"),
        "output": last_step.get("output"),
        "error": None,
    }

    # 配列内のエラーステップを検出(途中失敗のケース)
    for step in steps:
        if step.get("status") == "error":
            normalized["error"] = step.get("error_message")
            break

    return normalized

この正常化関数をcall_toolの末尾に配置することで、SDK 2.xのsteps形式でも1.xのフラット形式でも、ツール定義側が受け取るデータ構造は同一になる。SDKが将来3.xにアップデートされた場合も、この関数の内部実装を差し替えるだけで済む。

Claude Codeへの統合:プラグインマートと環境変数の制御フロー

Claude Codeへの統合は「claude plugin marketplace add」コマンドでプラグインを追加する形で始まる。xbill9/nb2liteリポジトリが提供しているMCPサーバーをこのコマンドで登録すると、CLI内部で依存関係の解決とプラグインのロードが行われる。ここで設計判断が分かれるのが、APIキーの取得元だ。

プラグイン設定ファイルにキーを埋め込む方式と、環境変数から参照する方式の2通りがある。前者は開発環境では手軽だが、設定ファイルがリポジトリにコミットされた瞬間にキーが漏洩するリスクを負う。後者は「.envファイルやシークレットマネージャーから読み込む」形になるため、キーの配布とローテーションを外部に委ねられる。チームで共用する場合は後者を前提に、環境変数名の命名規則(例: GEMINI_API_KEY)をチーム内で統一しておくと、CLIのバージョンアップ時に設定を再入力しなくて済む。

エージェントが画像生成ツールを呼び出す際の注意点は、コンテキストの引き継ぎ方である。通常のCLIコマンドでは引数としてプロンプトを渡すだけだが、プラグイン経由のツール呼び出しでは、CLIがセッションコンテキストを保持した上でツールに渡す形になる。つまり、マルチターンで画像編集を行う場合、前のターンでの生成結果を次の呼び出しにどう引き継ぐかが、ツール定義側のスキーマに依存する。ここがSDKのバージョン差で壊れると、CLI側ではツール自体が認識されなくなるため、プラグインのロードログでエラーを確認する必要がある。

大規模チームでの運用を想定すると、APIキーの配布方法とMCPサーバーの可用性監視をどうCLIの設定に反映させるかが課題になる。プラグインマート経由の統合は手軽だが、内部で解決される依存関係のバージョンがCLIの更新で変わると、動作が静かに変わる。トラブルシューティングの際は、CLIの内部ログに出力されるプラグインロード時の解決済みバージョンを確認し、必要であればロックファイルで固定する運用にするのが安全だ。

sequenceDiagram
    participant U as "ユーザー"
    participant CC as "Claude Code CLI"
    participant P as "プラグイン MCPサーバー"
    participant E as "環境変数"
    participant API as "Nano Banana 2 Lite API"
    U->>CC: claude plugin marketplace add
    CC->>P: プラグインロード・依存解決
    P->>E: APIキー参照
    E-->>P: キー返却
    CC->>P: ツール呼び出し - 画像生成
    P->>API: リクエスト送信
    API-->>P: レスポンス - steps形式
    P-->>CC: 結果返却

Codex CLIとの連携:非対話モードでのツール承認と設定の罠

Codex CLIの非対話モード(exec)でMCPツールを呼び出す際、config.tomldefault_tools_approval_mode = "approve" の設定が必要である。この設定がない場合、execモードはツール呼び出しの承認を待ったままブロックし、パイプラインがタイムアウトで中断する。開発環境で即座にツールを動かしたい場面では「approve」で問題ないが、これは外部からの入力を含むコンテキストで不正なツール呼び出しを無条件に許可してしまうリスクを伴う。

つまり、このモードはセキュリティレベルと自動化適性のトレードオフをどう切るかという設計判断になる。以下に3つのモードを対比する。

モードセキュリティ自動化適性使用シナリオ
approve低(全ツール無条件許可)高い開発環境・信頼済みパイプライン
manual中(呼び出しごとに確認)低い本番デプロイ前の最終確認
deny高(全ツール拒否)なしCIの静的チェック段階

実用上の落とし穴として、MCPサーバーが定義するツール名とCodexの内部ツールカタログが競合する場合がある。例えば、サーバー側が「image_edit」という名前でツールを公開し、Codex内部にも同名のビルトインツールが存在する場合、どちらが優先されるかの挙動が設定ファイルでは明示されない。この場合、ツール名に接頭辞を付けて衝突を回避するか、Codex側のツール定義をオーバーライドする設定を追加する必要がある。どちらの挙動になるかは、CLIのバージョンによって変わるため、アップデート前後でツール一覧をダンプして差分を確認する運用が望ましい。

自動化パイプラインに組み込む際は、ツール呼び出しの前に可用性チェック(エンドポイントへのヘルスチェック)を挟み、失敗時はフォールバック処理(例: ローカルモデルでの代替生成)に切り替えるロジックをシェル側で実装するのが実用的だ。Codexのexecモードはエラー時に標準出力にメッセージを出すだけでexit codeの区別が粗いため、ログパースではなくエンドポイントの応答で健全性を判定する方が確実である。

Antigravity CLIでの統合:スキル配置とシンボリックリンクの運用

Antigravity CLIでは「agy mcp add」コマンドでMCPサーバーを追加する。サーバー本体の登録はこの1コマンドで完了するが、xbill9/nb2liteリポジトリが提供する検証スキルをグローバルディレクトリに配置するには、シンボリックリンクの使用が有効な手段となる。スキルファイルがリポジトリ内のローカルパスに存在するため、CLIが参照するグローバルディレクトリからリンクで到達させる構造になる。

シンボリックリンクの運用で問題になるのは、環境依存とリンク切れだ。開発マシン上の絶対パスでリンクを作成しておくと、別マシンやコンテナイメージ上で同じパスが存在しない場合に、CLIがスキルを読み込めずにエラーを返す。CI/CDパイプラインでは、ビルド時にリポジトリのチェックアウトパスを環境変数として渡し、ln -s でリンクを動的に作成するステージを設けることで、この問題を回避できる。また、リポジトリのブランチ切替や削除でリンク先が消えると、CLI側では「スキル未検出」の曖昧なエラーになるため、リンクの存在チェックをパイプラインの前置き処理に組み込むのが安全だ。

別の注意点として、MCPサーバーとCLI間のデータ転送で画像バイナリを扱う場合、ペイロードサイズとエンコーディングの違いが問題になる。ベース64エンコードでサイズが約1.33倍に膨張するため、大きな画像を複数ターンにわたってやり取りすると、CLI側のバッファ上限に到達する。この場合は、サーバー側で画像をテンポラリファイルに書き出し、パスをツール結果として返す形にすることで、インメモリでの膨張を避ける設計が有効である。

複数のエージェントCLIを並列運用する場合、それぞれのMCPサーバーが同じポートをバインドしようとすると衝突する。サーバー側でポート番号を引数として受け取る設計になっていれば、CLIごとに異なるポートを割り当てることで並列動作が可能になる。命名規則も同様に、サーバー名にCLI識別子を接尾辞として付け(例: nb2lite-claude, nb2lite-codex)、プロセス一覧やログでどのCLIがどのサーバーに接続しているかを一目で識別できるようにしておくと、運用時の混乱を防げる。

実装におけるトレードオフ:柔軟性 versus 保守性とセキュリティ

Interactions APIによるステートフル処理は、画像編集の文脈で開発効率を大きく向上させる。前回生成の画像を再描画せずに編集できるため、API呼び出し回数が減り、レイテンシも下がる。しかし、その代償としてセッションIDの永続化と管理コストがアーキテクチャに常駐する。IDの有効期限を超過した呼び出しは失敗するため、アプリケーション側で期限管理のロジックを実装する必要がある。さらに、保存された呼び出し履歴にはユーザーの画像データが含まれるため、データ保持期間に関するプライバシー規制への適合性を確認する責任も開発チームに残る。

MCP SDK 2.xにおける非同期化(list_tools()が非同期メソッドになったこと)は、並列でのツールディスカバリやI/O待ちの解消によりスループット向上に寄与する。一方で、同期的なコードベースにawaitが波及することで、エラーハンドリングの構造が複雑になる。例外がコルーチン内で発生した際にどうキャッチするか、タイムアウトをどこで制御するかなど、設計判断が増える。チームにasyncioの運用経験が薄い場合、この移行がボトルネックになるリスクを評価すべきである。

CLI統合における「approve」モードのような自動化設定は、パイプラインの無人化を実現する反面、不正なツール呼び出しに対するガードレールが薄くなる。Nano Banana 2 Lite(gemini-3.1-flash-lite-image)は2026年6月23日にGAリリースされ、2027年6月28日以降に退役予定であるため、このモデルに依存した実装は約1年という有効期間を持つ。退役後の代替モデルへの移行コストを事前に試算しておかないと、技術負債が一気に顕在化する。

観点利点注意点・リスク
開発効率ステートフル処理でAPI呼び出し回数を削減セッションID管理のロジックが常駐する
セキュリティ承認モードで自動化パイプラインを無人化不正呼び出しへのガードレールが薄くなる
保守性非同期化でスループット向上エラーハンドリング構造が複雑化
移行コスト短期検証で設計方針を早期に確定退役後のモデル差し替えコストを事前に試算

運用設計:エラーハンドリングとモニタリングの実践

google-genai SDKの1.xから2.xへの移行に伴い、Interactions APIのレスポンススキーマが「legacy schema」から「steps」形式へ変更された。この変更を踏まえていないコードはHTTP 400エラーを返す。運用上重要なのは、エラーログにレスポンスボディの詳細(特にエラーメッセージとリクエストID)を含めることである。ステータスコードだけでは原因の切り分けが困難であり、スキーマ変更か権限不足かを即座に判別できるログ設計が求められる。

MCPサーバーのエンドポイントとInteractions APIのセッションIDにはそれぞれ有効期限が存在する。タイムアウトや期限切れエラーが発生した際の再試行ロジックを設計する際は、冪等性を前提に置かないと二重処理のリスクが生じる。特に画像生成のように副作用を持つ操作では、再試行前に前回のリクエストが完了したかを確認する手段(例: リクエストIDによる照会)を組み込むのが安全である。

複数のCLIツール(Claude Code、Codex、Antigravity CLI)から同一のMCPサーバーを呼び出す場合、サーバー側のレートリミットに到達する。Python 3.10以上とgoogle-genai>=2,<3mcp>=2,<3のバージョン制約を満たす環境であっても、並列呼び出しの増加はサーバーの処理能力を超える。バックオフ処理(指数退避)を導入し、レートリミットエラー(HTTP 429)を検知した際に呼び出し間隔を段階的に拡大するロジックを実装するのが実務的な対応である。

Nano Banana 2 Liteの退役予定(2027年6月28日以降)を見据えた運用設計では、モデル名をハードコードしないことが基本となる。モデル識別子を設定ファイルや環境変数から読み込むようにし、代替モデルへの差し替えが設定変更だけで完結する構造にしておく。これにより、退役日の到来時にコード変更なしで移行できる。

stateDiagram-v2
    state "正常処理" as Normal
    state "エラー発生" as Error
    state "再試行" as Retry
    state "セッション終了" as End
    [*] --> Normal
    Normal --> Error : HTTP 400 / タイムアウト
    Normal --> End : 正常応答
    Error --> Retry : リトライ可能 - 冪等性確認済み
    Error --> End : リトライ不能 - スキーマ不整合等
    Retry --> Normal : 成功
    Retry --> End : 最大回数超過
    End --> [*]

まとめ:エージェント設計におけるAPI仕様の進化への適応

MCP 2.0とInteractions APIの進化は、単なる機能追加にとどまらない。MCP SDK 2.xでのクラス改名(FastMCP → MCPServer)とメソッドの非同期化、google-genai SDK 2.xでのレスポンススキーマ変更(legacy schema → steps形式)は、いずれも既存コードの制御フローを根本から書き換えることを要求する破壊的変更である。これらの変更に対応するためには、アダプター層を介して下流の仕様変更を隔離する設計が有効である。

Claude Code、Codex、Antigravity CLIという異なるエージェントツールへの統合では、それぞれがプラグインマート、config.toml、シンボリックリンクという異なる設定機構を持つ。一貫した運用設計を行うには、各CLIの認可フローと設定ファイルの構造を正確に理解し、APIキーの取得元やツール承認モードをチーム内で統一する必要がある。この統一がないまま運用を始めると、環境ごとに挙動が異なり、障害時の切り分けが困難になる。

Nano Banana 2 Lite(gemini-3.1-flash-lite-image)のサポート期間は2026年6月23日のGAリリースから2027年6月28日以降の退役までであり、約1年という限られた有効期間を持つ。この期間を技術検証に活用しつつ、モデル依存度の高いコンポーネントを抽象化し、代替モデルへの移行が設定変更だけで完結する構造を維持することが、長期的なメンテナンス性を確保する鍵となる。

API仕様の頻繁な変更が常態化する環境では、アダプター層のテストカバレッジを強化し、仕様変更を検知する回帰テストをCIに組み込むことが技術リーダーに求められる。Google Developer Experts(GDE)コミュニティの技術記事や公式ドキュメントの追跡を運用に組み込むことで、破壊的変更の兆候を早期に捕捉し、実装への影響を最小限に抑える体制を構築することが、堅牢なエージェント基盤の前提条件である。

関連記事

参考

本記事は海外の技術トレンド「Nano Banana 2 Lite, Revisited: MCP 2.0, the New Interactions API, and Three Agent CLIs」(DEV)で話題のテーマをもとに、両儀システムソリューションズが独自に解説したものです。