前回の続き)→MCP サーバー化

はじめに

前回は Claude Code Skill を整備し、ターミナルや VSCode 上の Claude Code からカレンダーに「打ち合わせを入れて」と話しかけるだけで予定が登録できるようになりました。

しかし Skill はローカル定義のため、Claude Code のセッション外では使えません。

  • ブラウザの claude.ai (Web)
  • iOS / Android の Claude アプリ
  • Claude Desktop

今回はこれらから操作できるようにすることを目標に、前回整備した REST API を Model Context Protocol(MCP) のエンドポイントでラップしました。


計画と現実のギャップ

当初のゴールは「Web・スマホ・デスクトップすべてから自然言語でカレンダーを操作できる」でした。しかし実際に試したところ、次の壁にぶつかりました。

  • claude.ai Web の「カスタムコネクタを追加」 — OAuth 専用で、静的 Bearer トークンを直接貼り付けることができない
  • iOS / Android の Claude アプリ — Node.js が動かないため mcp-remote が使えず、claude.ai 経由でしか接続できない。上記と同じ制約

結果として、今回の実装で接続できたのは Claude Desktop のみです。


MCP(Model Context Protocol)とは

MCP は Anthropic が策定した、AI モデルと外部システムをつなぐ標準プロトコルです。Claude Desktop では claude_desktop_config.json に MCP サーバーを登録することで、チャット画面からそのシステムをツールとして呼び出せます。

Skill が「Claude Code 専用の取扱説明書」だとすれば、MCP は「Claude クライアントが使える標準インターフェース」です。


アーキテクチャ

既存の Next.js アプリに /api/mcp/[transport] ルートを追加し、Vercel に同居させました。Claude Desktop からは mcp-remote がブリッジとなって HTTPS 経由で接続します。

Claude Desktop
        │
        ▼
npx mcp-remote(ブリッジ)
        │  HTTPS + Authorization: Bearer
        ▼
/api/mcp/[transport]          ← @vercel/mcp-adapter
        │
        ▼
mcpAuth.ts  (HMAC 検証 → {calendarId, role})
        │
        ▼
mcpTools.ts (Zod 定義 7 ツール)
        │
        ▼
src/lib/server/events.ts      ← サービス層(新設)
        │
        ▼
Supabase (service-role)

ポイントは サービス層の抽出 です。既存の REST Route Handler 内のロジックを src/lib/server/ 配下の純粋関数として切り出し、REST 経由でも MCP 経由でも同じ関数を呼ぶ構造にしました。curl から叩いても Claude Desktop から叩いても、完全に同じ挙動になります。


公開した MCP ツール(7つ)

tool name必要ロール用途
list_eventsuser / admin期間指定でイベント一覧取得
get_eventuser / admin単体取得
create_eventadminイベント登録
update_eventadmin部分更新
delete_eventadmin削除
list_labelsuser / adminラベル一覧
get_calendar_infouser / adminカレンダー情報確認

ツール引数に calendarId は含めていません。認証トークンから自動的に導出するため、利用者は意識する必要がありません。また、user ロールのトークンで接続した場合は書き込み系ツール(create / update / delete)がツール一覧から消え、誤操作を防げます。


認証の設計

新しい認証機構は作らず、前回から使っている HMAC Bearer トークン をそのまま流用しました。claude_desktop_config.json のヘッダオプションにトークンを記述するだけで完結します。


Claude Desktop への接続手順

claude_desktop_config.json に以下を追記します。

"wbcal": {
  "command": "npx",
  "args": [
    "-y",
    "mcp-remote",
    "https://<your-domain>/api/mcp/mcp",
    "--header",
    "Authorization:Bearer <WBCAL_TOKEN>"
  ]
}

Claude Desktop を再起動すると MCP ツールが有効になります。あとはチャットで「来週月曜 14:00 にチーム定例を登録して」と話しかけるだけです。


実装上の注意点

Edge ランタイムは不可

Supabase のサービスロールキーを使うため、runtime = "nodejs" を明示します。

export const runtime = "nodejs";
export const maxDuration = 60; // Vercel Hobby の上限

CORS は許可リスト方式で

/api/mcp/* のみを対象に Access-Control-Allow-Origin: https://claude.ai を返します。ワイルドカード * は使わず、next.config.tsheaders() で制御します。

ルートハンドラのコード

import { createMcpHandler } from "@vercel/mcp-adapter";
import { registerTools } from "@/lib/server/mcpTools";
import { resolveContextFromAuthHeader } from "@/lib/server/mcpAuth";

export const runtime = "nodejs";
export const maxDuration = 60;

const handler = createMcpHandler(
  async (server, { request }) => {
    const ctx = await resolveContextFromAuthHeader(
      request.headers.get("authorization")
    ); // 失敗すると adapter が 401 を返す
    registerTools(server, ctx);
  },
  { capabilities: { tools: {} } },
  { basePath: "/api/mcp", verboseLogs: false }
);

export { handler as GET, handler as POST, handler as DELETE };

前回(Skill)との使い分け

 Skill(前回)MCP サーバー(今回)
使える場所Claude Code(ターミナル / VSCode)Claude Desktop
実装場所リポジトリ内 SKILL.mdNext.js API Route (/api/mcp/)
認証.env.local から読むclaude_desktop_config.json に記述
呼び出し方curl をラップMCP プロトコル経由

両者は同じ REST API(→ 同じサービス層)を呼んでいるため、どちらから操作しても結果は同じです。Skill はそのまま残し、MCP を追加するだけで両立できます。


まとめ

今回の実装を経て、アプリの構造はこうなりました。

Web UI(ブラウザ)
    │
    ├── REST API (/api/events, /api/calendars, ...)
    │       │
    │       ├── Claude Code Skill(前回)
    │       │       → ターミナル / VSCode から自然言語で操作
    │       │
    │       └── MCP サーバー(今回)
    │               → Claude Desktop から自然言語で操作
    │
    └── Supabase(サービス層経由)

当初は Web やスマホからも操作できる想定でしたが、claude.ai のカスタムコネクタが OAuth 専用であることが判明し、今回は Claude Desktop に絞った対応となりました。

claude.ai Web / iOS / Android への正式対応は、MCP の OAuth 認可フロー実装が必要です。それは次回以降の課題です。


タイトルとURLをコピーしました