個人開発SaaSにMCP+OAuth 2.1を実装した記録。本体は薄く、認可が本番だった

2026/08/07 09:26 公開

個人開発SaaSにMCP+OAuth 2.1を実装した記録。本体は薄く、認可が本番だった

自作の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-Proxy(Lプロキシ)| 公式LINEのWebhookを複数転送・AI自動設定対応

PR

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週間の無料トライアル実施中。クレジットカード不要で、すべての機能をお試しいただけます。

L-Proxy について詳しく見る
記事一覧に戻る