メインコンテンツへスキップ
三田工場 技術サイト
WebアプリをAIから操作できるようにした話(第4回)— Claude Codeから繋げるときにハマった2つの落とし穴

WebアプリをAIから操作できるようにした話(第4回)— Claude Codeから繋げるときにハマった2つの落とし穴

Debugging11分で読めます

このシリーズ: 全4回

  1. 第1回: MCPサーバー化の動機とアーキテクチャ
  2. 第2回: MCP Streamable HTTPの実装
  3. 第3回: Bearer Token認証とDynamoDBトークン管理
  4. 第4回: Claude Codeから繋げるときの落とし穴 ← 今ここ

やりたかったこと

第2・3回で実装した MCP サーバーを Claude Code から使える状態 にする。

具体的には:

  • Claude Code のセッション起動時に team-task-scheduler のツール43個が自動ロードされる
  • /task-flow 今週のタスクを教えて のようなコマンドで自然言語操作できる

こんな人向け

  • MCPサーバーの実装は完成したのに Claude Code でツールが認識されない
  • ~/.claude/settings.jsonmcpServers を書いたが動かない
  • Claude Code の /mcp で接続失敗になっている

❌ 落とし穴①:GETエンドポイント未実装で接続自体が失敗する

何が起きたか

MCPサーバーの curl テストは全て成功していました:

bash
# initialize → 200 OK
curl -X POST https://your-app.cloudfront.net/api/mcp \
  -H "Authorization: Bearer <token>" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize",...}'
# → {"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2024-11-05",...}}

# tools/list → 43ツールが返る
curl -X POST https://your-app.cloudfront.net/api/mcp \
  -H "Authorization: Bearer <token>" \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'
# → {"result":{"tools":[...43件...]}}

しかし Claude Code を再起動しても /mcp でツールが認識されず、ToolSearch でも team-task-scheduler のツールが見つかりませんでした。

なぜこうなるのか

Claude Code の MCP HTTP クライアントは セッション開始時に GET /api/mcp リクエストを送って SSE チャネルを確立しようとします

当初の実装では POSTOPTIONS のみを実装していたため、GET に対して 405 Method Not Allowed が返っていました。

bash
curl -X GET https://your-app.cloudfront.net/api/mcp \
  -H "Authorization: Bearer <token>" \
  -H "Accept: text/event-stream"
# → HTTP 405 Method Not Allowed ← これが原因

Claude Code は GET が失敗した時点でそのMCPサーバーを「接続不可」と判断し、POST による tool 呼び出しも試みません。

✅ 解決した実装

GET ハンドラーを追加し、空の SSE ストリームを返すようにしました:

typescript
// src/app/api/mcp/route.ts に追加
export async function GET(request: NextRequest): Promise<NextResponse> {
  const auth = await authenticateMcpRequest(request.headers.get('authorization'));
  if (!auth) return unauthorized();

  // Lambda はステートレスなので長時間接続できない
  // 即座に閉じることで「SSEは使えないが接続は確立できた」と伝える
  const stream = new ReadableStream({
    start(controller) {
      controller.enqueue(new TextEncoder().encode(': connected\n\n'));
      controller.close();
    },
  });

  return new NextResponse(stream, {
    status: 200,
    headers: {
      'Content-Type': 'text/event-stream',
      'Cache-Control': 'no-cache',
      'Connection': 'keep-alive',
      'Access-Control-Allow-Origin': '*',
    },
  });
}

OPTIONS も GET を含めるよう更新:

typescript
export async function OPTIONS(): Promise<NextResponse> {
  return new NextResponse(null, {
    status: 204,
    headers: {
      'Access-Control-Allow-Origin': '*',
      'Access-Control-Allow-Methods': 'GET, POST, OPTIONS', // GET を追加
      'Access-Control-Allow-Headers': 'Content-Type, Authorization',
    },
  });
}

なぜこれで解決するのか

Claude Code は GET で 200 が返ると「このサーバーはSSEをサポートしている(が即座に閉じた)」と判断します。ストリームが即座に閉じられても、以降の POST リクエストは問題なく動作します。


❌ 落とし穴②:settings.jsonmcpServers は Claude Code が無視する

何が起きたか

落とし穴①を修正してデプロイし、Claude Code を再起動しました。しかし 依然としてツールが認識されません

確認のため ToolSearch を実行すると:

text
No matching deferred tools found

接続確認のために claude mcp list を実行すると:

text
plugin:context7:context7: npx -y @upstash/context7-mcp - ✓ Connected
playwright: npx @playwright/mcp@latest - ✓ Connected
# ← team-task-scheduler がリストにない!

team-task-scheduler存在すら認識されていませんでした

なぜこうなるのか

設定を書いたファイルは ~/.claude/settings.json でした:

json
// ~/.claude/settings.json
{
  "mcpServers": {
    "team-task-scheduler": {
      "type": "http",
      "url": "https://your-app.cloudfront.net/api/mcp",
      "headers": {
        "Authorization": "Bearer <token>"
      }
    }
  }
}

しかし Claude Code が MCP サーバー設定を読み込むのは ~/.claude.json(ホームディレクトリ直下の別ファイル) です。

~/.claude/settings.json は Claude Code の動作設定(言語、権限、モデル設定など)を書くファイルで、MCP サーバーの登録には使われません。

この2つのファイルを混同するのが「あるある」のミスです:

ファイル 用途
~/.claude/settings.json Claude Code の動作設定(言語、権限、環境変数など)
~/.claude.json MCPサーバーの登録、プロジェクトごとの設定

✅ 解決した方法

claude mcp add コマンドで正しいファイルに登録します:

bash
# ユーザースコープで登録(全プロジェクトで使えるようになる)
claude mcp add \
  --transport http \
  --scope user \
  team-task-scheduler "https://your-app.cloudfront.net/api/mcp" \
  --header "Authorization: Bearer <your-token>"

実行後の確認:

bash
claude mcp list
# → team-task-scheduler: https://... (HTTP) - ✓ Connected

コマンドの引数順序に注意が必要です。--header は URL の後に指定します

bash
# ❌ エラーになる(--header が name より前)
claude mcp add --transport http --scope user --header "..." team-task-scheduler "https://..."

# ✅ 正しい
claude mcp add --transport http --scope user team-task-scheduler "https://..." --header "..."

スコープの選択

スコープ コマンド 保存先 用途
user(全プロジェクト共通) --scope user ~/.claude.json 個人の共通ツール(今回はこれ)
local(プロジェクト固有) (デフォルト) ~/.claude.json(プロジェクトパス配下) プロジェクト専用ツール
project(チーム共有) --scope project .mcp.json チームで共有するツール

比較まとめ

❌ 問題 ✅ 解決後
GETエンドポイント 実装なし → 405 → 接続失敗 空SSEストリームを返す → 200
設定ファイル ~/.claude/settings.jsonmcpServers(無視される) claude mcp add --scope user~/.claude.json に書き込まれる
接続状態 /mcp でツールが認識されない 43ツールがロードされる

接続できたら:/task-flow カスタムコマンドを作る

ツールが使えるようになったら、自然言語で操作するカスタムコマンドを作るとさらに便利です。

~/.claude/commands/task-flow.md を作成:

markdown
Team Task Scheduler の MCP ツールを使って、以下のリクエストを処理してください:

$ARGUMENTS

## 使えるMCPツール

- **チーム・ユーザー**: `team_list`, `team_get`, `user_list`
- **プロジェクト**: `project_list`, `project_get`, `project_create`, `project_update`, `project_delete`
- **タスク**: `task_list`, `task_get`, `task_create`, `task_update`, `task_delete`
(省略)

## ガイドライン

- チームIDが不明な場合は team_list → project_list の順で確認する
- 複数の操作が必要な場合は順番に実行し、各ステップを簡潔に報告する

これで Claude Code から以下のように使えます:

text
/task-flow 私が担当しているアクティブなプロジェクトを教えて
/task-flow トレサビリティシステムの未完了タスクを一覧で表示して
/task-flow プロジェクトAの進捗を70%に更新して

Claude Code は $ARGUMENTS の内容に応じて、適切な MCP ツールを自動的に選択して実行してくれます。

まとめ

落とし穴①: GET エンドポイント未実装

  • Claude Code はセッション開始時に GET でSSE接続を試みる
  • 405 が返ると「接続不可」と判断しツールが一切ロードされない
  • Lambda 環境では「即座に閉じる空SSEストリーム」で対処

落とし穴②: 設定ファイルの間違い

  • ~/.claude/settings.jsonmcpServers は Claude Code が読み込まない
  • claude mcp add --scope user を使うと正しく ~/.claude.json に登録される

この2点を押さえておけば、他のアプリを MCP サーバーとして公開する際にも同じ問題を踏まずに済みます。

バイブコーディングで実装する

text
作成済みの MCP HTTP サーバーを Claude Code に接続する手順を実施してください。

【確認手順】
1. curl で GET エンドポイントをテスト:
   curl -X GET <MCP_URL> -H "Authorization: Bearer <TOKEN>" -H "Accept: text/event-stream"
   → 200 が返らなければ GET ハンドラーを実装する

2. GET ハンドラーの実装(Lambda/ステートレス環境の場合):
   Content-Type: text/event-stream で ': connected\n\n' を送信して即閉じる
   OPTIONS の Allow-Methods に GET を追加する

3. Claude Code への登録:
   claude mcp add --transport http --scope user <name> <url> --header "Authorization: Bearer <token>"
   ※ --header は URL の後に指定する
   ※ --scope user で全プロジェクトで使えるようになる

4. 接続確認:
   claude mcp list
   → <name>: <url> (HTTP) - ✓ Connected が表示されれば成功

AIに指示するときのポイント

  • GET エンドポイントを実装するよう明示する: 指示しないと POST のみの実装になり接続できなくなる。「Claude Code は GET リクエストで SSE 接続を確立しようとする」と説明するとAIが理解しやすい
  • 設定ファイルのパスを明示する: ~/.claude/settings.jsonmcpServers は機能しないことをプロンプトに書く。「claude mcp add コマンドを使って ~/.claude.json に登録する」と明示する
  • スコープを指定する: --scope user を省略するとプロジェクトローカルになり、他のプロジェクトで使えなくなる

これで「WebアプリをMCPサーバーにしてAIから操作できるようにした話」シリーズは完結です。第1回から順に読むことで、設計から実装・接続まで一貫した理解が得られます。

関連記事