さくらおかひろのり
個人開発SaaSにMCP+OAuth 2.1を実装した記録。本体は薄く、認可が本番だった
2026年08月07日
見出しはありません
要約を生成中...
自作のSaaS「L-Proxy」(LINE公式アカウントのWebhook転送サービス)を、ChatGPTやClaudeから直接操作できるようにしました。いわゆるリモートMCPサーバー対応です。体験談は別の記事に書いたので、こちらでは技術的な中身と、実装で踏んだ地雷を記録しておきます。
実装はClaude CodeとCodexにほぼ任せて1日。ただし「何をどう作るか」の設計判断は全部自分でやる必要がありました。同じことをやる人のために、判断ポイントを先に共有します。
MCPサーバーというと専用プロセスを想像しますが、L-Proxyでは既存のNext.jsアプリ(ダッシュボード)にエンドポイントを1本追加しただけです。
POST /mcp — Streamable HTTP相当のJSON-RPC。initialize / ping / tools/list / tools/call を受ける中身は、管理画面用にすでに作ってあったAPI処理をツールとして写像しただけです。既に動いているWebサービスなら、MCP本体はとても薄い。インフラも増えません(デプロイ先は今までどおりVercel)。
先に管理APIを/api/ai/v1/*として整理してからMCPを被せる2段構成にしたのも、結果的に正解でした。ツール定義がAPIと1対1になるので、テストも認可チェックも共通化できます。
時間を食ったのはMCPではなく認可です。要件は「ユーザーにAPIキーを触らせない」。中継サービスの分際でキー管理の負担をユーザーに背負わせたくなかったので、OAuth 2.1を全部実装しました。
/.well-known/*)ユーザー体験としては「AIツールにURLを登録→ブラウザが開く→Googleログイン済みなら許可を1クリック」で終わりです。
1. Claude CodeがDCRのレスポンスで落ちる
Dynamic Client Registrationの応答に client_uri: null / logo_uri: null を入れると、Claude Code側のSDKがschema validationで失敗します。未指定時は client_uri にissuer URL、logo_uri に空文字を返すよう正規化して回避しました。
2. Prismaの$transactionが配列形式だと壊れる
Vercel+Cloud SQL Connectorの構成で遅延初期化のPrismaラッパーを使っていると、prisma.$transaction([ ... ]) に渡したmodel callがPrisma Clientのpromiseではなく普通のPromiseになり、トークン交換が500になります。複数書き込みは prisma.$transaction(async (tx) => { ... }) のコールバック形式にする。
3. 契約ステータスの扱い漏れ
Stripeの PAST_DUE(支払いリトライ中)はサービス有効として扱うべきなのに、一部の判定で無効扱いになっていました。MCPを足すと契約状態を見る箇所が増えるので、ステータス判定は1か所に寄せておくべきでした。
4. Slackからコピーした手順のURLに山括弧が混ざる
Slackのリンク形式 <https://...> をそのままターミナルに貼ると登録に失敗します。手順書を配る側は、URLをコードブロックにしておくのが親切です。
L-Proxyが扱うのはLINEの通信です。設計で一番時間を使ったのは、AIに見せない・させないものの線引きでした。
origin までしか返さない(パスやクエリにトークンが入りがちなため)ポイントは、これをプロンプトではなくAPIのレスポンス設計で保証することです。モデルがどう振る舞っても、窓口に出ていない情報は漏れません。「AIから操作できるサービス」の信頼性は、モデルの行儀ではなくサーバー側の設計で担保するものだと思います。
L-Proxyはこちらから。14日間の無料トライアルで、AI接続まで試せます。
要約
コメント
まだコメントはありません。