1. なぜローカルデバッグの講義が必要なのか
これまでのモジュールで、Apps SDK と MCP のスタック構造は扱いました。ここでは、なぜローカルデバッグの講義が必要なのかを話します。
多くの人がこうなりがちです。「とりあえず ChatGPT を開いて『自分の App を使って』と書いて、あとは出力を眺める。動かなければ適当にコードを書き換える」。これはちょうど、サーバーログを一度も開かずに、ブラウザの HTML だけを見てバックエンドを直すようなものです。
ChatGPT Apps は特に「魔法」に陥りやすい領域です。GPT はツールを呼ぶかどうかを自分で判断し、独自のエラーロジックも持っています。内部で何が起きているか見えていないと、デバッグは呪術のようになってしまいます。
私たちの目標: これを健全なエンジニアリングプロセスに変えること。
- どこで Next/MCP のログを見るか分かる;
- MCP サーバーをインスペクタ経由で手動で叩けるようになる;
- Dev Mode が何を検証しているか理解し、ChatGPT が自分のサーバーに到達できているか確認できる。
そして何より重要なのは、「GPT 当てずっぽうデバッグ」をやめ、まずは スタックの低いレイヤー(サーバーとプロトコル)を検証し、その後に UI とモデルの挙動を見る、という順序にすることです。
2. メンタルモデル: デバッグの3層
混乱に溺れないため、デバッグを3つのレベルで考えることにします。いわば小さな「ミルフィーユ」です。
| レベル | そこで動くもの | 典型的な症状 | 使うデバッグ手段 |
|---|---|---|---|
| UI(ウィジェット) | React コンポーネント、ステート、window.openai | ウィジェットが空/グレー、レンダー崩れ、ボタンが効かない | ブラウザの DevTools |
| バックエンド / MCP サーバー | ツール、DB/API へのアクセス | 500 台、「ツールが落ちる」、おかしなデータ | サーバーログ、MCP Inspector |
| MCP プロトコル | JSON‑RPC、tools/list、tools/call、スキーマ | GPT が「ツールを呼べなかった」と言う、invalid params | インスペクタ + リクエストログ |
第2層では MCP サーバー自体(ツール、DB、API)が何をしているかに注目し、第3層では「配線」と MCP メッセージ(JSON‑RPC、スキーマなど)のフォーマットを見ます。
この3つが、この講義とデバッグ編の基本方針です。
リクエストの流れを図で見ると分かりやすいでしょう。
sequenceDiagram
participant User as ユーザー
participant ChatGPT as ChatGPT (Dev Mode)
participant Tunnel as トンネル (ngrok/CF)
participant Next as Next.js + MCP
User->>ChatGPT: 「$50 以内のギフトを選んで」
ChatGPT->>Next: tools/call search_gifts(トンネル経由)
Next->>Next: MCP のツールを呼び出し、DB/API にアクセス
Next-->>ChatGPT: JSON-RPC result + ToolOutput
ChatGPT-->>User: 回答 + ウィジェットのレンダリング
故障箇所はどこでもあり得ます。トンネル、エンドポイント、MCP ロジック、JSON スキーマ、React ウィジェット。デバッグの際のあなたの仕事は、どの層でエラーが起きているかを特定することであり、いきなり全部を書き換えることではありません。
3. Next.js と MCP のログ: すべての基本
一番地味で一番役に立つもの――ログから始めましょう。
ローカル開発時のログの場所
Next.js の Apps SDK 標準テンプレートでは、MCP サーバーは多くの場合 API ルート(/api/mcp など)にラップされています。npm run dev を起動すると、1つのターミナル内に:
- Next.js の dev サーバー;
- JSON‑RPC の tools/list、tools/call などを受ける MCP エンドポイントのハンドラー;
- console.log/console.error による各種出力。
MCP を別プロセスに分けている場合はターミナルが2つになりますが、要点は同じです。重要なものはすべてコンソールに現れます。
区別が重要:
- ビルド/起動エラー — next dev が立ち上がらない、TypeScript が落ちる、誤った import など;
- 実行時エラー — 起動はしたが、特定の /api/mcp リクエストでツールが落ちる。
Next.js の dev モードはランタイムエラーをオーバーレイでも表示し、スタックトレースもコンソールに出します。
MCP サーバーで何をログに出すべきか
MCP は JSON‑RPC を使いますが、デバッグのために完全な JSON をすべて出す必要はありません。短く構造化されたログの方が有用です。
MCP ログの良いプラクティスは少なくとも次を出すことです: timestamp、 request_id/traceId、 ツール名、 匿名化したパラメータ、 ステータス(ok/error)、 実行時間。
GiftGenius 向けの最小の logger.ts は次のようになります。
// src/lib/logger.ts
export function logToolEvent(
phase: "start" | "end" | "error",
data: Record<string, unknown>
) {
const ts = new Date().toISOString();
console.log(JSON.stringify({ ts, phase, ...data }));
}
ツールのハンドラーではこう使います。
// src/mcp/tools/searchGifts.ts
import { logToolEvent } from "@/lib/logger";
export async function searchGiftsTool(args: { q: string }) {
const traceId = crypto.randomUUID();
logToolEvent("start", { tool: "search_gifts", traceId, args });
try {
// ... 実際のギフト検索 ...
const results = []; // スタブ
logToolEvent("end", { tool: "search_gifts", traceId, count: results.length });
return results;
} catch (err) {
logToolEvent("error", { tool: "search_gifts", traceId, error: String(err) });
throw err;
}
}
重要な注意点が2つあります。
第一に、ログにフルの e‑mail、電話番号、カード番号、トークンを保存しないこと。これは見た目が悪いだけでなく、MCP の基本的なセキュリティプラクティスにも反します。
第二に、traceId は最良の友です。Next.js と MCP のログを合わせて見るとき、これでイベントを簡単にひも付けられます。特定の tools/call リクエスト、対応する React レンダー、ウィジェットのネットワークログを関連づけられます。
ログからどこで落ちたかを見極める
ターミナルに logToolEvent の JSON 行が流れているとします。典型的なシナリオ:
- phase: "start" と tool: "search_gifts" が来た;
- phase: "end" がなく、代わりに phase: "error" とスタックトレースがある;
- つまり、ツールはあなたのロジックに到達したが、その内部(外部 API 呼び出し、パース、DB 操作など)で壊れたことが分かる。
一方、そのツール名のログ自体がまったく見えない場合は、リクエストがツールに到達していません。スタックを上にたどり、トンネル、/mcp エンドポイント、tools/call の JSON リクエストを確認します。
4. MCP Inspector: ChatGPT 以前の段階での MCP デバッグ
ログが「目」だとすれば、MCP Inspector(あるいは MCPJam Inspector)は顕微鏡です。
MCP Inspector の概要と必要性
MCP のモジュールでは、Inspector を接続して「Hello, MCP」サーバーを確認しました。ここでは、これを主なデバッグツールとして使います。まず MCP 単体が健全かを確かめ、その後に Dev Mode と UI を見ます。
Inspector は(多くの場合、Web UI と CLI を持つ)MCP クライアントとして振る舞うアプリケーションです。HTTP/SSE や stdin/stdout でサーバーに接続し、tools/list、tools/call を実行して、生の JSON メッセージ、ハンドシェイク、ツール一覧、リソースなどを表示します。
肝は、ChatGPT を方程式から外すこと。ツールが動かないときは、まずサーバーが生きているか、プロトコルとスキーマが正しいかを確認し、そのうえで GPT を疑うべきです。
Inspector を使ったミニフロー
ローカルデバッグの典型的な流れは次のとおりです。
- npm run dev を起動して、Next.js + MCP エンドポイントを立ち上げる。
- MCP Inspector を起動する。例:
npx @modelcontextprotocol/inspector
(具体的なコマンドは使うツールによって異なります)。
- Inspector に MCP エンドポイントの URL を指定する。例: http://localhost:3000/api/mcp(トンネルも一緒に検証したい場合は HTTPS トンネルでも可)。
- ハンドシェイクが通るか確認する。サーバーは対応する capabilities、ツール一覧、リソースなどを返すはずです。
- 対象のツールを手動で呼ぶ。search_gifts を選び、引数を {"q": "30歳までの女性向け"} のように入れて「Call tool」を押し、以下を確認:
- レスポンスが来たか;
- JSON‑RPC や MCP のエラーになっていないか;
- その呼び出しに対し、サーバーがログに何を書いたか。
もし Inspector の時点で落ちるなら、ChatGPT を開く必要はありません。MCP サーバーを直してください。
Inspector では問題ないのに ChatGPT が文句を言うなら、さらに上位の問題(Dev Mode の URL、認可、モデルの振る舞い)を疑います。
「わざとツールを壊す」例
search_gifts を意図的に壊してみます。
export async function searchGiftsTool(args: { q: string }) {
if (args.q === "壊れて") {
throw new Error("デバッグ実演用の学習エラー");
}
// ... 通常のロジック ...
return [];
}
次に:
- Inspector で、引数 {"q": "壊れて"} を指定して search_gifts を呼ぶ。
- ログで phase: "error" とスタックトレースを見る。
- MCP サーバーがエラーを正直に返していることを確認する。
その後、これを ChatGPT Dev Mode に接続し、「『壊れて』という語を含むギフトを選んで」とモデルに頼むと、ツールを呼ぼうとして「I encountered an error running the tool」のようなメッセージがユーザーに表示されます。原因はモデルではなく、あなたが投げた明示的な例外だと分かります。
この手法は思考法の訓練になります。ビジネスエラー(こちらが Error を投げた)と、プロトコルエラー(JSON を壊した、ツール名が間違っている等)を明確に分けられるからです。
5. ウィジェットのデバッグ: DevTools、ステート、そして「デバッグバナー」
MCP サーバーが概ね見えてきたら、フロントエンド――Apps SDK のウィジェットに移ります。
ウィジェットのエラーをどこでどう見るか
あなたのウィジェットは ChatGPT 内の iframe サンドボックスでレンダリングされます。良いニュースは、その iframe でも通常どおりブラウザの DevTools が使えることです。
ミニ手順:
- ブラウザ(Chrome/Edge/Firefox)で ChatGPT を開く。
- DevTools を開く(通常は F12 または Ctrl+Shift+I)。
- Console タブで、ウィジェットが動いているフレームのコンテキストを選ぶ(多くは web-sandbox.oaiusercontent.com ドメイン)。
- チャットを更新/メッセージ送信して、GPT に App を表示させる。
もしウィジェットが:
- まったく表示されない;
- グレー/空のまま;
- コンソールに赤いエラーが出る
――それはほぼ間違いなく React コードの問題です。未定義のプロパティ、誤った import、壊れたフックなど。
Network タブも有用です。ここでは次が見えます。
- アプリの JS バンドルの読み込み(404/500 なら dev サーバー/トンネル側の問題);
- window.fetch 経由でウィジェットが外部に出すリクエストと、その 4xx/5xx 応答。
最小のデバッグバナー
便利な手法は、ウィジェットのルートコンポーネントに小さな「デバッグバナー」を追加することです。Dev Mode では環境とビルドバージョンを表示します。
例:
// src/components/DebugBanner.tsx
export function DebugBanner() {
if (process.env.NODE_ENV !== "development") return null;
return (
<div style={{ padding: 4, background: "#222", color: "#0f0", fontSize: 10 }}>
ENV: dev | build: local | {new Date().toLocaleTimeString()}
</div>
);
}
ウィジェットのルートコンポーネントで:
// src/app/widget/page.tsx
import { DebugBanner } from "@/components/DebugBanner";
export default function GiftGeniusWidget() {
return (
<div>
<DebugBanner />
{/* 残りのギフト検索 UI */}
</div>
);
}
ChatGPT を開いて App を起動したのにバナーが見えない場合、あなたの JS はそもそもブラウザに届いていません。ビルドエラー、エンドポイントの問題、あるいはウィジェットが MCP サーバーに登録されていない可能性があります。
ローカルステートとエラー処理
あなたのウィジェットは、読み込み・成功・エラーなどの状態を表示できるはずです。まだなら今が追加の好機です。
最小パターン:
const [status, setStatus] = useState<"idle"|"loading"|"error"|"success">("idle");
async function handleSearch(query: string) {
try {
setStatus("loading");
// MCP ツールを window.openai.callTool または Apps SDK のフック経由で呼ぶ
setStatus("success");
} catch (e) {
console.error("Search failed", e);
setStatus("error");
}
}
JSX:
{status === "error" && (
<div style={{ color: "red" }}>問題が発生しました。もう一度お試しください。</div>
)}
デバッグの観点で重要なこと:
- 例外を握りつぶさない(そうしないとコンソールが空で、UI は「固まった」ように見える);
- UI にエラーを明示的に反映する。そうしないとユーザーには App が死んだように見える。
6. デバッグの一部としての Dev Mode: 何をするのか、濡れ衣を着せないために
ここで ChatGPT Dev Mode を図に入れます。これまではあなたのコードだけを見てきました。しかし、ローカルや Inspector では問題ないのに、ChatGPT が「Error talking to [AppName]」と言う、あるいはそもそも App を提案してこないことがあります。
Dev Mode がすること
Dev Mode は、ChatGPT 上で次のことができるモードです。
- 自分の Apps を作成・編集する;
- MCP サーバーのエンドポイント(通常は https://あなたのドメイン/mcp または /api/mcp)を指定する;
- Store での公開なしにマニフェストやメタデータを素早く更新する。
デバッグの観点では、Dev Mode は単なる設定レイヤーの一つに過ぎません。
- URL が誤っている;
- 末尾の /mcp を付け忘れている;
- トンネルでドメインが変わったのに設定を更新していない
――この場合、ChatGPT はあなたのサーバーに到達できません。
Dev Mode でよくある壊れ方
定番の流れ:
- トンネル https://abcd.ngrok.io を立ち上げ、Dev Mode に設定。すべて動いていた。
- 翌日 ngrok を再起動して https://efgh.ngrok.io になった。
- Dev Mode には依然 https://abcd.ngrok.io/mcp が残っている。
- ChatGPT が「Error talking to GiftGenius」と言う。
このとき MCP Inspector を http://localhost:3000/api/mcp に向けると正常に見えるはずです。つまり MCP は生きているが、ChatGPT は見当違いの場所を見ているということです。
解決: Dev Mode の設定を開き、URL を更新し、必要なら末尾の /mcp を忘れない。
Dev Mode と Store の違い
この講義では Dev Mode だけを扱います。ここはあなたのサンドボックスです。URL を頻繁に変えたり、トンネルを付け替えたり、ツールスキーマをいじるのは普通です。
後に Store に進むと、エンドポイントはより厳密に固定され、こうしたお遊びはよくないものになります。しかし Store はまだ先の話。いまは Dev Mode で壊して直してを気楽に繰り返しましょう。
7. ミニ・デバッグ手順: 「何も動かない」ときにやること
ここまでを実用的な手順にまとめます。基本的には冒頭の3レベルデバッグを手順に落とし込んだものです。
たとえば、ChatGPT を開き、GiftGenius を選び、「ギークな友人向けに 30$ 以内でギフトを選んで」と頼んだところ:
- GPT が App について何も言わない;
- あるいは「Error talking to GiftGenius」と出る;
- あるいはウィジェットが空/グレーのまま。
絶望しないための手順は以下。
手順 1(MCP/サーバーレイヤー): Inspector とログで MCP を確認
まず GPT と UI を忘れます。サーバーだけに注目。
- npm run dev が動いていて、エンドポイント(/api/mcp)が応答していることを確認。
- MCP Inspector を http://localhost:3000/api/mcp またはあなたのトンネルに接続。
- ハンドシェイクを確認――ツール一覧が表示されるはず。
- GPT が呼ぶはずのツール(例: search_gifts)を、似た引数で手動実行。
ここで既に落ちるなら、MCP を直してください。スキーマ、ビジネスロジック、ネットワーク呼び出し。何が壊れているかを掴むのに、ログと traceId を活用します。
手順 2(プロトコル/Dev Mode レイヤー): Dev Mode と URL を確認
Inspector では完璧なのに、ChatGPT が依然として App を見つけられない、または接続問題を訴える場合:
- あなたの App の Dev Mode 設定を開く。
- MCP 用に指定した URL を確認。
- それが実際にサーバー/トンネルでリッスンしている URL と一致するかを照合(サーバーが必要とするなら末尾の /mcp を忘れずに)。
多くの場合、問題はまさにここにあります。
手順 3(UI レイヤー): DevTools でウィジェットを確認
ChatGPT がツール呼び出しに成功している(MCP のログで分かる)のに、ウィジェットの挙動がおかしいなら:
- ChatGPT のページでブラウザの DevTools を開く。
- Console タブであなたのウィジェットの iframe コンテキストを選択。
- JS エラーを確認。
- Network タブで次を確認:
- ウィジェットの JS バンドルが 404/500 なく読み込まれている;
- fetch/window.openai.fetch による追加リクエストが意味のある応答を返している。
同時に DebugBanner にも注目。表示されないなら、React ツリーに到達していません。
手順 4: バグ再現に Dev Mode を活用
同僚/ユーザーからバグレポートを受けたら、壊れたときの正確なプロンプトを残すようにしましょう。Dev Mode なら素早く再現できます。
- npm run dev を起動し、トンネルを立ち上げる。
- Dev Mode で App を選ぶ。
- 問題のプロンプトを貼り付ける。
- 同時に:
- MCP にどんな JSON リクエストが来ているかログで確認;
- 必要なら Inspector で同じ引数の tools/call を再現。
こうして「たまに動かない」を再現可能なシナリオに変えられます。
8. 快適なデバッグのための小さなコード工夫
仕上げに、GiftGenius にいくつか便利な断片を足しておきましょう。
環境設定とログレベル
サーバー設定のどこかで、MCP のエンドポイントとログレベルを明示しておくと便利です。
// src/config.ts
export const config = {
mcpEndpoint:
process.env.NODE_ENV === "development"
? "http://localhost:3000/api/mcp" // トンネルがこれを覆う
: "https://api.giftgenius.com/api/mcp",
logLevel: process.env.NODE_ENV === "development" ? "DEBUG" : "ERROR",
};
そして logToolEvent で logLevel を考慮すれば、本番で不要なスパムを出さずに済みます。
MCP の構造化エラーのロギング
ツールの処理では、想定内のエラーは捕捉して分かりやすいメッセージを返し、何でもかんでも throw しないようにしましょう。
export async function searchGiftsTool(args: { q: string }) {
const traceId = crypto.randomUUID();
logToolEvent("start", { tool: "search_gifts", traceId, args });
try {
// ... 通常のコード ...
return { content: [{ type: "text", text: "ギフトを3件見つけました" }] };
} catch (err) {
logToolEvent("error", { tool: "search_gifts", traceId, error: String(err) });
return {
content: [{ type: "text", text: "ギフトの検索でエラーが発生しました。後でもう一度お試しください。" }],
isError: true,
};
}
}
こうして結果に isError を付ければ、ChatGPT は問題を適切にユーザーへ伝えられ、あなたはログで何が起きたかを把握できます。
9. ローカルデバッグでよくあるミス(ChatGPT App)
ミス No.1: サーバーとインスペクタではなく「GPT 越し」にデバッグする。
モデルの返答だけを眺めて、どこにバグがあるか当てにいきたくなるものです。しかしモデルは最上位レイヤー。MCP サーバーが(Inspector で手動実行して)単体で動かないなら、GPT に奇跡は起きません。まずは MCP を安定動作させ、その後に ChatGPT をつなぎましょう。
ミス No.2: ログを見ない、または全部を垂れ流す。
ログがないと完全に盲目になります。どのツールがどんな引数で呼ばれ、どう終わったか分かりません。逆に過剰なログは、バラバラの行が並ぶだけの「マトリックス」化を招きます。tool、匿名化した args、traceId、status、実行時間を持つ、コンパクトで構造化されたログが最適です。
ミス No.3: 機微情報をログに残す。
トークン、フルの e‑mail やカード番号のロギングは、セキュリティ的にも OpenAI のポリシー的にも悪手です。デバッグに本当に必要な情報だけを残し、個人情報はマスクするか書かないようにしましょう。
ミス No.4: 何でも Dev Mode のせいにする。
「OpenAI が何か壊したせいだ」とされがちですが、実際にはトンネル再起動後に URL を更新し忘れた、あるいはパスを間違えた(/ にしてしまい、/mcp を付け忘れた)といった問題が多いです。サポートに連絡する前に、Dev Mode の設定でエンドポイントが実サーバーのアドレスと一致しているかを確認しましょう。
ミス No.5: DevTools とウィジェットのエラーを無視する。
ウィジェットが空/グレーなのは、たいていクライアント側の JavaScript エラーです。MCP のログだけを見て、ChatGPT 側の DevTools を開かないのは半分しか見ていないのと同じ。F12 で Console/Network を確認する習慣は大きな時間節約になります。
ミス No.6: 魔法の遅延で「修理」しようとする。
ときに setTimeout や Thread.sleep 的な遅延を入れて「読み込みが間に合うように」したくなります。しかし MCP/Next/React の世界ではほぼ誤治療です。問題はたいていスキーマ、誤ったエンドポイント、コードエラーであって、「サーバーが間に合っていない」わけではありません。遅延で埋めるより、どこで切れているか(Inspector → Dev Mode → ウィジェット)を理解しましょう。
ミス No.7: ローカルでの健全性確認なしに Vercel へデプロイ。
「早く本番へ」という気持ちは分かりますが、壊れた MCP を Vercel に移すのは、ローカルと本番の二重苦を作る最良の方法です。本モジュールでは意図的にこう求めます。まず MCP Jam/Inspector で OK、次に Dev Mode で基本シナリオが動くことを確認し、それからデプロイです。
GO TO FULL VERSION