1. はじめに
ChatGPT App を単なる「もうひとつの Web サーバ」と見なすと、すぐにアーキテクチャが混沌とします。どこかに Next.js、どこかに MCP サーバ、どこかにエージェント、どこかに commerce バックエンド——頭の中で全部がひとつの「サーバ」に溶けてしまうのです。
最初からこれはレイヤーケーキだと受け入れるほうがずっと得策です。
- 最上位 — 私たちがコントロールしないが、適応する対象である ChatGPT UI;
- その下 — チャット内で描画される、Apps SDK(Next.js 16、React 19)上の自分たちのウィジェット;
- さらに下 — ツール(tools/resources/prompts)を提供する MCP サーバ;
- オプション — 複雑なシナリオをオーケストレーションするエージェント層;
- 最下層 — いわば「地上の」サービス群:DB、外部 API、commerce/ACP(commerce シナリオ向けプロトコル)など。
コースのノートでは、この流れを次のようなチェーンとして描けます。
User → ChatGPT Widget → Apps SDK → MCP Gateway (Auth) → Agent Service → ACP / Stripe.
いまの課題は、このチェーンをわかりやすいメンタルモデルにすることです。
2. スタックの全体像
まずは全体の絵を見てから、レイヤーごとに下っていきます。
flowchart TD
U[ChatGPT のユーザー] --> C["ChatGPT UI チャット + Apps パネル"]
C --> W["あなたの App のウィジェット (Apps SDK, Next.js)"]
W --> M["MCP サーバ (tools/resources/prompts)"]
M --> AG["エージェント(複数可) (Agents SDK, オーケストレーション)"]
AG --> B["バックエンドと ACP DB、サービス、決済"]
いくつか重要な点に注意してください。
第一に、ユーザーが目にするのは ChatGPT UI とあなたのウィジェットという2つのレイヤーだけで、それより下は「舞台裏」です。
第二に、MCP は単なる略語ではなく、Apps SDK があなたのツール群とやり取りするための公式標準です。サーバはツールの一覧を列挙でき、call_tool を受け付けて、ChatGPT 内でレンダリングするための UI リソースへのリンクを返せなければなりません。
第三に、Agents と ACP の各レイヤーは形式上オプションですが、実際の商用アプリではほぼ確実に必要になります。多段の計画がいる箇所もあれば、決済が必要になる箇所もあるからです。
では各レイヤーを個別に見ていきましょう。
Insight: ChatGPT はフレームワークである
ChatGPT との統合はひとつの場所にあるわけではなく、多数の統合ポイントに分散しています。開発者の感覚的にはフレームワークに近いものです。フレームワークがいつどこであなたのコードを呼ぶかを決め、あなたは正しい場所に正しいものを書き足すだけです。
ChatGPT でもまさに同じです。
- ウィジェット — mcp-resources を通じて登録され、GPT がいつ表示するかを自動で決めます
- mcp-tools — GPT がいつ呼び出すかを自動で決めます
- product feed — mcp-tool を通じてモデルに追加できますが、標準的な手段は site register merchant です
- ACP/InstantCheckout — 別の API
- 認可/認証 — 専用の mcp auth サーバ
3. レイヤー1 — ChatGPT UI(私たちの「ホスト」)
ChatGPT UI は OpenAI のブラウザ(およびモバイル)インターフェイスで、ユーザーがメインの対話を行う場所です。おなじみの入力欄、メッセージ履歴、モデル選択ボタン、アプリ(Store/Composer)のタブがあります。
このレイヤーは私たちはプログラムしません。コードや DOM、スタイルにはアクセスできません。しかし、ここが枠組みを定めます。
- ユーザーがアプリを明示的(Store/Composer)または暗黙的(モデルが App を提案)に「選ぶ」のはここです;
- テキストで答えるか、あなたの tool を呼ぶか、ウィジェットを描画するか、あるいはその全部を行うかを決めるのもここです;
- インラインウィジェット、フルスクリーンモード、PiP ウィンドウなどの基本 UX パターンがあるのもここです(詳しくはモジュール 8)。
実務的には、ChatGPT UI は私たちのホストアプリであることを忘れないでください。私たちが中に組み込まれるのであって、逆ではありません。GPT サーバはあなたのウィジェットのコードを自分たちのサーバに取り込み、不要なものを取り除いたうえで、自分たちのドメインから自分たちのチャットに読み込みます。
4. レイヤー2 — Apps SDK とウィジェット(チャット内の Next.js 16)
次のレイヤーは、Apps SDK を用いて React/Next.js で書かれたあなたの UI コードです。
メンタルモデルは簡単です。チャット内に埋め込まれてレンダリングされるミニ SPA のようなもの。ただし注意点があります。
- あなたのコードはサンドボックスで動く: 制限された DOM、独自のネットワークルール、ChatGPT とやり取りするための特別な window.openai オブジェクト(これについては別講義);
- ウィジェットは対話フローを制御できない: ユーザーは共通のチャットに書き込み、モデルがあなたの App をいつ呼ぶかを決め、あなたは自分の「枠」の中だけで応答します;
- Apps SDK がすべてを肩代わりする: ウィジェット状態と対話履歴の同期、tool 結果の処理、MCP との連携など。
Next.js 開発者の視点ではかなり見慣れた形です。ページ/コンポーネント、フック、props があります。ただし、従来の fetch('/api/...') の代わりに、より頻繁に MCP サーバで定義されたツール(tools)や Apps SDK の特別なフックに頼ることになります(詳細は後の章で)。
少し具体化するため、ここでは仮のプロジェクト GiftGenius を思い出しましょう。これは、誰向けか、予算、用途などのパラメータからプレゼント選びを手伝う App です。
将来の UI のごく一部(SDK 固有の要素はまだ無し、あくまでイメージ):
// GiftSummary.tsx — 私たちの App のシンプルな React コンポーネント
type GiftIdea = {
id: string;
title: string;
price: number;
};
interface GiftSummaryProps {
ideas: GiftIdea[];
}
export function GiftSummary({ ideas }: GiftSummaryProps) {
return (
<ul>
{ideas.map((idea) => (
<li key={idea.id}>
{idea.title} — ${idea.price}
</li>
))}
</ul>
);
}
のちほど、このコンポーネントは結果(ToolOutput)として MCP サーバのツールから ideas を受け取るようになります。しかしアーキテクチャの観点で重要なのは別点です。こうしたコードはすべて「第2レイヤー」に属し、状態の表示だけを担当します。
5. レイヤー3 — MCP サーバ:ツールとデータの世界
ここからはサーバサイドに下っていきます。
Model Context Protocol(MCP)は、LLM クライアント(ChatGPT、Apps SDK、Agents)があなたのサーバと通信する方法を定義した標準です。どのツールが利用可能か、その入出力スキーマ、呼び出し方、ほかに読み込めるリソース/プロンプトなどを規定します。
Apps SDK 向けの最小限の MCP サーバは、次の3つができる必要があります。
- ツール一覧(List tools)を JSON Schema とメタデータ付きで返す;
- ツール呼び出し(Call tools)を処理する — call_tool リクエストを受け取り、ビジネスロジックを実行し、構造化された結果を返す;
- 必要に応じて、特定のウィジェットを表示するために html, js, css, ... を返す。
重要な点として、MCP はトランスポート非依存のプロトコルです。ChatGPT Apps に関しては、HTTP 版(ストリーム可能な実装)に関心がありますが、トランスポートやメッセージ形式の詳細は MCP モジュール(レベル 6)のテーマです。ここでは「Apps SDK が下側で呼ぶのは任意の REST エンドポイントではなく、MCP サーバである」という理解で十分です。
アーキテクチャ的に MCP レイヤーは独立したマイクロサービスになることが多いです。
flowchart LR
subgraph App["あなたの ChatGPT App"]
W["ウィジェット (Next.js + Apps SDK)"]
M["MCP サーバ (@modelcontextprotocol/sdk)"]
end
W <-- JSON-RPC over HTTP/SSE --> M
M --> DB[(ギフトカタログ)]
M --> EXT[外部 API]
MCP サーバの内部では、通常の TypeScript/Node コードを書き、データベース、キュー、サードパーティの API などを使います。公式の MCP 向け TypeScript SDK は、JSON-RPC のシリアライズ、スキーマ検証、呼び出しのルーティングを担います。
GiftGenius では、MCP のツールのひとつは search_gifts のような名前になるでしょう。TypeScript のレベルでは、次のような普通の関数に見えます。
// 疑似コード: MCP サーバ内のビジネスロジック
export async function searchGifts(params: {
recipient: string;
budget: number;
}) {
// ここで DB/カタログへアクセスする
const items = await findGiftsInCatalog(params);
return items.slice(0, 10);
}
後でこれをスキーマ記述付きの MCP ツールとしてラップしますが、肝心なのは、このレイヤーが「MCP を話す普通のバックエンド」であるという点です。
6. レイヤー4 — Agents SDK:複雑なシナリオの頭脳
すべてのアプリにエージェントが必要なわけではありません。しかし、シナリオが「ツール1回呼び出し=1回の応答」を超えた瞬間、エージェント層はとても有用になります。
エージェントは本質的に制御可能な LLM プロセスであり、次を行います。
- ユーザーのリクエストと対話履歴のファクトを読む;
- どのツールをどの順序でどんな引数で呼ぶか、手順を計画する;
- 結果を分析し、「ツールの呼び直し」「ユーザーへの追加質問」「より複雑な回答の構築」を判断する;
- 場合によってはステップ間で状態(メモリ、セッション、チェックポイント)を保持する(これはレベル 12)。
Agents SDK はこの種のシナリオを構造化して記述する手段を提供します。エージェントにどのツールを使わせるか、状態をどう保持/復元するか、ループをどう制限するか等々。エージェントはバックエンド内で実行され、ChatGPT Apps のウィジェット制約を受けずに OpenAI の力を自由に使えるようにします。
このスタックの文脈では、エージェントは一般に MCP レイヤーとドメイン API の間のバックエンドに位置します。外部 API や内部関数、MCP ツールを「手」として使い、自身は「頭脳」を担います。
たとえば、GiftGenius のシナリオは次のようになります。
- ユーザーが「母に 50ドルまででプレゼントを選んで」と書きます。
- ChatGPT があなたのアプリの search_gifts ツールを呼び出します。
- search_gifts の背後では、まずいくつかの詳細(興味、用途)を確認しようと判断するエージェントが動きます。
- ユーザーが追加の希望を詳述します。
- ChatGPT は追加引数付きであなたの search_gifts ツールをもう一度呼び出します。
- サーバ上のエージェントは(在庫確認など)追加のツールを呼ぶことがあります。
- 最終的な候補を ChatGPT に返し、必要なら可視化用ウィジェットのリンクも返します。
後の章でエージェントの run サイクル、冪等性、安全性を詳しく扱いますが、ここでの要点は、エージェント層はオプションながら強力な「頭脳」であり、複雑なオーケストレーションの一部を肩代わりしてくれるということです。
7. レイヤー5 — ACP/バックエンド:お金・データ・現実の配慮
最下層は、いわゆる通常のサービス群です。
- データベース(商品カタログ、ユーザー、注文);
- 外部 API(決済プロバイダ、物流、外部 SaaS);
- ACP(Agentic Commerce Protocol)や Instant Checkout のような、commerce シナリオ向けの専用プロトコル。
ACP は、ChatGPT とエージェントがあなたの commerce バックエンドとどう対話するかを定義します。SKU の選定依頼、カート作成、注文確定、返金、成功/失敗 Webhook など。
GiftGenius の場合は、おおむね次のようになります。
- MCP ツール search_gifts が商品フィード/DB から読み取る;
- エージェントが特定の商品を選んだら、commerce インテント(ACP 経由)を開始する;
- ACP 互換のバックエンドが PaymentService に「課金する」と伝え、ステータスを ChatGPT に通知する;
- ユーザーは ChatGPT 内で外部サイトに移動せずに「注文が完了しました。領収書はこちら」といった最終ステータスを確認する。
ここまで各レイヤーを見たので、次にエンドツーエンドの具体的なシナリオを確認しましょう。
8. エンドツーエンド:ユーザーのリクエストが全レイヤーを通過する流れ
リクエスト例:「母に 50 ドルまでで、読書とお茶が好きな人向けのプレゼントを選んで」。
これをステップに分解します。
- ユーザーは ChatGPT にテキストを書き込みます。これは第1レイヤー(ChatGPT UI)。ユーザーからは普通のチャットに見えます。
- モデルは対話履歴、あなたの App のメタデータ(説明、カテゴリ、権限)を読み、GiftGenius が適切だと判断します。Apps SDK のディスカバリ規則に従い、モデルはツールの説明文、過去の利用状況、コンテキスト、ブランドの言及まで考慮します。
- ChatGPT は次のいずれかを行います。
- UI を出さずに、すぐにあなたの App のツールを呼び出す(tool-first シナリオ);
- または応答で「GiftGenius を使ってプレゼント選びを手伝えます」と提案し、ツールを呼び出す。
- ChatGPT はsearch_gifts ツールに対して call_tool リクエストを MCP サーバへ送ります。MCP サーバはビジネスロジックを実行し、DB/フィードへアクセスして、予算と嗜好でフィルタし、該当商品のリストを JSON で返します。
- ツールの結果が ChatGPT に戻ります。ここで ChatGPT は次のいずれかを行えます。
- 単にテキスト回答(「こちらが 3 つのアイデアです...」)のデータとして使い、ウィジェットは表示しない;
- ウィジェットを表示し、ToolOutput をあなたのコンポーネントに渡して商品カードをレンダリングする。
- このタイミングで初めて、あなたのウィジェット GiftGenius(Apps SDK)が起動し、Next.js のコードがチャット内で描画されます。ウィジェットは「誰へのプレゼント?」「予算」「興味」などの確認フォームを出せます。ユーザーはボタンをクリックしても、チャットに書き続けてもよく、モデルが App と同期してくれます。
- ウィジェットに実データ(ギフトカタログ)が必要になっても、 fetch('https://my-backend/gifts') のように直接叩きません。代わりに MCP ツールの呼び出しを自ら開始します。ChatGPT は再び search_gifts ツールに対する call_tool を MCP サーバへ送ります。
- シナリオが多段の場合(追加確認やランク付け、在庫の追加チェック、代替案の提示など)、エージェント層が計画、ワークフロー管理、エージェントのオーケストレーションを担います。
- ユーザーが特定の商品を「購入」したいと決めたら、ChatGPT は ACP プロトコルで購入を開始します。commerce バックエンドは ACP と Instant Checkout を通じて取引を実行し、ステータスを返し、Webhook を発火します。ChatGPT はユーザーに「注文が完了しました。こちらがレシートです」といった最終ステータスを表示します。
開発者にとって嬉しいのは、各レベルに明確な責務の境界があることです。そして各レイヤーは、標準化された新しいプロトコル(MCP、ACP)で結ばれており、古い REST リクエストではないという点です。
ここまでが論理的な図解です。つまり、どのレイヤーが存在し、そこをリクエストがどう流れるか。次は物理的な側面に進みます。これらのレイヤーをコードとインフラでどう展開するのか——ひとつの Next モノリスでいくのか、複数サービスに分けるのか(ここで言うのはモノリス vs マイクロサービスという一般論のことではありません)。
9. Next.js モノリス vs 分離アーキテクチャ
素朴な疑問として、「これ全部を別個のサービスにしなきゃいけないの? Next.js のモノリスひとつで済ませちゃダメ?」というものがあります。
答え:可能です。本コースではシンプルな形から入ります。最初は「ほぼ全部」を単一のリポジトリ、さらには単一のランタイムにまとめても構いません。
flowchart LR
U[ChatGPT] --> W["Next.js App (Apps SDK)"]
W --> M["MCP endpoint (同じ Next.js 内)"]
M --> DB[(DB/カタログ)]
つまり、あなたの Next.js サーバ(API ルートや専用サーバ)は同時に次を担います。
- UI ウィジェットの配信(Apps SDK のページ/コンポーネント);
- MCP エンドポイントの実装(HTTP 上の JSON-RPC);
- DB/外部 API へのアクセス。
これは開発時や初期バージョンの App にとても便利です。可動部品が少なく、デプロイが簡単だからです。
ただし、機能が増えるにつれ、レイヤーを分ける理由が出てきます。
- MCP サーバは(重いツールが多いため)個別にスケールしたい;
- 財務系バックエンドは別ドメインで、別チームが所管し、特別なセキュリティ要件がある;
- エージェントのロジックは監視や SLA を伴う独立アプリに切り出したい。
その場合、先ほど見たような構成に近づきます。
flowchart TD
U[ChatGPT] --> W[Next.js + Apps SDK]
W --> MG[MCP Gateway]
MG --> M1[MCP Gifts Server]
MG --> M2[MCP Analytics Server]
M1 --> AG[Agent Service]
AG --> ACP[Commerce/ACP Backend]
ここで MCP Gateway という概念が加わります。ChatGPT からの共通入口であり、複数の MCP サーバへのルーティング、REST API との連携、認可、レート制限などを扱います。
本コースではよりモノリシックなシナリオからサンプルを書き始めますが、最初から分割しやすいようにコードを整理していきます。
10. どこにコードを書くのか(何を他に委ねるのか)
モノリスでも分散アーキテクチャでも、どこに自分のコードが置かれ、何を他サービス/チームに委ねるのかを明確にしておくのは有益です。
TypeScript/Next.js 開発者の視点から、自分がコントロールする領域を明示します。
ウィジェット(Apps SDK + Next.js)では:
- ツールの状態とユーザー入力を表示する React コンポーネントを書く;
- Apps SDK のフックを用いて ToolInput/ToolOutput やウィジェットの状態(widget state)を読む;
- 表示モード(インライン/フルスクリーン/PiP、テーマ、サイズ—レベル 8 で扱う)の調整;
- 高度なシナリオのために window.openai を通じて ChatGPT とやり取りする(別モジュール)。
MCP サーバでは:
- MCP SDK を使って tools/resources/prompts を記述する;
- ツールのビジネスロジックを実装する(本質的には DB や API にアクセスする通常の TypeScript 関数);
- モデルが読みやすいようスキーマや応答を最適化する(ハルシネーションを減らし、より構造化する)。
エージェント層(Agents SDK を使う場合)では:
- エージェントが使えるツールと目標を記述する;
- run サイクル、メモリ、ループ制御を設定する;
- エージェントが無意味なことをしたり無限計画に陥らないよう監督する。
ACP/バックエンドでは:
- 既存の commerce サービス(Stripe、自前ストアの product feed など)と統合する;
- あるいは、ACP を理解し注文の送受信ができる新規バックエンドを設計する。
重要なのは、成熟したプロダクトでは1人がすべてのレイヤーを完全に掌握することは稀だという点です。しかしプロトタイピング段階(および本コース)では、少なくともどの場所にどのコードがあるのか理解できるようになるはずです。
11. アーキテクチャが UX とプラットフォームポリシーに与える影響
UX とポリシーは独立したモジュールのテーマですが、レイヤー分割の選択が UX とプラットフォーム要件にどう影響するかは、アーキテクチャ段階で理解しておくべきです。ここで先にいくつか注意点を挙げます。
第一に、サンドボックス。ウィジェットは無制限にインターネットへアクセスしたりユーザーデータを集めたりはできません。すべては管理されたツールと、MCP/Store に記述された権限を通じて行われます。プラットフォームは、あなたが App に必要なデータとアクションを正直に記述することを期待し、その記述に基づいてディスカバリ/提案を行います。
第二に、UX フロー。モデルが一時的にあなたの App を「忘れる」ことも、逆に過度に提案することもあります。そのため、アーキテクチャは中断に強くあるべきです。もしエージェントが長いワークフローを終える前にユーザーが話題を変えても、アプリはそれを無理なく受け流す必要があります。本コースの多段シナリオやワークフローのオーケストレーションは、MCP ツールとエージェント層の上に構築します。
第三に、販売。App が課金を始めた瞬間、セキュリティ、ログ、ACP 契約などの追加要件が有効になります。レイヤー(UI、MCP、Agents、ACP/Backend)の分け方によって、Store レビューやセキュリティ監査をどれだけスムーズに通過できるかが大きく変わります。
最初のまとめ
頭の中に次の地図ができたことを願います。
- 上位レイヤー(ChatGPT UI + Apps SDK)は、ユーザーがあなたの App をどう見てどう感じるかを決める;
- 中位レイヤー(MCP)は、モデルにツールとデータを提供する標準的な方法である;
- エージェントと commerce のレイヤーは、App を単なる「データビューア」ではなく、ロジックとお金を備えた完全なプロダクトにする。
次のレベルでは、まず一番おもしろいところから始めます。Next.js ベースの公式 Apps SDK テンプレートをダウンロードし、ローカルで起動して Dev Mode の ChatGPT に接続します。つまり、まずは Apps SDK/ウィジェット層に触れ、MCP/エージェントはダミーか同梱バックエンドとして置いておきます。
とはいえ、いまの図を頭に入れておくことは重要です。モノレポを見るときに apps/ が UI、 services/mcp がプロトコル、 services/agent がオーケストレーター、 そして services/commerce がお金、だと理解できるように。
12. スタックアーキテクチャ理解での典型的な誤り
誤り1:ChatGPT App = 「自分の REST API への単なる Webhook」だと思うこと。
「ボット」の世界の癖で、モデルが自分の URL に POST を送ってくるだけ、と捉えがちです。実際にはモデルとあなたのコードの間に Apps SDK と MCP が立ちます。必要なのは、ツールとそのスキーマ、ふるまいの記述であって、任意の HTTP を「受ける」ことではありません。
誤り2:UI とビジネスロジックのレイヤーを混在させること。
よくあるアンチパターンは、複雑なドメインロジックをウィジェット側に引き込み、MCP レイヤーを薄い仲介だけにしてしまうことです。結果として UI が重く、テストしにくく、ChatGPT の外で再利用しづらくなります。規則やデータアクセスは MCP/エージェント側に置き、ウィジェットは表示と軽いインタラクションに専念するほうが堅牢です。
誤り3:MCP を無視して「独自プロトコル」を書くこと。
「MCP なんて要らない。JSON を返せばモデルが解釈するだろう」という誘惑に駆られることがあります。短いデモでは「動く」ように見えるかもしれませんが、MCP と Apps SDK が「箱から出してすぐ」提供するディスカバリ、インスペクション、認可、マルチクライアント対応といった標準機能を、すぐに失うことになります。
誤り4:アプリ全体をひとつのレイヤーの上に築くこと。
すべてをエージェントに詰め込み、責務過多にする人もいれば、逆にすべてを MCP ツールに押し込む人、巨大な Next.js モノリスにする人もいます。適切なのは、レイヤーごとの責務を受け入れることです。UI は表示、MCP はデータ/アクションへのアクセス、エージェントはオーケストレーション、ACP/Backend はドメイン不変条件とお金。
誤り5:アーキテクチャが Store レビューとセキュリティに与える影響を過小評価すること。
もし単一サーバが MCP エンドポイントと ACP リソースを兼ね、秘密情報を保持し、生ログに何でも書く……という構成だと、セキュリティ/コンテンツポリシーのレビューは長期戦になり得ます。明確な境界とプロトコルで分離されたアーキテクチャは、後段での作業を大いに楽にします。
GO TO FULL VERSION