個人開発SaaSにMCP+OAuth 2.1を実装した記録。本体は薄く、認可が本番だった
2026/08/07 09:26 公開
自作のSaaS「L-Proxy」(LINE公式アカウントのWebhook転送サービス)を、ChatGPTやClaudeから直接操作できるようにしました。いわゆるリモートMCPサーバー対応です。体験談はnoteに書いたので、こちらでは技術的な中身と、実装で踏んだ地雷を記録しておきます。
実装はClaude CodeとCodexにほぼ任せて1日。ただし「何をどう作るか」の設計判断は全部自分でやる必要がありました。同じことをやる人のために、判断ポイントを先に共有します。
構成: 専用サーバーは立てない
MCPサーバーというと専用プロセスを想像しますが、L-Proxyでは既存のNext.jsアプリ(ダッシュボード)にエンドポイントを1本追加しただけです。
POST /mcp— Streamable HTTP相当のJSON-RPC。initialize/ping/tools/list/tools/callを受ける- ツールは14個。設定状況の要約(context)、LINE公式アカウントのCRUD、転送先のCRUD、マスク済みログ・転送失敗ログの取得
中身は、管理画面用にすでに作ってあったAPI処理をツールとして写像しただけです。既に動いているWebサービスなら、MCP本体はとても薄い。インフラも増えません(デプロイ先は今までどおりVercel)。
先に管理APIを/api/ai/v1/*として整理してからMCPを被せる2段構成にしたのも、結果的に正解でした。ツール定義がAPIと1対1になるので、テストも認可チェックも共通化できます。
本番はOAuth 2.1だった
時間を食ったのはMCPではなく認可です。要件は「ユーザーにAPIキーを触らせない」。中継サービスの分際でキー管理の負担をユーザーに背負わせたくなかったので、OAuth 2.1を全部実装しました。
- Protected Resource Metadata / Authorization Server Metadata(
/.well-known/*) - Dynamic Client Registration(AIクライアントが自動でclient登録してくる)
- PKCE S256必須・publicクライアントのみ
- アクセストークン・リフレッシュトークンはSHA-256ハッシュでDB保存(生値は保存しない)
- リフレッシュトークンは使用ごとにローテーション
- 認可画面の本人確認は既存のNextAuth(Googleログイン)セッションに相乗り
- ダッシュボードから接続をいつでも失効できる
ユーザー体験としては「AIツールにURLを登録→ブラウザが開く→Googleログイン済みなら許可を1クリック」で終わりです。
踏んだ地雷4つ
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をコードブロックにしておくのが親切です。
「AIにやらせないこと」を実装で保証する
L-Proxyが扱うのはLINEの通信です。設計で一番時間を使ったのは、AIに見せない・させないものの線引きでした。
- チャネルシークレットはwrite-only。登録時に受け取った後、どのAPIからも返さない
- Webhookの生ボディ(メッセージ本文)はツールの応答に含めない。マスク済みの種別・結果のみ。ログ自体も受信から24時間で自動削除
- 転送先URLは
originまでしか返さない(パスやクエリにトークンが入りがちなため) - 決済確定・LINE Developers側のWebhook URL差し替えはツール化しない(人間の工程として残す)
ポイントは、これをプロンプトではなくAPIのレスポンス設計で保証することです。モデルがどう振る舞っても、窓口に出ていない情報は漏れません。「AIから操作できるサービス」の信頼性は、モデルの行儀ではなくサーバー側の設計で担保するものだと思います。
まとめ: 薄いMCP、厚い認可、太い線引き
- MCP本体は薄い。既存APIがあるなら1日で載る(Claude Code・Codexに任せた場合)
- 認可(OAuth 2.1)が実装量の本体。DCR・PKCE・トークン管理・失効導線まで含めて設計する
- 一番大事なのは「AIにやらせないこと」の線引き。ここはAIに任せず、自分で決める
L-Proxyはこちらから。14日間の無料トライアルで、AI接続まで試せます。
Lステップ × AI を同時に使いたい方へ
公式LINEのWebhook、1つしか設定できなくて困っていませんか?
公式LINEでは、Webhookの送信先URLを1つしか設定できません。そのため、Lステップを導入している企業が「DifyでAIチャットボットも動かしたい」「自社システムにもイベントを飛ばしたい」と思っても、Webhookの奪い合いになってしまいます。
結果として、片方を諦めるか、中継サーバーを自前で構築するか——どちらにしても時間とコストがかかる選択を迫られます。
L-Proxyなら、1つのWebhookを複数サービスに同時転送
公式LINEとサービスの間にL-Proxyを挟むだけで、Webhookを好きな数だけ分岐できます。
- Lステップはそのまま、AIチャットボットを追加導入
- 自社の顧客管理システムにもリアルタイムでイベント連携
- 設定はURL貼り替えだけ。コード不要、最短5分で導入
今なら2週間の無料トライアル実施中。クレジットカード不要で、すべての機能をお試しいただけます。