2026.05.27

カスタムMCPサーバ実装におけるクライアント登録方式の徹底比較!

導入

こんにちは。グループ研究開発本部 次世代システム研究室のH.Oです。

今回は カスタムMCP(Model Context Protocol)サーバを実装するときに最初にぶつかる、「クライアントをどう登録させるか」という問題を整理したいと思います。

きっかけは、担当プロジェクトで MCP サーバを書く必要が出てきたことです。Web 越しに使われる MCP サーバを書く以上、認可は OAuth 2.1 ベースで組むことになります。OAuth は「クライアントは事前に認可サーバに登録されていて、client_id を持っているところから認可フローが始まる」という前提の仕組みです。

MCP サーバには Claude Desktop / ChatGPT / Cursor といった、こちらが事前に知り得ない不特定多数の AI クライアントが繋いでくるという前提があります。「事前に登録済みのクライアントしか来ない」OAuth と、「事前に誰が繋ぐか分からない」MCP — この食い違いをどう埋めるか、つまり最初の client_id をどう発行・流通させるか、というのが本記事の主題である クライアント登録(client registration) の問題です。

MCP 仕様はこの問題への答えとして、現在 3 つの方式を並列に許しています。

  • 事前登録(pre-registration):運用者が手で client_id / client_secret を発行し、クライアント側の設定ファイルに書き込んでもらう、従来からある OAuth スタイルの方式
  • Dynamic Client Registration(以下 DCR):クライアントが認可サーバの /register エンドポイントを叩き、その場で client_id を払い出してもらう方式
  • Client ID Metadata Document(以下 CIMD):クライアントが自前の HTTPS URL に JSON でメタデータを公開し、その URL 自体を client_id として使う方式

しかもこの 3 方式の推奨優先順位は、MCP 仕様の改版で動き続けています。現行の 2025-11-25 版では SHOULD / MAY の付き方が組み替わり、主要 AI クライアントの実装状況とも完全には一致していません。「結局、自分の MCP サーバはどれを実装すれば繋いでもらえるのか」を判断するには、3 方式それぞれの長所・短所と、仕様がそう動いた背景まで踏み込む必要がありました。

本記事はその調査結果を、公式 Document を出典付きで整理した上で、最後に「自分のユースケースでは 事前登録 / DCR / CIMD のどれを実装すべきか」まで判断できる地点に持っていきます。理解の裏取りとして、NestJS で 3 方式を 1 つのコードベースに同居させた検証用デモも実装し、実物の挙動も並べて観測しています。

アジェンダ

  • 結論ファースト
  • クライアント登録方式の変遷
  • 前提:MCP におけるクライアント登録の何が「課題」なのか
  • 3 つのクライアント登録方式の詳細
    • 事前登録(pre-registration)
    • Dynamic Client Registration(DCR)
    • Client ID Metadata Document(CIMD)
  • MCP authorization 仕様の優先順位
  • 主要 MCP クライアントの対応状況一覧
  • 事前登録 / DCR / CIMD の挙動を実際に並べて比較する
  • ユースケース別にどれを使うべきか(結論)
  • まとめ
  • 最後に

結論ファースト

  • 実装する際の優先順位は「事前登録 → CIMD → DCR → 手入力」。(2025-06-18 版で SHOULD だった DCR は MAY に降格し、CIMD が SHOULD に昇格した)
  • 使い分けは、自分の MCP サーバに どんな相手が繋いでくるか で決まる。社内・契約済み B2B のように、運用者が手で client_id を発行して相手の config に貼れる相手 しか繋いでこないなら 事前登録不特定多数の AI クライアントから繋がれる前提 なら DCR 必須 + CIMD 並行(主要 AI クライアントは現状 DCR で繋いでくる一方、仕様の SHOULD は CIMD)

クライアント登録方式の変遷

MCP における「クライアント登録」の議論は、MCP 仕様自身の更新と一緒に動いてきました。ここは MCP 側のタイムラインで整理します。

2024-11:MCP 公開(stdio 中心、認可は議論の外)

Anthropic が MCP を公開した 2024 年 11 月時点 では、MCP サーバはローカル stdio プロセスとして起動する想定が中心で、クライアントとサーバが同一ホストで動くため認可の仕組みは要件にすら入っていませんでした。Cline / Continue / Zed のような開発ツール系クライアントが今も stdio 中心で動いていることは、その時期の名残でもあります。この時点では「クライアント登録」という概念自体が MCP の語彙に存在しません。

2025-03:authorization 仕様の初版(DCR が SHOULD として明文化)

リモート MCP サーバ(HTTP/SSE 経由)の運用が現実化したタイミングで、authorization 仕様の初版が公開されました(Model Context Protocol “Authorization” 2025-03-26 版)。ここでは MCP サーバ自身が OAuth 2.1 の認可サーバを兼ねる構成が前提とされ、不特定多数のクライアントを受け入れるために Dynamic Client Registration を SHOULD として要求するというラインが引かれました。MCP authorization の文脈で「クライアント登録」が明示的に書かれたのはこの版が最初です。Claude Desktop / Cursor / Claude Code といった既存クライアントが DCR を実装してきた背景はここにあります。

2025-06:Resource Server と認可サーバの分離(RFC 9728 を採用、DCR は引き続き SHOULD)

次の 2025-06-18 版 では、MCP サーバを Resource Server として位置づけ、認可サーバを別レイヤとして分離するモデルに整理されました。クライアントは MCP サーバの RFC 9728 OAuth 2.0 Protected Resource Metadata を起点に、認可サーバの URL を辿るというディスカバリ手順が定義されています。これにより MCP サーバ運営者は Auth0 / Okta / Keycloak のような既存 IdP に認可を任せる構成が組めるようになりました。クライアント登録は引き続き DCR が SHOULD として推奨されています。

2025-11-25(現行版):CIMD を SHOULD に昇格、DCR を MAY に降格

そして本記事の主題である 2025-11-25 版 で、CIMD が SHOULD に昇格、DCR が MAY に降格するという変更が入りました。なお MCP の versioning ページ では、2025-11-25 は明示的に Current(現行リリース)と位置づけられており、これは将来の予告ではなく、すでに有効な仕様の話です。「事前登録 → CIMD → DCR → 手入力」というフォールバック構造が明文化され、MCP authorization 仕様として「どの方式で繋ぐべきか」が初めて優先順位付きで規定されたかたちになっています。

整理すると、MCP 側のクライアント登録の歴史は次のように動いてきました。

2024-11  MCP 公開              stdio 中心、認可は議論の外
2025-03  authorization 初版    MCP サーバ = 認可サーバ、DCR を SHOULD
2025-06  RFC 9728 を採用       MCP サーバ = Resource Server に分離、DCR は引き続き SHOULD
2025-11  現行版                CIMD を SHOULD に昇格、DCR は MAY に降格、優先順位を明文化

「ローカル stdio で認可不要」→「MCP サーバが認可サーバを兼ねて DCR で繋ぐ」→「Resource Server と認可サーバを分離して discovery を明確化」→「CIMD を主軸に、サーバ非依存な portable client_id を採用」、という方向で動いてきていることが分かります。

前提:MCP におけるクライアント登録の何が「課題」なのか

OAuth はもともと、限られた数のクライアント・アプリが、限られた数のサービス(GitHub、Google、Slack 等)に対して認可フローを構築するという、いわば「1 対多」または「多対 1」の構造を前提に設計されてきました。前述のとおり、OAuth 2.0 自体がクライアント登録方法を仕様外として割り切ったのも、この前提があったからです。

しかし MCP の場合、この前提が成り立ちにくくなります。MCP は「任意のクライアント(Claude Desktop, Cursor, ChatGPT, 自作 CLI, …)が 任意の MCP サーバ(社内データソース, SaaS, 個人プロジェクト, …)に接続する」という多対多の構造を想定しているからです。Model Context Protocol “Authorization” 2025-06-18 版 は、Dynamic Client Registration を SHOULD として推奨していた理由として以下を明示していました(現行の 2025-11-25 版では同じ問題意識から CIMD が SHOULD に格上げされています)。

Clients may not know all possible MCP servers and their authorization servers in advance.
Manual registration would create friction for users.
It enables seamless connection to new MCP servers and their authorization servers.

この引用から読み取れるのは、MCP が事前登録だけでは想定するユースケースを十分にカバーできないと判断している、ということです。一点目として、クライアント開発者はどの MCP サーバが世の中にあるか事前に知り得ない。二点目として、エンドユーザに「Claude Desktop と社内 MCP サーバを繋ぎたいので IT 部門に client_id を発行してもらってください」と頼むのは現実的でない。三点目として、新しい MCP サーバが立ち上がるたびに各クライアントが対応するのは難しい。これらが MCP に DCR が組み込まれた背景です。

ところが、DCR を真面目に実装してみると、別の課題が浮き彫りとなります。DCR は「クライアントが認可サーバごとに /register を叩いて、自分用の client_id / client_secret をその場で生成してもらう」という設計です。このため、クライアント側には「認可サーバごとに別の登録状態」が溜まっていきます。これは MCP の現行仕様(2025-11-25 版)でも明示されています。

Clients MUST maintain separate registration state (client credentials, tokens) per authorization server and MUST NOT assume that credentials valid for one authorization server will be accepted by another.

例えば Claude Desktop ユーザが、社内 MCP サーバ A、ベンダー MCP サーバ B、コミュニティ MCP サーバ C にそれぞれ繋ぐと、Claude Desktop の中には 3 つの異なる client_id / client_secret ペアが保管されることになります。サーバが増えるほどこの「分散したクレデンシャル群」が積み上がっていき、しかも認可サーバの URL が変わったり、認可サーバを乗り換えたりすると再登録が必要になる。さらに /register エンドポイントを無認証で公開する設計上、bot による濫用登録のリスクもあります。Auth0 や Curity の DCR 解説 Document(Auth0 Docs “Dynamic Application Registration”Curity “Dynamic Client Registration”)でも、運用時のレート制限・登録レコードの寿命管理・Initial Access Token の必要性に触れられています。

これに対する次の手として提案されているのが、Client ID Metadata Document(CIMD)です。

3 つのクライアント登録方式の詳細

ここから本題に入り、現在 MCP authorization が想定する 3 つの方式をそれぞれ整理します。

事前登録(pre-registration)

古典的 OAuth スタイルです。運用者がコンソールや CLI などを通じて client_id / client_secret を事前に発行し、それをクライアント側の config に焼き込みます。

データの流れとしては、以下のように「アプリケーションをサービスに登録する」という人手の手続きが先行します。

[開発者]  --(管理コンソール経由)-->  [認可サーバ]
                                       |
                                       +--> client_id / client_secret を発行
                                       |
[クライアント config に焼き込み]  <----+

強みは挙動が明快なことです。client_id は安定していて、運用者から見て「どの組織のどのアプリか」が静的にマッピング可能、監査ログでも追跡しやすく、レート制限や粒度の細かい権限管理も付けやすい。一点目として運用者側に発行手続きの主導権があり、二点目としてクライアントは複雑なロジックを実装しなくてよい、というメリットがあります。

一方で弱みは、すでに述べたとおりスケールしないことです。MCP サーバが N 個、クライアントが M 個あるとき、事前登録の世界では運用者が N × M 個の発行作業をすることになりかねません。さらに、AI チャットクライアント(Claude Desktop など)のエンドユーザに「事前発行された ID と secret を画面に貼ってください」と要求するのは UX として無理があります。

Dynamic Client Registration(DCR)

クライアントが認可サーバの registration_endpoint(認可サーバの公開メタデータで提示されるエンドポイント。oauth.net “Authorization Server Metadata” 参照)に対して POST /register を叩き、client_id(と必要なら client_secret)をその場で発行してもらう仕組みです。

具体的な HTTP 交換は、oauth.net “Dynamic Client Registration”Auth0 Docs “Dynamic Client Registration” で実例つきで解説されています。記事用に簡略化すると以下のような形です。(Claude Desktopを用いる場合)

POST /register HTTP/1.1
Host: as.example.com
Content-Type: application/json

{
  "client_name": "Claude Desktop",
  "redirect_uris": ["https://claude.ai/oauth/callback"],
  "grant_types": ["authorization_code", "refresh_token"],
  "response_types": ["code"],
  "token_endpoint_auth_method": "none",
  "scope": "mcp:tools mcp:resources"
}
HTTP/1.1 201 Created
Content-Type: application/json

{
  "client_id": "s6BhdRkqt3",
  "client_id_issued_at": 2893256800,
  "client_name": "Claude Desktop",
  "redirect_uris": ["https://claude.ai/oauth/callback"],
  ...
}

MCP の 2025-06-18 版では「Authorization servers and MCP clients SHOULD support the OAuth 2.0 Dynamic Client Registration Protocol」とされていました(Model Context Protocol “Authorization” 2025-06-18 版)。これが現行の 2025-11-25 版で MAY に降格しています(Model Context Protocol “Authorization” 2025-11-25 版)。

DCR の強みは、クライアントが認可サーバに事前登録されていない状態からでも接続を成立させられることです。クライアントは MCP サーバの URL を知ってさえいれば、ディスカバリ → /register → 認可 → トークン取得 → MCP 呼び出しを完全自動化できます。Claude.ai の Custom Connector(Anthropic Support “Building Custom Connectors via Remote MCP Servers”)や、ChatGPT の Custom Connectors(OpenAI Help Center “Connectors in ChatGPT”)が「URL を貼るだけで繋がる」体験を提供できるのは、この DCR が前提にあるからです。

その一方で弱みは 3 つあります。

一点目として、認可サーバごとに登録状態が分散すること。Claude Desktop が 100 個の MCP サーバに繋がれていれば、100 個の client_id を抱えることになります。

二点目として、認可サーバ変更時の再登録が必要になること。たとえば MCP サーバ運営者が認可サーバを Auth0 から自前実装に切り替えると、すべての既存クライアントが再登録を要求されます。

三点目として、/register を無認証で公開する設計が DDoS や大量登録による DB 肥大化のリスクを持つこと。Curity “Dynamic Client Registration”Auth0 Docs “Dynamic Application Registration” は、この対策として「Initial Access Token」(/register を叩くために事前に取得しておくトークン)や Software Statement の利用を推奨していますが、これらを使うと「クライアントが事前に何も登録されていなくても繋げる」という DCR 最大のメリットが失われるため、運用上の悩みどころとなります。

Client ID Metadata Document(CIMD)

3 つ目の方式です。考え方は「client_id 自体を HTTPS URL にしてしまう」というものです。

参考にできるドキュメントとして以下があります。

たとえばクライアント開発者が https://my-mcp-client.example.com/.well-known/oauth-client/metadata.json というエンドポイントを公開し、そこに以下のような JSON を置いておきます。

{
  "client_id": "https://my-mcp-client.example.com/.well-known/oauth-client/metadata.json",
  "client_name": "My MCP Client",
  "redirect_uris": ["https://my-mcp-client.example.com/oauth/callback"],
  "token_endpoint_auth_method": "none",
  "grant_types": ["authorization_code"],
  "response_types": ["code"],
  "scope": "mcp"
}

クライアントは認可サーバの /authorize に対して、client_id パラメータにこの URL を渡します。認可サーバはその URL を取得して JSON を読み、メタデータ(redirect_uris 等)を実行時に解決します。

この方式の核心は、client_id が認可サーバを跨いで portable(可搬)であることです。MCP authorization の現行仕様(Model Context Protocol “Authorization” 2025-11-25 版)には次のように明記されています。

Client IDs based on Client ID Metadata Documents are portable across authorization servers, since they are self-hosted HTTPS URLs resolved by the authorization server on demand. No re-registration is needed when the authorization server changes.

DCR が抱えていた「認可サーバごとに別の登録状態」「サーバ乗り換え時の再登録」「/register 濫用」の 3 つの課題に、設計レベルで対処する形になっています。クライアントはメタデータ JSON を一度公開しておけば、CIMD に対応する任意の認可サーバに対して同じ client_id を使えます。認可サーバ側も /register を実装する必要がなく、必要なときに URL を fetch するだけです。

ただしCIMDにも課題があります。

一点目として、クライアントが自分のメタデータを HTTPS で公開し続ける必要があり、CLI ツールやデスクトップアプリにとっては配信インフラを別途用意することになります。GitHub Pages や Cloudflare Pages のような無料 HTTPS ホスティングを使う実装が多くなる、というのが先行例(AT Protocol、Solid OIDC)の傾向です。実例は Bluesky “OAuth for AT Protocol” などで紹介されています。

二点目として、認可サーバ側はメタデータ URL を毎回 fetch するか、適切にキャッシュするかという実装上の選択が発生します。CIMD draft では Cache-Control ヘッダや HTTP の ETag を尊重するモデルが示唆されています。

三点目として、メタデータ URL の信頼性・改ざん検知をどう設計するか、という新しいセキュリティ論点が生まれます。URL を所有しているドメイン全体の信頼性に依存するため、DNSハイジャック やドメイン乗っ取りに対する備えが必要になります。CIMD は HTTPS の真正性に依拠するため、TLS 証明書管理がセキュリティ境界として効いてきます。

 

MCP authorization 仕様の優先順位

ここまでの 3 方式を、MCP authorization の現行仕様(Model Context Protocol “Authorization” 2025-11-25 版)は明示的に優先順位付けしています。原文を引用します。

Clients supporting all options SHOULD follow the following priority order:

  1. Use pre-registered client information for the server if the client has it available
  2. Use Client ID Metadata Documents if the Authorization Server indicates if the server supports it (via client_id_metadata_document_supported in OAuth Authorization Server Metadata)
  3. Use Dynamic Client Registration as a fallback if the Authorization Server supports it (via registration_endpoint in OAuth Authorization Server Metadata)
  4. Prompt the user to enter the client information if no other option is available

ここで興味深いのは 2 点あります。

一点目は、2025-06-18 版で DCR は SHOULD だったのに対し、現行の 2025-11-25 版では DCR が MAY に降格し、代わりに CIMD が SHOULD に昇格していることです。仕様上の推奨が DCR から CIMD に切り替わっています。

二点目は、事前登録が「もし利用可能なら最優先」とされていることです。クライアント側の運営者と認可サーバ側の運営者が事前に握り合える関係(社内 SaaS のような閉じた構成)であれば、DCR や CIMD を使わずに事前登録を使ったほうが運用上シンプルになる、という判断が読み取れます。

この優先順位は、事前登録・CIMD・DCR を対立する選択肢ではなく、フォールバック関係にある選択肢として並べる構造になっています。事前登録が成立する相手ならそれを使い、できなければ CIMD で portable に解決し、それも無理なら DCR で接続する、という設計です。

主要 MCP クライアントの対応状況一覧

modelcontextprotocol.io/clients で公開されている公式 Document から、クライアント登録方式の対応状況を抜粋して整理します。

 

MCP クライアント 事前登録 DCR CIMD
Claude.ai
Claude Desktop ×
Claude Code ×
ChatGPT
VS Code GitHub Copilot
GitHub Copilot CLI ×
GitHub Copilot coding agent ×
Cursor ×
MCPJam(Inspector 系の OSS)
Inspector(tryinspector.com) ×
Cline × ×
Continue × ×
Zed × ×

各クライアントの一次情報としては、それぞれの公式 Document が参考になります。

注:「事前登録」は OAuth リモートサーバ接続全般において config から client_id を読める or stdio MCP として動かす場合のローカル接続パターンを含むため、ほぼ全クライアントで成立します。逆に DCR / CIMD は「クライアントが自動で動く」ことを意味するため、明示的なサポート機能として宣言されているかを基準にしています。OAuth Client Credentials Grant は別軸ですが、参考として含めました。

この表から読み取れることは 2 つあります。

一点目として、Claude.ai / Claude Desktop / Claude Code・ChatGPT・VS Code GitHub Copilot といった主要 AI クライアントはすべて DCR を実装済みであり、MCP サーバ側で /register を提供することが、これらクライアントから接続される上での前提条件となっています。

二点目として、CIMD は Claude.ai / ChatGPT / VS Code Copilotなど が先行対応しているということです。Claude Desktop は現時点で CIMD 未対応です。一方で Cline / Continue / Zed のようにローカル開発ツールに寄った OSS クライアントは、事前登録ベースの設定で動作しています。

事前登録 / DCR / CIMD の挙動を実際に並べて比較する

ここまで概念で扱ってきた 3 つの方式が、認可サーバの応答としてどう違うのかを実物で見ます。NestJS で書いた mcp-client-registration-demo事前登録DCRCIMD の順に走らせ、外から観測できる差を以下の 4 観点で並べていきます。

  1. AS(Authorization Server) metadata に出る機能宣言(registration_endpoint / client_id_metadata_document_supported)
  2. POST /register を叩いたときの応答
  3. /token を叩く際に要求される認証
  4. 発行された JWT の client_id クレームに何が載るか

3 モードのフロー(全体像)

まず比較対象の認可フローを並べます。

事前登録:

[Operator] --(事前発行)--> [AS]   demo-client / demo-secret を保持
                            │
[Client] <-- creds 受領 ----┘
   │ (1) GET /.well-known/oauth-authorization-server
   │ (2) POST /authorize (PKCE) → 302 with code
   │ (3) POST /token + Basic(demo-client:demo-secret) → JWT
   │ (4) POST /mcp + Bearer JWT
   ▼
[MCP Resource]

DCR:

[Client]
   │ (1) POST /register (RFC 7591) → client_id: dcr_xxxxx
   │ (2) GET /.well-known/oauth-authorization-server
   │ (3) POST /authorize (PKCE) → 302 with code
   │ (4) POST /token (Basic なし、PKCE のみ) → JWT
   │ (5) POST /mcp + Bearer JWT
   ▼
[MCP Resource]

CIMD:

[Client] -- メタデータ JSON を HTTPS で公開しておく
           例: https://example.com/.well-known/oauth-client/metadata.json
   │
   │ (1) GET /.well-known/oauth-authorization-server
   │ (2) POST /authorize  (client_id = メタデータ URL)
   │            │
   │            ▼                       ┌──────┐
   │     [AS が URL を fetch] ─────────►│Client│
   │            │   メタデータ JSON     │Host  │
   │            ▼                       └──────┘
   │     redirect_uris などを検証 → 302 with code
   │ (3) POST /token (Basic なし、PKCE のみ) → JWT
   │     ※ JWT.client_id = メタデータ URL
   │ (4) POST /mcp + Bearer JWT
   ▼
[MCP Resource]

事前登録 は「クライアントが事前登録済み」、DCR は「クライアントが /register で名乗りに行く」、CIMD は「クライアントが公開した URL を AS が fetch しに来る」というのが、ステップ列の頭の違いです。以降、この差が具体的にどう観測されるかを見ていきます。

 

デモ実装の概要

Nest.jsで検証用の擬似的なデモを作って、ローカルでリクエストとレスポンスを確認しました。

Nest.js の単一プロセスに、認可サーバ・MCP リソースサーバ・(CIMD 用に)クライアントメタデータ配信を同居させた最小構成です。これらを観測の見通しを良くするために 1 プロセスに寄せています。

公開エンドポイントを以下のように設定しています。

  • 認可サーバのエンドポイント:/.well-known/oauth-authorization-server /authorize  /token
  • 動的登録(DCR モードのみ):/register
  • クライアントメタデータ配信(CIMD モードの確認用):/demo-client/metadata.json
  • MCP リソースサーバ:/mcp(whoami ツールが Bearer JWT のクレームをそのまま返す)

モードは環境変数 MODE で切り替えるように実装しています。

.env には署名鍵などモード共通の値だけ入れておきます。
事前登録モードのclient_idはdemo-client に設定しています。(client_secretは仕様上optionalとなっており、今回は省略しています)

認可フロー(全モード共通の骨格)

各モードで起動した後、PKCE 付き authorization code フローを 1 回回します。

  1. code_verifier / code_challenge を生成
  2. GET /authorize で 302 + code を受け取る
  3. POST /tokencode をアクセストークン JWT に交換
  4. POST /mcpwhoami ツールを Bearer JWT 付きで呼んで、JWT クレームの中身を確認

以降の観点 1 〜 4 では、この共通フローのうち、モードによって挙動が変わる 1 ステップを抜粋します。

観点 1:AS metadata に出る機能宣言

curl -s http://localhost:3000/.well-known/oauth-authorization-server | jq

モード共通で返る最小セット:

{
  "issuer": "http://localhost:3000",
  "authorization_endpoint": "http://localhost:3000/authorize",
  "token_endpoint": "http://localhost:3000/token",
  "jwks_uri": "http://localhost:3000/.well-known/jwks.json",
  "response_types_supported": ["code"],
  "grant_types_supported": ["authorization_code"],
  "code_challenge_methods_supported": ["S256"],
  "token_endpoint_auth_methods_supported": [
    "client_secret_basic", "client_secret_post", "none"
  ],
  "scopes_supported": ["mcp"]
}

モードごとの差分:

  • 事前登録:追加フィールドなし
  • DCR:"registration_endpoint": "http://localhost:3000/register" が追加されています。
  • CIMD:"client_id_metadata_document_supported": true が追加されます。

クライアントはこの 1 〜 2 行を見て、どの方式で接続を組み立てるかを決定します。

観点 2:POST /register の応答

事前登録モード:ルート自体がマウントされていません。

$ curl -s -o /dev/null -w "HTTP %{http_code}\n" \
    -X POST http://localhost:3000/register \
    -H 'content-type: application/json' -d '{}'
HTTP 404

DCR モード:RFC 7591 準拠で client_id を発行。

$ curl -s -X POST http://localhost:3000/register \
    -H 'content-type: application/json' \
    -d '{
      "client_name": "flow-dcr",
      "redirect_uris": ["http://localhost:9999/cb"],
      "token_endpoint_auth_method": "none"
    }' | jq
{
  "client_id": "dcr_qayPoiljvIWUuDPl",
  "client_secret": null,
  ...
}

CIMD モード:/register は無く(404)、代わりにクライアント側でメタデータ JSON を HTTPS で公開します。デモでは同じプロセスから配信します。

$ curl -s http://localhost:3000/demo-client/metadata.json | jq
{
  "client_id": "http://localhost:3000/demo-client/metadata.json",
  "client_name": "demo-cimd-client",
  "redirect_uris": ["http://localhost:9999/cb"],
  "token_endpoint_auth_method": "none",
  "grant_types": ["authorization_code"],
  "response_types": ["code"],
  "scope": "mcp"
}

DCR では認可サーバにレコードが書き込まれて client_id が払い出される一方、CIMD では認可サーバには何も書き込まれず、クライアントが公開した URL がそのまま client_id として機能します。データの流れの向き(client → AS の POST か、AS → client の GET か)が逆です。

観点 3:/Authorizeと/token を叩く際の認証方式

事前登録モード(confidential client。client_secret を Basic で送る):

// codeを取得
$ curl -i -X POST http://localhost:3000/authorize \
--data-urlencode "response_type=code" \
--data-urlencode "client_id=demo-client" \
--data-urlencode "redirect_uri=http://localhost:6274/oauth/callback" \
--data-urlencode "scope=mcp" \
--data-urlencode "code_challenge=$CHALLENGE" \
--data-urlencode "code_challenge_method=S256" \
--data-urlencode "username=demo" \
--data-urlencode "password=password"

HTTP/1.1 302 Found
X-Powered-By Express
Vary: Origin, Accept
Access-Control-Expose-Headers: Mcp-Session-Id,WWW-Authenticate
Localtion: http://localhost:9999/cb?code=*******************
Content-Type: text/plain; charset=utf-8
Content-Length: 95
Date: Tue, 26 May 2026 01:35:17 GMT
Connection: keep-alive
Keep-Alive: timeout=5

ここからcodeを取得してAUTH_CODE=*********** を設定。
/tokenを叩く

 curl -X POST http://localhost:3000/token \
-u demo-client:demo-secret \
--data-urlencode "grant_type=authorization_code" \
--data-urlencode "code=$AUTH_CODE" \
--data-urlencode "redirect_uri=http://localhost:6274/oauth/callback" \
--data-urlencode "code_verifier=$VERIFIER"
{"access_token":"**************","token_type":"Bearer","expires_in":3600,"scope":"mcp"}

DCR モード(public client。Basic なし。client_id/register の払い出し値):

// codeを取得
curl -i -X POST http://localhost:3000/authorize \
--data-urlencode "response_type=code" \
--data-urlencode "client_id=$CLIENT_ID" \
--data-urlencode "redirect_uri=http://localhost:9999/cb" \
--data-urlencode "scope=mcp" \
--data-urlencode "code_challenge=$CHALLENGE" \
--data-urlencode "code_challenge_method=S256" \
--data-urlencode "username=demo" \
--data-urlencode "password=password"

HTTP/1.1 302 Found
X-Powered-By Express
Vary: Origin, Accept
Access-Control-Expose-Headers: Mcp-Session-Id,WWW-Authenticate
Localtion: http://localhost:9999/cb?code=*******************
Content-Type: text/plain; charset=utf-8
Content-Length: 95
Date: Tue, 26 May 2026 01:19:17 GMT
Connection: keep-alive
Keep-Alive: timeout=5

Found. Redirecting to http://localhsot:9999/cb?code=**************

ここからcodeを取得してAUTH_CODE=*********** を設定。
/tokenを叩く

$ curl -X POST http://localhost:3000/token \
    --data-urlencode "grant_type=authorization_code" \
    --data-urlencode "client_id=dcr_*************" \
    --data-urlencode "code=$AUTH_CODE" \
    --data-urlencode "redirect_uri=$REDIR" \
    --data-urlencode "code_verifier=$VERIFIER"
{"access_token":"*********","token_type":"Bearer","expires_in":3600,"scope":"mcp"}

CIMD モード(public client。Basic なし。client_id はメタデータ URL):

curl -i -X POST http://localhost:3000/authorize \
--data-urlencode "response_type=code" \
--data-urlencode "client_id=http://localhost:3000/demo-client/metadata.json" \
--data-urlencode "redirect_uri=http://localhost:9999/cb" \
--data-urlencode "scope=mcp" \
--data-urlencode "code_challenge=$CHALLENGE" \
--data-urlencode "code_challenge_method=S256" \
--data-urlencode "username=demo" \
--data-urlencode "password=password"

HTTP/1.1 302 Found
X-Powered-By Express
Vary: Origin, Accept
Access-Control-Expose-Headers: Mcp-Session-Id,WWW-Authenticate
Localtion: http://localhost:9999/cb?code=*******************
Content-Type: text/plain; charset=utf-8
Content-Length: 95
Date: Tue, 26 May 2026 01:54:57 GMT
Connection: keep-alive
Keep-Alive: timeout=5

ここからcodeを取得してAUTH_CODE=*********** を設定。
/tokenを叩く

$ curl -X POST http://localhost:3000/token \
    --data-urlencode "grant_type=authorization_code" \
    --data-urlencode "client_id=http://localhost:3000/demo-client/metadata.json" \
    --data-urlencode "code=$CODE" \
    --data-urlencode "redirect_uri=$REDIR" \
    --data-urlencode "code_verifier=$VERIFIER"
{"access_token":"**********","token_type":"Bearer","expires_in":3600,"scope":"mcp"}

DCR と CIMD は public client + PKCE という点で挙動が同じで、client_id の中身(不透明文字列か、URL か)が異なるだけです。事前登録だけが client_secret_basic の confidential client パターンになります。

観点 4:JWT の client_id クレームに何が載るか

得た JWT で /mcpwhoami ツールを呼ぶと、JWT のクレームをそのまま返してくれます。

$ curl -s -X POST http://localhost:3000/mcp \
    -H "Authorization: Bearer $ACCESS_TOKEN" \
    -H "content-type: application/json" \
    -d '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"whoami"},"id":1}' | jq

モードごとの client_id クレーム:

事前登録モード:

{ "sub": "demo", "client_id": "demo-client", ... }

DCR モード:

{ "sub": "demo", "client_id": "dcr_********", ... }

CIMD モード:

{ "sub": "demo", "client_id": "http://localhost:3000/demo-client/metadata.json", ... }

client_id の正体は、事前登録では .env の静的文字列、DCR では /register が動的発行した不透明 ID、CIMD ではクライアントが公開した URL そのもの、と並びます。リソースサーバから見ると、CIMD では client_id を見るだけで「どこの誰が公開したメタデータか」が URL の形で分かるので、別の認可サーバから来たトークンであっても同じ client_id で識別できます。DCR では認可サーバごとに値が変わるため、複数 AS を跨いだクライアント同定はやりにくくなります。

差分まとめ

観察ポイント 事前登録 DCR CIMD
AS metadata の機能宣言 なし registration_endpoint client_id_metadata_document_supported: true
POST /register 404 201 で client_id 発行 404
client_id の正体 事前発行された静的文字列 AS が払い出す文字列 MCPクライアントが公開した HTTPS URL
クライアントメタデータの保管場所 AS 側 AS 側 MCPクライアント側(HTTPS で公開)
別の AS に同じ client_id で繋ぐ 不可 不可(再 /register 必要) 可(URL を渡し直すだけ)
クライアント側に必要な事前情報 client_id,client_secret,認可サーバのURL 認可サーバの URL のみ 認可サーバの URL のみ

ユースケース別にどれを使うべきか(結論)

自分の MCP サーバが「どんな相手から繋がれるか」で、実装すべき方式は 2 つに集約できます。

  • 既知の相手しか繋いでこない(社内・契約済み B2B)事前登録
  • 不特定多数の AI クライアントから繋がれる前提DCR を実装(必須) + CIMD を並行実装。主要 AI クライアントは現状 DCR 前提で繋いでくる一方、仕様の SHOULD は CIMD に移っている

MCP 仕様(2025-11-25 版)が示す優先順位「事前登録 → CIMD → DCR → 手入力」は、繋ぎに来るクライアント側の runtime フォールバックとして規定されたものなので、サーバ実装としては「想定する相手の母集団に対して、その優先順位で実装が用意されている状態を作る」のがゴールになります。

まとめ

  • MCP のクライアント登録には 事前登録 / DCR / CIMD の 3 方式があり、MCP 仕様自体が 2024-11 の公開 → 2025-03 の authorization 初版 → 2025-06 の RFC 9728 採用 → 2025-11-25 の CIMD 主軸化、という流れで動いてきた
  • MCP の現行仕様(2025-11-25 版)では、2025-06-18 版で SHOULD だった DCR が MAY に降格し、CIMD が SHOULD に昇格。優先順位は「事前登録 → CIMD → DCR → ユーザ手入力」のフォールバック構造(公式 Document: Model Context Protocol “Authorization” 2025-11-25 版)
  • 実物の挙動を観察すると、事前登録 / DCR / CIMD の差は AS metadata の 1〜2 行、/register の有無、/token の認証方式、JWT に焼かれる client_id の正体、の 4 点に集約される
  • MCP サーバの実装としてどの方式を用意するかは 想定する相手の母集団 で決まる。社内・B2B(相手が事前に分かっている)は事前登録、不特定多数に公開する場合は DCR 必須 + CIMD 並行、というのが現時点での現実的な棲み分け
  • 主要 AI クライアント(Claude 系・ChatGPT・GitHub Copilot 系・Cursor)は DCR 実装済みで、CIMD は Claude.ai / ChatGPT / VS Code Copilot / MCPJam が先行対応している

最後に

グループ研究開発本部 次世代システム研究室では、最新のテクノロジーを調査・検証しながらインターネット上の高度なアプリケーション開発を行うエンジニア・アーキテクトを募集しています。募集職種一覧 からご応募をお待ちしています。

  • Twitter
  • Facebook
  • はてなブックマークに追加

グループ研究開発本部の最新情報をTwitterで配信中です。ぜひフォローください。

 
  • AI研究開発室
  • 大阪研究開発グループ

関連記事