1. はじめに ― 公開したMCPサーバは無防備
MCPサーバを自分で作り、それをHTTPで公開すると、そのサーバはネットワーク越しに誰でも呼び出せる状態になります。手元で自分だけが使っている間は意識しませんが、URLを外に出した瞬間から、次のような問題が現実になります。
- 無認可アクセス: 誰でもツールを実行できてしまう。ファイルを読む、外部APIを叩く、といったツールを持たせていれば、そのまま悪用される。
- なりすまし: 呼び出してきた相手が誰なのかをサーバが確認できない。
- トークンの使い回し: あるサーバ向けに発行された資格情報が、別のサーバに流用される。
そこで認証と認可を入れることになりますが、ここで悩ましいのが手軽さとのバランスです。利用者ごとに「まず管理画面でクライアント登録を申請してください」と要求すると、任意のクライアントから気軽に繋いでもらう、という利点が損なわれます。
この記事では、― 守ることと事前登録を強いない手軽さ ― を両立させるために、OAuth を使った構成を扱います。MCPの認可の考え方は仕様として定義されているので、まずそれに沿って整理します。この記事では現時点で最新の版に沿います。
なお、この記事の後半では実際に動かして確認しますが、その環境はあくまで仕組みを見るためのデモです。そのまま公開用途に使わないでください。理由は最後の章にまとめます。
2. 設計方針 ― 認可サーバを切り離す
登場人物
この構成には3人の登場人物がいます。
MCPの最新仕様では、MCPサーバは OAuth 2.1 のリソースサーバとして振る舞います。
なぜ認可を自前で持たず、認可サーバに分離するのか
MCPサーバの中にログイン画面やパスワード管理、トークン発行の仕組みを抱え込むこともできますが、そうすると認証まわりの責任をすべて自分で負うことになります。パスワードの保管、多要素認証、トークンの失効といった難しい部分を、本来ツールを提供したいだけのサーバが引き受けるのは割に合いません。
そこで、認証とトークン発行は認可サーバという専用の存在に任せ※、MCPサーバは「渡されたトークンが正しいかどうかを確認するだけ」に徹します。この分離により、MCPサーバのコードは認可の詳細から解放され、認可サーバは既存のサービス(この記事のデモではローカルに立てた実装、実運用ならAuth0のような外部サービス)に置き換えられます。
こうして役割を分けたうえで残るのが、「事前登録を強いずに、繋ぎに来たクライアントへどうやってクライアント識別子(client_id)を持たせるか」という問題です。次の章で先に必要な用語を整理し、その次の章でこの登録方式を扱います。
※ OAuthの認可サーバは、トークン発行の前段として利用者の認証も担うのが通例です(概念としての認証≠認可の話はここではしません)。
3. 前提知識 ― OAuth 2.1 と関連仕様
登場する用語と仕様を、必要な範囲で整理します。MCPの認可は OAuth 2.1 を土台にしています。
OAuth 2.1
OAuth は、あるサービスへのアクセスを許可するための枠組みです。次の四つの役割で構成されます。
- リソースオーナー: 資源の持ち主。多くの場合は利用者本人。
- クライアント: 資源にアクセスしたいアプリ。
- 認可サーバ: リソースオーナーを認証し、アクセストークンを発行する。
- リソースサーバ: アクセストークンを検証して資源を提供する。
クライアントはパスワードそのものを預かるのではなく、認可サーバからアクセストークンという短命の資格情報を受け取り、それをリソースサーバへの各リクエストに添えます。リソースサーバはトークンを確認するだけで、利用者のパスワードを知る必要がありません。OAuth 2.1 は、この枠組みを定めた OAuth 2.0 に、その後に確立した安全な使い方(PKCEの必須化など)を取り込んだものです。
認可コードフローと PKCE
アクセストークンの取得方法にはいくつかありますが、今回使うのは認可コードフローです。クライアントは利用者をブラウザで認可サーバのログイン画面へ送り、認証と同意が済むと、いったん認可コードという短い値を受け取ります。クライアントはそのコードを認可サーバのトークンエンドポイントに送り、アクセストークンと交換します。
このとき、認可コードが途中で盗まれても悪用されないように、PKCEという仕組みを併用します。クライアントは毎回ランダムな値を生成し、その要約を認可リクエストに載せておき、トークン交換のときに元の値を提示します。両者が一致しなければトークンは発行されません。MCPの認可ではPKCEが必須です。
JWT
JWT(JSON Web Token)は、JSONで表した情報に署名を付けた、持ち運びできる形式のトークンです。中身はクレームと呼ばれる項目の集まりで、今回の検証で重要になるのは次の三つです。
iss: 発行者。どの認可サーバが発行したか。aud: 受け手。どのリソースサーバ向けのトークンか。exp: 有効期限。これを過ぎたトークンは無効。
アクセストークンをJWT形式で発行する場合の作法は、専用のプロファイルとして定義されています(ヘッダの型が at+jwt になります)。
JWK と JWKS
JWTの署名を検証するには、発行者の公開鍵が必要です。この公開鍵をJSONで表したものがJWK(JSON Web Key)で、複数のJWKをまとめた集合がJWKS(JSON Web Key Set)です。
認可サーバは自分の公開鍵をJWKSとして決まったURLで公開します。リソースサーバはそのURLから鍵を取得して署名を検証します。両者の間で秘密の共有鍵を事前に配る必要がなく、公開鍵を取りに行くだけで検証できる点がポイントです。
メタデータの発見
クライアントは、リソースサーバや認可サーバの設定を事前に知らなくても、決まった場所に置かれたメタデータを読むことで必要な情報(認可サーバの場所、各エンドポイントのURL、JWKSのURLなど)を発見できます。
- 保護リソースメタデータ: リソースサーバが「自分を守っている認可サーバはここだ」と示す情報。決まったパス(
/.well-known/oauth-protected-resource)に置かれます。 - 認可サーバメタデータ: 認可サーバが自分のエンドポイントを列挙した情報。
これらにより、クライアントは最初に一度アクセスするだけで、その先の手順を自力でたどれます。
- 参照: RFC 9728 OAuth 2.0 Protected Resource Metadata / RFC 8414 OAuth 2.0 Authorization Server Metadata
リソースを指定するための Resource Indicators
トークンの aud(受け手)を正しく絞るために、クライアントは「このトークンはどのリソースサーバ向けか」を認可リクエストで指定します。これにより、あるサーバ向けに出したトークンが別のサーバで通用してしまう問題を防げます。最新のMCP認可仕様では、クライアントがこの resource 指定を含めること、そしてサーバが自分向けに発行されたトークンかを検証することが、いずれも必須とされています。
4. クライアントをどう登録するか ― 3つの方式
OAuthでは、クライアントは認可サーバに対して自分を示す client_id を持つ必要があります。問題は、公開したMCPサーバに「事前登録なしで任意のクライアントに繋いでほしい」場合、この client_id をどう用意するかです。最新のMCP認可仕様(2025-11-25)は、次の三つの方式を挙げ、優先順位を示しています。
事前登録
クライアントとサーバに既存の関係がある場合の方式です。あらかじめ認可サーバにクライアントを登録し、払い出された client_id を使います。相手が決まっているなら最も素直ですが、「任意のクライアントに気軽に繋いでほしい」という今回の目的には向きません。
Client ID Metadata Documents(CIMD)― 第一優先
関係のない任意のクライアント向けに、最新仕様が第一に推奨する方式です。クライアントは自分のメタデータを書いたJSON文書をHTTPSのURLに公開しておき、そのURL自体を client_id として使います。
文書の例は次のようなものです。
{
"client_id": "https://app.example.com/oauth/client-metadata.json",
"client_name": "Example MCP Client",
"redirect_uris": ["http://127.0.0.1:3000/callback"],
"grant_types": ["authorization_code"],
"response_types": ["code"],
"token_endpoint_auth_method": "none"
}
client_id の値が、その文書自身のURLと完全に一致している点がポイントです。クライアントは認可リクエストでこのURLを client_id として渡し、認可サーバはそのURLを取得して中身(redirect_uris など)を検証し、問題なければ認可を続けます。
重要なのは、このURLはアプリの提供元が一つ用意するものであって、利用者一人ひとりが用意するものではないという点です。たとえば Claude Code は公開の文書 https://claude.ai/oauth/claude-code-client-metadata を持ち、その利用者は全員この同じURLを client_id として使います。利用者側は何もホストしません。
この方式の利点は、認可サーバ側に登録レコードを作らせずに済むことです。認可サーバはURLを取得して検証するだけで、状態を持ちません。次に述べる動的クライアント登録の「繋ぐたびに登録が増える」という弱点を避けられるため、第一優先とされています。
制約もあります。文書はHTTPSで公開する必要があり、client_id と文書のURLは完全に一致していなければなりません。また、クライアントと認可サーバの双方がこの方式に対応している必要があり、認可サーバは自分のメタデータに client_id_metadata_document_supported: true を載せて対応を示します。
動的クライアント登録(DCR)― フォールバック
クライアントがその場で認可サーバの登録エンドポイントに登録リクエストを送り、client_id を発行してもらう方式です。以前は「任意のクライアントに繋がせる」ための中心的な手段でしたが、2025-11-25の仕様では、後方互換や特定の要件のためのフォールバックという位置づけに変わりました。
優先順位
クライアントは次の順で方式を選びます。事前登録の情報があればそれを使う。無ければ、認可サーバがCIMDへの対応を広告していればCIMDを使う。それも無ければDCRにフォールバックする。どれも使えなければ、利用者にクライアント情報の入力を促す。
今回のデモでDCRを使う理由
この記事のデモは、ローカルで手軽に動かすことを優先します。検証に使うMCP Inspectorは、CIMDに必要な「クライアントメタデータ文書のURL」を設定する口を持たず、DCR(またはclient_idの手入力)で繋ぎます。加えてCIMDは文書のHTTPS公開が前提で、平文の localhost では扱いづらいという事情もあります。そこで、このデモではDCRを使います。
ただし強調しておきたいのは、どの方式を選んでも、MCPサーバ(リソースサーバ)側の実装は変わらないということです。サーバがやるのは受け取ったトークンの検証だけで、方式によって違うのは「クライアントが client_id をどう手に入れるか」の一点にすぎません。このあと作るサーバのコードは、仮にCIMDに切り替えても、そのまま通用します。
5. 通信シーケンス ― どう繋がるか
トークンを持っていないクライアントが、最終的に接続を確立するまでの流れは次のとおりです(登録方式はDCRの場合)。
ポイントは(2)です。ここでMCPサーバは、認可サーバのURLを直接返すのではなく、保護リソースメタデータのURLを返します。クライアントはそこを起点にして、認可サーバの場所やエンドポイントを自力で発見していきます。この間接的な指し方が現在のMCP認可仕様のかたちです。
前章のとおり、CIMDを使う場合はこの図の(5)が「client_id として自分のメタデータ文書のURLを提示し、認可サーバがそれを取得して検証する」に置き換わるだけで、それ以外の流れは同じです。
6. 実装のポイント
ここからは実際のコードを見ていきます。MCPサーバ側には公式のSDK(@modelcontextprotocol/sdk)を使い、認可サーバ側にはローカルで動く実装(node-oidc-provider)を使いました。トークンの署名検証には jose を使います。joseはJavaScriptとTypeScript向けに、JWTやJWK、JWKSの扱いを一通り提供するライブラリです。
トランスポート ― SSE と Streamable HTTP の違い
MCPをHTTPで公開するときの通信方式(トランスポート)には二種類あり、これらは別物です。
HTTP+SSE(古い方式)は、二つのエンドポイントを使います。クライアントは、サーバから自分への通知を受け取るために、SSE(Server-Sent Events)の長い接続をGETで開きっぱなしにし、自分からサーバへのメッセージは別のエンドポイントへPOSTで送ります。送信と受信で口が分かれており、常時つなぎっぱなしの接続が前提です。
…
記事の続きは下のURLをクリック!
https://rightcode.co.jp/blogs/56428
もっと上を目指したあなたへ
現在、ライトコードでは「WEBエンジニア」「データエンジニア」「ゲーム・XRエンジニア」「UI/UXデザイナー」「PM/PMO」などのポジションで積極採用中です!
ライトコードは、技術力に定評のある受託開発をメインにしているIT企業。
有名WEBサービスやアプリの受託開発などの企画、開発案件が目白押しの状況です!
- もっと大きなことに挑戦したい!
- エンジニアとしてもっと成長したい!
- モダンな技術に触れたい!
現状に満足していない方は、まずは一度、【Wantedly内の弊社ページ】をのぞいてみてください。