CodeGym /コース /ChatGPT Apps /MCP の検査とデバッグ:MCP Jam、Inspector、ログ

MCP の検査とデバッグ:MCP Jam、Inspector、ログ

ChatGPT Apps
レベル 6 , レッスン 4
使用可能

1. なぜ MCP‑インスペクターが必要なのか

フロントエンドをデバッグしているのに DevTools を開くのを禁じられているところを想像してください。MCP‑インスペクターがないと、ちょうどそんな生活になります。 MCP プロトコルは ChatGPT と Apps SDK の「裏側」で動いています。チャットの返答だけを見て「なぜ自分のツールを認識してくれないのだろう?」と考えるだけでは、実質的に手探りで撃っているのと同じです。

MCP Inspector(公式)や MCP Jam のようなインスペクターは、開発者向けの MCP クライアントです。できることは次のとおりです。

  • ChatGPT と同じ方法であなたの MCP サーバーに接続する;
  • handshake / capabilities を実行する;
  • tools/resources/prompts の一覧を取得する;
  • 任意の引数で任意の tool を手動実行する;
  • 生の JSON メッセージ(requests / replies / errors)を表示する。

要するに「MCP 版 Postman(ただし賢い)」です。通常の REST クライアントと違って、インスペクターは MCP の流儀を理解しています。 tools/listtools/call を理解し、引数スキーマを表示でき、 保護されたサーバー向けに OAuth フローをサポートしていることもあります。

インスペクターがないと、次のようなデバッグになります。ChatGPT を起動して App を呼び出し、"Error talking to app" が出たり、 そもそもツールが呼ばれなかったりして、「モデルがツールを呼びたくなかったのか? MCP が落ちているのか? JSON のエラーか?」と当てずっぽうになります。 インスペクターがあれば、層ごとに切り分けできます。まずは MCP サーバーとインスペクターの 1 対 1、次に ChatGPT ↔ MCP の組み合わせ、という順番です。

2. インスペクターのミニレビュー:MCP Inspector、Jam など

実務では、MCP 用インスペクターを主に 2 種類使うことになります。

第一に、Model Context Protocol のリポジトリにある 公式 MCP Inspectorです。これは Web アプリ (多くの場合 React の SPA)で、ローカルで立ち上げるか npx/Docker 経由で起動し、 HTTP/SSE で MCP サーバーに接続できます。

第二に、MCP Jam 系のインスペクターがあり、OAuth 周りの利便性が追加されていることが多いです。 彼らは .well-known/oauth-protected-resource を自動で読み、 そこから authorization_endpointtoken_endpoint を取得し、PKCE フローを通して 認可済みの状態で MCP にアクセスできます。

MCP Jam は MCP Inspector をベースに開発されています。MCP Inspector が 最低限の デバッグ機能を実装しているのに対して、MCP Jam は日々の MCP 開発で必要なものを一通り実装しています。 個人的には、最初から MCP Jam を使うことをおすすめします。後から学び直す必要がありません。

この講座の文脈での違いは次のとおりです。

  • ベースの Inspector は、最も単純な非保護の MCP サーバーに対しても常に必要;
  • MCP Jam(または同等のもの)は、認証・認可モジュールに進んだ段階で特に有用。

ただし発想は 1 つです。これは ChatGPT が「黙って」やっていることを見やすく表示してくれる、普通の MCP クライアントです。

3. MCP インスペクターでの標準的な作業フロー

典型的なシナリオを追ってみましょう。あなたは MCP サーバーに新しい tool を実装し、本当に動作するか確認したいとします。

前回の講義で最小の MCP サーバーを立ち上げました。ここに体系的な検証手順を加え、次の流れで 「サーバー → インスペクター → JSON ロジック」を一周させます。

手順 1 — MCP サーバーを起動する

前回と同様に行います。例えばスクリプト npm run mcp-dev があるとします。

# MCP サーバーの起動例
npm run mcp-dev
# 内部では例えば: ts-node src/mcp-server.ts

サーバーが選んだトランスポートをリッスンしていることが重要です。本講座では通常、HTTP エンドポイント /mcp を 任意のポートで公開します。例:http://localhost:4001/mcp

手順 2 — MCP Jam を起動する

2 つ目のターミナル:

# MCP Jam を起動する一例
npx @mcpjam/inspector@latest
# 必要に応じて --port 4002 などを付与

その後、インスペクターはブラウザで開き、たいてい http://localhost:6274 などのポートで表示されます。

起動時の画面で MCP サーバーの URL を求められるので、次を入力します。

http://localhost:4001/mcp

ngrok などでトンネルしている場合は、トンネルの URL を入力します。

手順 3 — handshake / capabilities

MCP Jam が接続されると、ChatGPT がやるのと同じ処理を自動で実行します。

  1. 初期化リクエスト(initialize)をクライアント情報とともに送信する。
  2. サーバーからプロトコルバージョンと capabilities を受け取る。
  3. capabilities に基づいて、tools、resources、prompts などをサーバーがサポートしているか判断する。

UI では次のように表示されることが多いです。

Connected
Protocol: mcp/2025-06-18
Capabilities:
- tools: list, call
- resources: list, read
- prompts: list, get

この段階で接続できない(connection refused、CORS、500 など)場合は、すぐにエラーが見えます。つまり問題は モデルではないし ChatGPT でもなく、あなたのサーバー側やネットワークにあると切り分けられます。

手順 4 — ディスカバリー:tools/resources/prompts を確認する

ハンドシェイクが成功すると、インスペクターは通常 tools/listresources/listprompts/list などを自動で呼び、サイドバーを埋めます。次のような情報が見えます。

  • ツール一覧(説明と入力引数の JSON Schema 付き);
  • コレクション/パス単位でグルーピングされたリソース一覧;
  • 用意済みのプロンプトとその概要。

さきほど追加したばかりの tool が一覧に出てこない場合、サーバーでの登録が誤っているか、 更新されたコードでサーバーが起動していない可能性があります。ここで気づけば、「なぜ ChatGPT がツールを呼んでくれないのだろう」と 当てずっぽうに悩まずに済みます。

4. MCP Jam での手動 tool 呼び出し

MCP Jam の最も便利な機能は、ツールを手動で呼び出せることです。これは tools/call のための個人用 UI です。

ツールを選び、引数を入力する

前のモジュールで suggest_gifts という tool を実装したとします。

// src/mcp/tools/suggestGifts.ts のどこか
export const suggestGiftsTool = {
  name: "suggest_gifts",
  description: "年齢、予算、興味に基づいてギフトアイデアを提案する",
  inputSchema: {
    type: "object",
    properties: {
      age: { type: "number" },
      budget: { type: "number" },
      interests: {
        type: "array",
        items: { type: "string" }
      }
    },
    required: ["age", "budget"]
  },
  // handler は別で定義する
};

MCP Jam で suggest_gifts をクリックすると、inputSchema に基づいたフォームが右側に生成されます。 そこで次のように入力します。

{
  "age": 30,
  "budget": 100,
  "interests": ["ゲーム", "本"]
}

そして「Call」などのボタンを押します。

インスペクターは MCP リクエスト tools/call を送り、すぐに次が表示されます。

  • 送信されたリクエストの生 JSON;
  • レスポンスの生 JSON(result または error);
  • 結果の見やすいプレビュー(ある場合)。

JSON ログを読む

通常、インスペクターは次のように表示します。

// Request
{
  "id": "1",
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": {
    "name": "suggest_gifts",
    "arguments": {
      "age": 30,
      "budget": 100,
      "interests": ["ゲーム", "本"]
    }
  }
}
// Reply
{
  "id": "1",
  "jsonrpc": "2.0",
  "result": {
    "content": [
      {
        "type": "text",
        "text": "1) ボードゲーム … 2) 書店のギフト券 …"
      }
    ]
  }
}

handler が例外で落ちた場合は、JSON‑RPC 形式の error が見えます。

{
  "id": "1",
  "jsonrpc": "2.0",
  "error": {
    "code": -32603,
    "message": "Internal error",
    "data": "TypeError: Cannot read properties of undefined ..."
  }
}

重要なのは、ここで見ているのがプロトコル層だということです。Apps SDK/ChatGPT が期待する形式と違うレスポンスになっていれば、 「GPT のバグだ」と言い出す前に気づけます。

5. リソースとプロンプトのデバッグ

MCP ができるのはツールだけではありません。resourcesprompts もあります。

インスペクター経由で次ができます。

  • リソース一覧(resources/list)を開き、メタデータを確認する;
  • 特定のリソースを読む(resources/read)ことで、返されるデータが正しいか検証する;
  • (実装していれば)リソース検索を実行する;
  • 用意済みのプロンプトとその本文を確認する。

例えば、リソース gift_catalog があるとします。

// リソース登録の擬似コード
registerResource({
  uri: "resource://giftgenius/catalog",
  name: "ギフトカタログ",
  mimeType: "application/json",
  handler: async () => {
    return JSON.stringify(giftCatalogData);
  }
});

インスペクターでこのリソースが見えるので、クリックするだけで JSON を確認できます。JSON が不正だったり、 MIME タイプが変だったりする場合、ChatGPT がそれを読んだりウィジェットに組み込んだりする前に検知できます。

6. MCP サーバーのログ:何を、どこに、どう記録するか

MCP Jam は優れていますが、それだけでは不十分です。必要なのは MCP サーバー自身のログです。これがないと本番運用は運任せになります。

何を記録するか

最低限のおすすめ:

  • すべての受信 MCP メッセージ(request/notification):
    • 時刻;
    • メソッド(tools/calltools/list など);
    • ツール名(あれば);
    • 機微情報を除いたマスク/短縮済みの引数;
  • すべての送信レスポンス:
    • ステータス(成功/エラー);
    • 処理時間;
    • 結果の短縮版、もしくは少なくとも型;
  • 技術的なエラー:
    • JSON パース;
    • handler 内の予期しない例外。

その上で、PII や秘密情報を丸ごとログに書かないことが非常に重要です。トークン、パスワード、機密クエリの全文などは不可です。 本番運用のログ推奨では、PII を短縮・マスクした形で記録するよう明示されていることが多いです。

ログの出力先:stdout / stderr

MCP には重要な要件があります。JSON メッセージは「正しいチャンネル」に、デバッグログは別チャンネルに出すことです。 例えば stdout/stderr 上のトランスポートを使う場合は次のとおりです。

  • JSON‑RPC メッセージは stdout に出す;
  • console.logconsole.error などは stderr に流す。

JSON とテキストログを同じストリームに混在させると、クライアント(MCP Jam や ChatGPT)はメッセージをパースできません。 JSON の途中に突然 Server started at http://localhost:4001 のような文字列が紛れ込むからです。 これは MCP サーバーで頻出するミスの 1 つです。

HTTP の場合は少し楽ですが、原則は同じです。HTTP レスポンスは 純粋な JSON である必要があり、 ログはコンソールやファイルなど、レスポンスボディ以外に出します。

TypeScript 製 MCP サーバー向けの簡易ロガー

仮の MCP サーバーに小さなロガーを追加してみましょう。

// src/logger.ts
export function logRequest(method: string, details: unknown) {
  console.error(
    JSON.stringify({
      level: "info",
      type: "request",
      method,
      details,
      ts: new Date().toISOString(),
    })
  );
}

export function logError(method: string, error: unknown) {
  console.error(
    JSON.stringify({
      level: "error",
      type: "error",
      method,
      error: String(error),
      ts: new Date().toISOString(),
    })
  );
}

そして tools のハンドラに組み込みます。

// src/mcp-server.ts(抜粋)
server.setRequestHandler("tools/call", async (req) => {
  logRequest("tools/call", {
    name: req.params?.name,
    // ここでは payload 全体ではなく安全なフィールドだけを入れるのが望ましい
  });

  try {
    const result = await handleToolCall(req);
    return result;
  } catch (e) {
    logError("tools/call", e);
    throw e;
  }
});

こうすると、コンソールに構造化された JSON ログが出力され、ts や追加の requestId で容易に突き合わせできます。

7. 組み合わせ:MCP Jam + ログ

MCP の正しいデバッグ戦略は、ほぼ次のようになります。

  1. インスペクターで問題を再現する。例えば tools/list が空、tools/call が落ちる、JSON レスポンスが不自然、など。
  2. 同時に MCP サーバーのログを見る。起動時に何を書いているか、各メッセージでどんなエラーが出ているか、スタックトレースがあるか。
  3. ログにある idmethodts を、インスペクター側で見える情報と突き合わせる。

例えば、インスペクターでは次のように見えます。

{
  "error": {
    "code": -32603,
    "message": "Internal error"
  }
}

同時にログではこうです。

{
  "level": "error",
  "type": "error",
  "method": "tools/call",
  "error": "TypeError: Cannot read properties of undefined (reading 'age')",
  "ts": "2025-11-21T10:15:12.345Z"
}

診断は明確です。どこかの handler で age を期待しているのに、スキーマ/引数がそれと一致していません。

8. ミニチェックリスト:MCP サーバーは App 連携の準備ができているか

MCP サーバーを実際の ChatGPT App に接続する前に、インスペクターで簡単なチェックリストを確認すると便利です。

第一に、handshake と capabilities はエラーなく通るべきです。MCP Jam が、サーバーが必要なエンティティ(少なくとも tools と、必要に応じて resources / prompts)をサポートしていると表示する必要があります。

第二に、インスペクターに出る tools/resources/prompts の一覧は、自分が実装したつもりのツール/リソース/プロンプトのセットと一致しているべきです。 name のタイプミスや登録漏れなどは、ここで即座に見つかります。

第三に、妥当な引数でのツール呼び出しは、安定して正しい result を返すべきです。 本番で実際に想定しているいくつかの代表ケースを試すのが望ましいです。

第四に、無効な引数での呼び出しは、読みやすい error(JSON‑RPC スタイル)を返すべきであり、 500 で落ちるべきではありません。例えば必須パラメータが欠けているなら、ChatGPT がユーザーにわかりやすいメッセージへ変換できる構造化エラーを返すのが望ましいです。

第五に、その間のサーバーログがスタックトレースでコンソールを埋め尽くすようではいけません。エラーは構造化され、 機微なデータは丁寧にフィルタリングされているべきです。

これらがインスペクターで満たされていれば、ずっと安心して MCP サーバーを Apps SDK に接続し、Dev Mode のウィジェットで遊べます。

9. MCP サーバーの典型的な不具合と、インスペクターでの見つけ方

ここからが本題です。よく壊れる点と、その見え方を確認します。

構成と接続

「サーバーが動かない」と思っていても、実は所定のポートやエンドポイントをリッスンしていないだけ、ということがあります。 インスペクターはその場合 connection refused と正直に出したり、接続自体ができなかったりします。 よくある原因は、URL の誤り(例えば /mcp とすべきところを /api/mcp にしている)、 別プロセスがポートを占有している、トンネルが立ち上がっていない、CORS によるブロック、などです。

不正な JSON/ログとプロトコルの混在

最も痛いパターンの 1 つが、console.log("Server started") を stdout に出しながら、 その同じストリームで JSON‑RPC メッセージを流してしまうケースです。クライアントは純粋な JSON を期待しているのに、 テキスト+JSON を受け取ってパースに失敗し、形式エラーになります。

解決策は単純です。プロトコルのストリーム(stdout や HTTP レスポンスボディ)に出すものと、 ログ(stderr や専用ログファイル)に出すものを厳密に分けることです。

スキーマと実装の不一致

これもありがちなミスです。inputSchema ではある定義をしているのに、コード側では別の形を期待しているケースです。 例えばスキーマでは age は数値、interests は任意の文字列配列なのに、 コードで arguments.interests.toLowerCase() を呼んでしまう、といった具合です。モデル(とインスペクター)は正直に interestsnull にしたり、そもそもプロパティを送らなかったりします——そこで落ちます。

インスペクターを使えば、tools/call に実際に送られている JSON を目視でき、 コードと突き合わせられます。

tools/resources の名前の不一致

capabilities/tools/list では tool を suggest_gifts_v2 として公開しているのに、 Apps のマニフェストやウィジェットでは suggest_gifts を期待している、という状況だと、 「ツールが見つからない」はプロジェクトの終わりまでつきまといます。インスペクターなら、tools の一覧と name フィールドで一目瞭然です。GPT の気持ちを推測する必要はありません。

遅い/ハングする tools

インスペクターでのツール呼び出しが 30 秒もかかり、その後タイムアウトで落ちるなら、ChatGPT がうまく対処してくれると期待すべきではありません。 MCP インスペクターを使えば、どの段階で遅いのか(ネットワーク、DB、外部 API)を把握できます。 ログに各リクエストの開始・終了時刻を入れておくと、外れ値をすぐに見つけられます。

10. インスペクションと MCP デバッグでのよくある誤り

誤り1:MCP を ChatGPT だけでデバッグしようとする。
多くの開発者は、まず MCP を App に接続し、「うまく動かない」と見てからプロンプトやツールの説明、 ひいてはモデルのバージョンまで変え始めます。その間、MCP サーバーがそもそも起動していなかったり、tools/list が空だったりします。 必ずインスペクターから始めましょう。そこで問題があるなら、モデルのせいではありません。

誤り2:JSON‑RPC とログを同じストリームに混在させる。
MCP クライアントが純粋な JSON を期待しているのに、stdout にデバッグ文字列を書いてしまうと、結果は明白です——パースが壊れ、 Inspector は奇妙なエラーを示します。ログは別(stderr、ファイル、外部ログ基盤など)に、 プロトコルメッセージは厳密に専用のチャンネルに出しましょう。

誤り3:capabilities とツール一覧を確認しない。
ツールが「消える」原因の多くは、登録忘れや対応する capability の有効化忘れです。 インスペクターで capabilitiestools/list を確認しないと、 犯人はモデルだと長い間勘違いすることになります。実際には登録コードの問題です。

誤り4:スキーマエラーと JSON の不一致を無視する。
inputSchema と実際の JSON が食い違っていると、モデルやインスペクターの挙動が不自然になるのは当然です。 生の JSON メッセージをインスペクターで見ず、スキーマを検証しないと、これらのエラーは思わぬ場面で顕在化します。

誤り5:PII やトークンを含め、何でもかんでもログに書く。
デバッグの勢いで、個人情報や秘密を含む request body 全文をログに出してしまいがちです。 本番では時限爆弾になります。情報漏えい、コンプライアンス違反など。 診断に本当に必要なものだけを、短縮/匿名化してログに残しましょう。

誤り6:最小ケースで問題を再現しない。
バグが ChatGPT 経由の複雑な対話で現れ、そのままの形でデバッグしようとすることがあります。 同じシナリオをインスペクターで 1~2 個の MCP リクエストに還元して再現し、プロンプトや会話履歴、 モデルの「気分」の影響を切り離す方がはるかに効率的です。

1
アンケート/クイズ
MCPプロトコル、レベル 6、レッスン 4
使用不可
MCPプロトコル
MCP: プロトコル、サーバー、インスペクタ
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION