2025年12月17日水曜日

Role based JWT profileで保護したORDS REST APIにMCP Inspectorでアクセスする - Okta Integrator編

Oracle REST Data ServicesのRESTモジュールをRole based JWT profileで保護し、そのREST APIにMCP Inspectorでアクセスしてみます。

元にしている記事は以下の2本です。

OktaのOIDC認証にてAPEXアプリとそれから呼び出すORDSのREST APIを認証する

Oracle REST Data ServicesのRole based JWT profileによる保護を確認する
https://apexugj.blogspot.com/2025/12/protecting-ords-by-role-based-jwt-profile.html

記述が重複しますが、Okta Integratorでの確認作業について記載します。

OktaのAdmin Consoleよりグループを開き、グループを追加します。


グループの名前ORDSUsers説明は「ORDS REST APIへのアクセスを許可」と記述します。

保存をクリックします。


グループに再度アクセスし、グループ一覧を更新します。

作成したグループORDSUsersを開き、ユーザーを割り当てます。


ユーザーを割り当てをクリックします。


グループORDSUsersにユーザーを割り当てます。


ユーザーの割り当てを完了します。


アプリケーション・メニューのアプリケーションを開き、アプリ統合を作成します。


サインイン方法としてOIDC - OpenID Connectを選択します。OIDCを選択するとアプリケーションタイプの選択が現れます。アプリケーションタイプシングルページアプリケーションを選択します。

へ進みます。


アプリ統合を設定します。

アプリ統合名ORDS MCPとします。付与タイプリフレッシュトークンにチェックを入れます。

サインインリダイレクトURIにMCP InspectorのRedirect URLを設定します。ポート番号6274が使用されている場合は、ポート番号が異なるかもしれません。サインアウトリダイレクトURIは何を設定すべきか不明なので、x をクリックして削除しておきます。

割り当てアクセス制御として選択されたグループにアクセスを制限を選択し、選択されたグループに先ほど作成したグループORDSUsersを含めます。

以上を設定し保存します。


アプリ統合としてORDS MCPが作成されます。

クライアントIDはMCP InspectorのOAuth 2.0 FlowClient IDに設定する値です。メモしておきます。


MCP Inspectorの以下の場所です。


セキュリティAPIを開き、認証サーバーを追加します。(タブには認可サーバーと書いてあります)


追加する認証サーバーの名前ORDSMCPオーディエンスapi://ordsmcp(本来ordsmcpの部分はGUIDなど、世界で一意になる値を割り当てるのが望ましい)とします。説明としてAuthentication Server for ORDS MCPを入力します。

このオーディエンスの値が、ORDS_SECURITY.CREATE_JWT_PROFILEを呼び出す際の引数p_audienceの値になります。また、oauth-protected-resourceの属性resourceに指定する値になります。

以上で保存します。


追加された認証サーバーの設定タブを開き、メタデータURIを確認します。


このURLで取得できるファイルを、nginxが返す/.well-known/oauth-authorization-serverとして配置します。

URLをブラウザで開き、属性jwks_uriの値をコピーします。

この値はORDS_SECURITY.CREATE_JWT_PROFILEの引数p_jwk_urlの値になります。

また、属性issuerをの値をコピーします。この値はORDS_SECURITY.CREATE_JWT_PROFILEの引数p_issuerの値になります。


メタデータURLの末尾にある.well-known以下を除いた部分が、oauth-protected-resourceauthorization_serversの値になります。

以上でORDS REST APIの保護に必要な値が集まりました。

クレームタブを開き、クレームを追加します。


追加するクレームの名前rolesとします。トークンタイプに含めるアクセス・トークンです。

値タイプグループを選択し、フィルター次で始まるORDSを設定します。このフィルターにより、rolesクレームに先ほど作成したグループORDSUsersが含まれます。

クレームを無効化チェックせず含めるいずれかのスコープを選択します。

以上で作成します。


同様の手順でscopeクレームを追加します。

追加するクレームの名前scopeとします。トークンタイプに含めるアクセストークン値タイプを選択し、として以下を記述します。

String.replace(Arrays.toCsvString(access.scope),","," ")

クレームを無効化チェックせず含めるいずれかのスコープを選択します。

以上で作成します。


rolesクレームとscopeクレームが作成されました。


アクセスポリシーを開き、ポリシーを追加します。


ポリシーの名前ORDS MCPとします。説明に「ORDS MCP」と記述し、次に割り当てる次のクライアントを選択し、クライアントにORDS MCPを含めます。

以上でポリシーを作成します。


アクセスポリシーとしてORDS MCPが作成されました。

ルールを追加します。


ルール名Defaultとします。デフォルトの設定は変更せず(最低限コア付与認証コードチェックされていれば動くはず)、ルールを作成します。


アクセス・ポリシーORDS MCPとルールDefaultが作成されます。


以上でOktaの設定は完了です。

ロール・ベースJWTプロファイルを設定します。

ORDS_SECURITY.CREATE_JWT_PROFILEを実行します。それぞれの引数には、今まで集めた設定値を割り当てます。
begin
    ords_security.delete_jwt_profile;
    ords_security.create_jwt_profile(
        p_issuer => 'https://integrator-********.okta.com/oauth2/******************'
        ,p_audience => 'api://ordsmcp'
        ,p_jwk_url => 'https://integrator-********.okta.com/oauth2/******************/v1/keys'
        ,p_role_claim_name => '/roles'
    );
end;
/

ビューUSER_ORDS_JWT_PROFILEを検索し、設定した内容を確認します。

select issuer,audience,jwk_url,role_claim_name from user_ords_jwt_profile


以下のスクリプトを実行し、Oracle REST Data Servicesに権限oracle.example.mcpを作成(すでに存在する場合は再定義)します。ロールとしてORDSUsersを作成し、RESTモジュールsampleserverを保護します。
declare
    l_roles    owa.vc_arr;
    l_modules  owa.vc_arr;
    l_patterns owa.vc_arr;
begin
    ords.create_role(
        p_role_name => 'ORDSUsers'
    );
    l_modules(1) := 'sampleserver';
    l_roles(1)   := 'ORDSUsers';
    ords.define_privilege(
        p_privilege_name => 'oracle.example.mcp',
        p_label          => 'Priviledge for MCP',
        p_roles          => l_roles,
        p_modules        => l_modules,
        p_patterns       => l_patterns    -- no assignment
    );
end;
/

nginxを実行しているインスタンスに接続し、/.well-known/以下を設定します。

rootユーザーで作業します。

sudo -s

[opc@apex ~]$ sudo -s

[root@apex opc]# 


nginxのドキュメント・ルートに移動します。

cd /usr/share/nginx/html

[root@apex opc]# cd /usr/share/nginx/html

[root@apex html]# 


ディレクトリ.well-knownがすでに存在すれば名称を変えて保存し、新たに.well-knownを作成します。

mv .well-known well-known.bak
mkdir .well-known

[root@apex html]# mv .well-known well-known.bak

[root@apex html]# mkdir .well-known

[root@apex html]# 


リモートMCPサーバーのアクセスパスが/ords/apexdev/sampleserver/mcpなので、oauth-protected-resourceとして記述する内容は.well-known以下の.well-known/oauth-protected-resource/ords/apexdev/sampleserver/mcpに記述します。Entra IDでは、このファイルの位置が.well-known以下の.well-known/ords/apexdev/sampleserver/mcpでした。

今回の実装ではサーバーが401 Unauthorizedを返すときにWWW-Authenticateヘッダーを返しません。そのため、oauth-protected-resourceの位置が定まらないのかもしれません。

ファイル.well-known/oauth-protected-resource/ords/apexdev/sampleserver/mcpを作成し、以下を記述します。
{
  "resource": "api://ordsmcp",
  "authorization_servers": {
    ["https://integrator-*******.okta.com/oauth2/********************/"]
  }
}
authorization_serversの設定で、JSON配列を{}で囲んでJSONオブジェクトにしています。構文的に間違っているように思いますが、こうしないとMCP Inspectorがauthorization_serversを認識しません。

メタデータをダウンロードし、.well-known以下に配置します。

curl -OL https://integrator-********.okta.com/oauth2/*******************/.well-known/oauth-authorization-server
mv openid-configuration .well-known/


[root@apex html]# curl -OL https://integrator-*******.okta.com/oauth2/*******************/.well-known/oauth-authorization-server

  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current

                                 Dload  Upload   Total   Spent    Left  Speed

100  2624    0  2624    0     0   6777      0 --:--:-- --:--:-- --:--:--  6762

[root@apex html]# mv oauth-authorization-server .well-known/

[root@apex html]# 


以上で全体の設定が完了しました。

MCP Inspectorを起動します。

npx @modelcontextprotocol/inspector

Transport TypeStreamable HTTPを選択し、URLにORDSのRESTモジュールとして実装されているサンプルのMCPサーバーのURLを設定します。

https://ホスト名/ords/apexdev/sampleserver/mcp

OAuth 2.0 FlowClient IDに、Oktaに作成した統合アプリORDS MCPクライアントIDを設定します。

Connectをクリックし、認証プロセスを開始します。


Oktaのサインイン画面に遷移します。ユーザー名を入力し、次へ進みます。


このユーザーはOkta Verifyを構成しています。

Okta Verifyのコードを入力し確認します。


パスワードを入力し確認します。


ユーザー認証に成功しました。


以上でRole based JWT profileで保護したORDS REST APIを、MCP Inspectorでアクセスできました。

Oktaによるユーザー認証では、スコープの指定が不要でした。

以下より、ブラウザ版のChatGPTで、このMCPサーバーをアプリとして作成してみます。ChatGPTに組み込むアプリはリソースとしてHTMLを返す必要があり、このMCPサーバーは要件を満たしてはいませんが、認証の確認はできます。

ChatGPTで認証できるように、統合アプリORDS MCPサインインリダイレクトURIとして、以下を追加します。

https://chatgpt.com/connector_platform_oauth_redirect


ChatGPTのプランに依存すると思いますが(Plusプランで確認しています)ブラウザ版ChatGPTを開発者モードにすると、設定アプリとコネクター高度な設定が表示されます。

アプリを作成するをクリックします。


新しいアプリの名前My Oracle Appとします。説明として「Oracleデータベースに接続しSELECT文を実行します」を記述します。

MCPサーバーのURLは、MCP Inspectorに設定したURLと同じです。

https://ホスト名/ords/apexdev/sampleserver/mcp

認証OAuthを選択し、OAuthクライアントIDに、Oktaに作成した統合アプリORDS MCPのクライアントIDを設定します。

理解したうえで、続行しますチェックし、作成するをクリックします。


Oktaの認証プロセスが開始します。サインイン手順はMCP Inspectorと同じなので省略します。


サインインが完了すると、作成したアプリMy Oracle Appが接続します。

アクションとしてツールのget_schemaやrun_sqlが見えているので、OAuthによる認証は成功しています。


ここで使用したMCPサーバーはChatGPTアプリとしての実装は行なっていないため、何かができるわけではありませんが、OAuthで認証できることは確認できました。

Claude Desktopではカスタムコネクタとして追加します。

Claude Desktopで認証できるように、統合アプリORDS MCPサインインリダイレクトURIとして、以下を追加します。

https://claude.ai/api/mcp/auth_callback


Claude Desktopでも、ChatGPTと同じ設定でカスタムコネクタを追加します。


追加したカスタムコネクタMy Oracle App連携させます。


Oktaでの認証プロセスが完了すると、連携/連携させるというボタンが設定に変わります。


設定をクリックすると、コネクタとして利用できるツールが確認できます。


Claude DesktopのカスタムコネクタはChatGPTとは異なり、リモートMCPサーバーとして利用できるようです。

そのため、チャットから呼び出すことができました。


今回の記事は以上です。