internal-kb-mcp

internal-kb-mcp

A remote MCP server on Cloudflare Workers protected by Okta custom authorization server, demonstrating tool-level authorization via JWT access tokens and scopes/groups.

Category
访问服务器

README

internal-kb-mcp — Okta のカスタム認可サーバーで保護した MCP サーバー

Cloudflare Workers 上に構築した リモート MCP サーバーの検証用実装 です。 アクセス制御を MCP サーバー側で自前実装するのではなく、Okta のカスタム認可サーバーが発行したアクセストークン (JWT) の中身だけを根拠に、ツール単位で認可するという構成を検証します。

このリポジトリは検証記事の付属サンプルです。実運用を想定したものではありません。

何を検証できるか

検証したいこと このリポジトリでの表現
MCP クライアントが「どこで認証すればよいか」を自力で見つけられるか RFC 9728 の保護リソースメタデータ + 401 応答の WWW-Authenticate
スコープによるツール単位の認可 whoami / wiki_search
IdP のグループによる特権ツールの制御 contract_lookup
認可の状態を利用者が確認できるか whoami が検証済みクレームをそのまま返す

認可フロー

sequenceDiagram
    autonumber
    participant C as MCP<br/>クライアント
    participant W as Worker<br/>(MCP サーバー)
    participant O as Okta<br/>認可サーバー

    C->>W: POST /mcp (トークンなし)
    W-->>C: 401 + WWW-Authenticate<br/>resource_metadata=...
    C->>W: GET /.well-known/<br/>oauth-protected-resource
    W-->>C: resource / authorization_servers<br/>/ scopes_supported
    C->>O: 認可リクエスト<br/>(audience = MCP_RESOURCE)
    O-->>C: アクセストークン JWT<br/>(scp / groups を含む)
    C->>W: POST /mcp<br/>Authorization: Bearer ...
    W->>O: JWKS 取得<br/>({issuer}/v1/keys)
    O-->>W: 公開鍵
    W-->>C: 署名 / iss / aud 検証 OK<br/>→ ツール実行

ポイントは、Worker 側がクライアントシークレットを一切持たないことです。 この Worker は OAuth のリソースサーバーであり、JWKS で取得した公開鍵を使って JWT を検証するだけです。認可の判断材料はすべてトークンの中にあります。

ツールと認可条件

ツール 認可条件 引数 内容
whoami スコープ whoami.read なし 検証済みトークンの subject / scopes / groups / audience / issuer / 有効期限を返す
wiki_search スコープ wiki.read query (必須) 社内ナレッジのダミーデータを全文検索する
contract_lookup Okta グループ mcp-managers への所属 customer (任意) 顧客の契約情報のダミーデータを返す。省略時は全件

判定に使うクレームは次のとおりです。

  • スコープ: アクセストークンの scp クレーム
  • グループ: アクセストークンの groups クレーム(Okta 側でトークンに載せる設定が必要)

条件を満たさない場合はトランスポート層で弾かずに、ツールの応答として「なぜ実行できないか」を返します(isError: true)。認可の効き方が MCP クライアントの画面上で見えるようにするための、意図的な設計です。

データはすべてソースコード内のダミーです(WIKI / CONTRACTS)。実在の顧客情報は含まれていません。

セットアップ

前提

  • Cloudflare アカウント(Workers が有効)
  • Okta のテナント(カスタム認可サーバーを作成できるプラン)
  • Node.js と npm

1. Okta 側

Okta 管理コンソールの Security > API > Authorization Servers から、カスタム認可サーバーを作成し、以下を設定します(UI の名称は Okta のバージョンにより多少異なります)。

  1. カスタム認可サーバーを作成する
    • 作成すると issuer が https://<your-okta-domain>.okta.com/oauth2/<authorization-server-id> の形式で払い出されます。これが OKTA_ISSUER になります。
  2. Audience を Worker の URL に設定する
    • 後述の MCP_RESOURCE と完全に一致させます。ここがずれていると、署名が正しくても aud 不一致で常に 401 になります。
  3. スコープを追加する
    • whoami.read
    • wiki.read
  4. グループを作成する
    • mcp-managers を作成し、特権ツールを使わせたいユーザーを所属させます。
    • 権限差を確認するために、所属していないユーザーも 1 人用意しておくと検証しやすくなります。
  5. アクセストークンに groups クレームを載せる
    • カスタム認可サーバーの Claims で、groups という名前のクレームを Access Token に対して追加します。
    • これを設定しないと contract_lookup は誰も実行できません(グループ判定が常に空になるため)。
  6. アクセスポリシー / ルールを設定する
    • どのクライアント・どのユーザーに、どのスコープを付与するかを定義します。

MCP クライアントが Okta からトークンを取得するための OAuth クライアントの登録方法(動的クライアント登録を使うか、事前に登録したクライアントを使うか)は、利用する MCP クライアントによって異なります。この部分は検証記事側の手順を参照してください。

2. Cloudflare 側

git clone https://github.com/yamashin55/okta-mcp-cloudflare-demo.git
cd okta-mcp-cloudflare-demo
npm install

wrangler.jsonc の vars を、手順 1 で確定した自分の環境の値に必ず書き換えます。

"vars": {
  "OKTA_ISSUER": "https://<your-okta-domain>.okta.com/oauth2/<authorization-server-id>",
  "MCP_RESOURCE": "https://<worker-name>.<your-subdomain>.workers.dev"
}

MCP_RESOURCE はデプロイ後に確定する URL なので、一度デプロイして URL を確認してから書き換え、もう一度デプロイする流れになります。

npx wrangler deploy

OKTA_ISSUER と MCP_RESOURCE はどちらも秘密情報ではありません。次項のメタデータエンドポイントから無認証で公開される値です。そのため wrangler secret ではなく平文の vars で管理しています。

3. 接続確認

保護リソースメタデータは無認証で取得できます。

curl -s https://<your-worker-url>/.well-known/oauth-protected-resource
{
  "resource": "https://<your-worker-url>",
  "authorization_servers": ["https://<your-okta-domain>.okta.com/oauth2/<authorization-server-id>"],
  "scopes_supported": ["whoami.read", "wiki.read"],
  "bearer_methods_supported": ["header"]
}

トークンなしでツールを呼ぶと、401 と WWW-Authenticate が返ります。MCP クライアントはこのヘッダーを見て、認可サーバーの場所を知ります。

curl -i -X POST https://<your-worker-url>/mcp \
  -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
HTTP/2 401
www-authenticate: Bearer resource_metadata="https://<your-worker-url>/.well-known/oauth-protected-resource"

認可が効いていることの確認

確認したいこと 操作 期待される結果
トークンが検証されている whoami を実行 subject / scopes / groups / audience / issuer が返る
スコープで守られている wiki.read を含まないトークンで wiki_search を実行 「このツールにはスコープ wiki.read が必要です。」
グループで守られている mcp-managers に所属していないユーザーで contract_lookup を実行 「このツールは Okta グループ mcp-managers のメンバーのみ実行できます。」
audience が効いている MCP_RESOURCE と Audience をずらす すべてのリクエストが 401

whoami を最初に実行して、実際にどのスコープとグループがトークンに載っているかを確認してから他のツールを試すと、原因の切り分けが楽になります。

実装

すべて src/index.ts の 1 ファイルです。

箇所 役割
fetch ハンドラ /.well-known/oauth-protected-resource の応答、Bearer トークンの取り出し、jwtVerify による検証
getJwks() issuer ごとに JWKS を使い回すキャッシュ
unauthorized() RFC 9728 に沿った WWW-Authenticate 付きの 401 応答
createServer() 3 つのツールの登録と、スコープ / グループによる認可判定
claimsOf() / scopesOf() / groupsOf() 検証済みクレームをツールから参照するためのヘルパー

検証済みのクレームは createMcpHandler の authContext 経由でツールに渡しています。ツール側はトークンの検証を一切行わず、検証済みの事実だけを見て判断する構造です。

開発

npm run dev          # ローカル開発サーバー (wrangler dev)
npm run type-check   # 型チェック (tsc --noEmit)
npm run lint:fix     # oxlint
npm run format       # oxfmt
npm run cf-typegen   # wrangler types (バインディング変更時)
npm run deploy       # wrangler deploy

wrangler dev でローカル起動した場合も Okta のトークン検証は有効なままです(JWKS の取得にネットワークアクセスが発生します)。ローカルで検証を通すには、MCP_RESOURCE と Okta の Audience を合わせておく必要があります。

注意事項

  • 本リポジトリは 検証・学習を目的としたサンプル実装です。無保証で提供され、実運用環境での利用は想定していません。
  • 収録しているナレッジ・契約情報はすべてダミーデータです。
  • wrangler.jsonc の値はプレースホルダです。そのままデプロイしても動作しません。
  • 内容の正確性・動作について、作者はいかなる責任も負いません。利用は自己責任でお願いします。

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选