実際にOracle REST Data ServicesのRESTサービスとして、簡単なリモートMCPサーバーを実装してみました。以下の記事でツール呼び出し向けに実装しているget_schemaとrun_sqlを、リモートMCPサーバーのtools/listで一覧し、tools/callで呼び出せるようにします。
ツールとして使用するget_schemaとrun_sqlの実装や設定をワークスペースに作成するため、以下のAPEXアプリケーションをインポートします。表OPENAI_TOOLSとファンクションget_schema、run_sqlが作成され、属性などの定義情報が表に挿入されます。
https://github.com/ujnak/apexapps/blob/master/exports/chat-with-generative-ai-hc-242.zip
現時点でリモートMCPサーバーを直接呼ぶには、ClaudeのMaxプランに入るか、mcp-remoteを中間に入れる方法しか見つけられませんでした。mcp-remoteではなぜか上手く通信できず、また、ClaudeのMaxプランは高価です。
そういった理由で、とりあえず、MCP Inspectorで動作確認を行なっています。以下のGIF動画ように呼び出しています。
MCP Inspectorを起動します。
% npx @modelcontextprotocol/inspector
Starting MCP inspector...
⚙️ Proxy server listening on port 6277
🔍 MCP Inspector is up and running at http://127.0.0.1:6274 🚀
MCP Inspector is up and running at ... に続いて表示されているURLに、ブラウザより接続します。
Transport TypeにStreamable HTTPを選択し、URLにORDSのRESTサービスのURLを入力します。ORDSのRESTサービスをOAuth2で保護している場合、Header NameにAuthorization、Bearer TokenにORDSのトークンURLを呼び出して得られたトークンを設定します。
Connectをクリックすると、methodがinitializeのリクエストが送信されます。そのレスポンスを受けて、接続が確立します。レスポンスに含まれるcapabilitiesはtoolsだけです。
Toolsタブを開き、List Toolsをクリックします。
methodがtools/listのリクエストが送信され、そのレスポンスとしてget_schemaとrun_sqlが呼び出し可能なツールとして返されます。
ツールからget_schemaを選択して、Run Toolをクリックします。methodがtools/callのリクエストが送信され、そのレスポンスとしてget_shemaの出力が返されます。
同様にツールからrun_sqlを選択します。パラメータのsqlに以下を入力し、Run Toolをクリックします。
select * from eba_countries_v
run_sqlが呼び出され、上記のSELECT文の出力が返されます。
Disconnectをクリックします。以上で最初の状態に戻ります。
MCPサーバーはAlways FreeのAutonomous Databaseに実装しています。MCP Inspectorで確認する範囲であれば、パブリックIPやDNSに登録されたホスト名、HTTPSといった条件は不要かと思います。
主にJSONRPCを扱うパッケージとしてMCP_HTTP_SERVER_PKGを作成しています。プロシージャORDS_HANDLERを、ORDSのRESTサービスから呼び出します。
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
create or replace package mcp_http_server_pkg | |
as | |
/** | |
* JSON-RPC標準エラー・コード | |
*/ | |
C_PARSE_ERROR constant number := -32700; | |
C_INVALID_REQUEST constant number := -32600; | |
C_METHOD_NOT_FOUND constant number := -32601; | |
C_INVALID_PARAMS constant number := -32602; | |
C_INTERNAL_ERROR constant number := -32603; | |
/** | |
* ORDSのPOSTハンドラに設定してMCP Serverの処理を呼び出す。 | |
* | |
* @param p_script_name owa_util.get_cgi_env('SCRIPT_NAME')で取得されるスクリプト名。 | |
* @param p_username ORDS RESTハンドラ内の:current_userで取得されるユーザ名。 | |
* @param p_request :bodyで取得されるリクエストボディ。実態はJSONRPCのリクエスト。 | |
* @param p_response レスポンスとなるBLOB。実態はJSONRPCのレスポンス。notificationの場合はnull。 | |
* @param p_session_id apex_session.create_sessionで作成したセッションID。 | |
* @param p_status_code レスポンスのHTTPステータスコード。通常は200。 | |
*/ | |
procedure ords_handler( | |
p_script_name in varchar2 | |
,p_username in varchar2 | |
,p_request in blob | |
,p_response out blob | |
,p_session_id out varchar2 | |
,p_status_code out number | |
); | |
end mcp_http_server_pkg; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
create or replace package body mcp_http_server_pkg as | |
C_MCP_SESSION_ID_HEADER constant varchar2(16) := 'Mcp-Session-Id'; | |
/** | |
* 正常なレスポンスからメッセージを作成する。 | |
*/ | |
function create_success_response( | |
p_id in number | |
,p_result in clob | |
) | |
return blob | |
as | |
l_response_json json_object_t; | |
begin | |
l_response_json := json_object_t(); | |
l_response_json.put('jsonrpc', '2.0'); | |
l_response_json.put('id', p_id); | |
l_response_json.put('result', json_object_t(p_result)); | |
return l_response_json.to_blob; | |
end create_success_response; | |
/** | |
* エラーからレスポンス・メッセージを作成する。 | |
*/ | |
function create_error_response( | |
p_id in number | |
,p_code in number | |
,p_message in varchar2 | |
,p_data in clob default null | |
) | |
return blob | |
as | |
l_response_json json_object_t; | |
l_error_json json_object_t; | |
begin | |
l_response_json := json_object_t(); | |
l_response_json.put('jsonrpc', '2.0'); | |
l_response_json.put('id', p_id); | |
l_error_json := json_object_t(); | |
l_error_json.put('code', p_code); | |
l_error_json.put('message', p_message); | |
if p_data is not null then | |
l_error_json.put('data', json_object_t(p_data)); | |
end if; | |
l_response_json.put('error', l_error_json); | |
return l_response_json.to_blob; | |
end create_error_response; | |
/** | |
* 動的時に実行するSQLを生成する。 | |
*/ | |
function create_dynamic_sql( | |
p_package_name in varchar2 | |
,p_method in varchar2 | |
) | |
return varchar2 | |
as | |
l_sql varchar2(4000); | |
begin | |
l_sql := 'begin ' || p_package_name || '.' || replace(p_method,'/','_') || '('; | |
l_sql := l_sql || ':username, :p_params, :p_context, :p_result, :p_error, :p_status_code); end;'; | |
return l_sql; | |
end create_dynamic_sql; | |
/** | |
* ORDSのPOSTハンドラに設定してMCP Serverの処理を呼び出す。 | |
*/ | |
procedure ords_handler( | |
p_script_name in varchar2 | |
,p_username in varchar2 | |
,p_request in blob | |
,p_response out blob | |
,p_session_id out varchar2 | |
,p_status_code out number | |
) | |
as | |
/* | |
* JSONRPCリクエスト。 | |
*/ | |
l_request_json json_object_t; | |
l_id number; | |
l_method varchar2(128); | |
l_params clob; | |
l_version varchar2(16); | |
l_username varchar2(128); | |
/* | |
* MCPセッションID。 | |
*/ | |
l_session_id varchar2(128); | |
/* | |
* MCPサーバーの実体であるハンドラの設定。 | |
*/ | |
r_mcp_http_server mcp_http_servers%rowtype; | |
/* | |
* MCPサーバーのmethodの動的な呼び出し。 | |
*/ | |
l_sql varchar2(4000); | |
l_result clob; | |
l_error clob; | |
l_status_code number; | |
begin | |
/* | |
* ユーザー名の取得。 | |
*/ | |
if p_username is null then | |
select sys_context('USERENV', 'SESSION_USER') into l_username from dual; | |
else | |
l_username := p_username; | |
end if; | |
/* | |
* Mcp-Session-Idヘッダを取得する。 | |
*/ | |
p_session_id := owa_util.get_cgi_env(C_MCP_SESSION_ID_HEADER); | |
/* | |
* リクエストの検証とパースを行う。id, method, paramsを取り出す。 | |
*/ | |
begin | |
l_request_json := json_object_t(p_request); | |
/* | |
* リクエストのidを取得する。ClaudeのMCP Inspectorでは数値。 | |
* notificationの場合はnullなのでチェック不要。 | |
*/ | |
l_id := l_request_json.get_number('id'); | |
/* JSON-RPCのバージョンを確認する。 */ | |
l_version := l_request_json.get_string('jsonrpc'); | |
if l_version is null or l_version != '2.0' then | |
p_status_code := 400; | |
p_response := create_error_response( | |
p_id => null, | |
p_code => C_INVALID_REQUEST, | |
p_message => 'Invalid JSON-RPC version. Expected "2.0".' | |
); | |
return; | |
end if; | |
/* | |
* methodを取得する。methodは必須。 | |
*/ | |
l_method := l_request_json.get_string('method'); | |
if l_method is null then | |
p_status_code := 400; | |
p_response := create_error_response( | |
p_id => null, | |
p_code => C_INVALID_REQUEST, | |
p_message => 'Method is required in the request.' | |
); | |
return; | |
end if; | |
/* | |
* paramsが必須かどうかはmethodに依存。 | |
*/ | |
l_params := l_request_json.get_object('params').to_clob(); | |
exception | |
when others then | |
p_status_code := 400; | |
p_response := create_error_response( | |
p_id => null, | |
p_code => C_PARSE_ERROR, | |
p_message => 'Invalid JSON format in request body. sqlerrm: ' || sqlerrm | |
); | |
return; | |
end; | |
/* | |
* ハンドラの設定を取得する。 | |
*/ | |
begin | |
select * into r_mcp_http_server from mcp_http_servers where ords_uri = p_script_name; | |
exception | |
when no_data_found then | |
p_status_code := 400; | |
p_response := create_error_response( | |
p_id => l_id, | |
p_code => C_INVALID_REQUEST, | |
p_message => 'No MCP Server registered: ' || p_script_name | |
); | |
return; | |
end; | |
/* | |
* メソッドがinitializeの場合は新規にセッションを開始する。 | |
*/ | |
if l_method = 'initialize' then | |
apex_session.create_session( | |
p_app_id => r_mcp_http_server.apex_app_id | |
,p_page_id => r_mcp_http_server.apex_page_id | |
,p_username => l_username | |
); | |
p_session_id := v('APP_SESSION'); | |
else | |
/* セッションIDが取得できたらセッションをアタッチする。 */ | |
if p_session_id is not null then | |
apex_session.attach( | |
p_app_id => r_mcp_http_server.apex_app_id | |
,p_page_id => r_mcp_http_server.apex_page_id | |
,p_session_id => p_session_id | |
); | |
else | |
p_status_code := 400; | |
p_response := create_error_response( | |
p_id => l_id, | |
p_code => C_INVALID_REQUEST, | |
p_message => 'Mcp-Session-Id is required for method: ' || l_method | |
); | |
return; | |
end if; | |
end if; | |
/* | |
* デバッグレベルを設定する。何故かこの処理は効いていない。 | |
*/ | |
if r_mcp_http_server.log_level is not null then | |
apex_debug.enable(r_mcp_http_server.log_level); | |
end if; | |
/* | |
* MCP Serverのハンドラを呼び出す。 | |
*/ | |
l_sql := create_dynamic_sql(r_mcp_http_server.package_name, l_method); | |
execute immediate l_sql | |
using in l_username, in l_params, in r_mcp_http_server.tool_set | |
,out l_result, out l_error, out l_status_code; | |
p_status_code := l_status_code; | |
if l_id is not null then | |
/* | |
* idがある場合は通常のリクエスト。 | |
*/ | |
if l_error is not null then | |
/* | |
* エラーが発生した場合はエラーレスポンスを返す。 | |
*/ | |
p_response := create_error_response( | |
p_id => l_id, | |
p_code => C_INTERNAL_ERROR, | |
p_message => 'Error in MCP Server: ' || l_error | |
); | |
else | |
/* | |
* 正常に処理が完了した場合はレスポンスを返す。 | |
*/ | |
p_response := create_success_response( | |
p_id => l_id | |
,p_result => l_result | |
); | |
end if; | |
end if; | |
exception | |
when others then | |
/* | |
* 例外が発生した場合はエラーレスポンスを返す。 | |
*/ | |
p_status_code := 500; | |
p_response := create_error_response( | |
p_id => l_id, | |
p_code => C_INTERNAL_ERROR, | |
p_message => 'Internal Server Error: ' || sqlerrm | |
); | |
end ords_handler; | |
end mcp_http_server_pkg; |
MCPサーバーとして必要なinitialize、tools/list、tools/callの処理は、パッケージMCP_SAMPLEに実装しています。
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
create or replace package mcp_sample as | |
procedure initialize( | |
p_username in varchar2 | |
,p_params in clob | |
,p_context in varchar2 | |
,p_result out clob | |
,p_error out clob | |
,p_status_code out number | |
); | |
procedure notifications_initialized( | |
p_username in varchar2 | |
,p_params in clob | |
,p_context in varchar2 | |
,p_result out clob | |
,p_error out clob | |
,p_status_code out number | |
); | |
procedure tools_list( | |
p_username in varchar2 | |
,p_params in clob | |
,p_context in varchar2 | |
,p_result out clob | |
,p_error out clob | |
,p_status_code out number | |
); | |
procedure tools_call( | |
p_username in varchar2 | |
,p_params in clob | |
,p_context in varchar2 | |
,p_result out clob | |
,p_error out clob | |
,p_status_code out number | |
); | |
end mcp_sample; | |
/ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
create or replace package body mcp_sample as | |
procedure initialize( | |
p_username in varchar2 | |
,p_params in clob | |
,p_context in varchar2 | |
,p_result out clob | |
,p_error out clob | |
,p_status_code out number | |
) | |
as | |
l_request_json json_object_t; | |
l_result_json json_object_t; | |
l_response clob; | |
l_response_json json_object_t; | |
l_id varchar2(128); | |
l_si json_object_t; | |
begin | |
p_status_code := 200; | |
p_error := null; | |
/* 固定でcapabilitiesを返す。 */ | |
l_result_json := json_object_t(q'~{ | |
"protocolVersion": "2025-03-26", | |
"capabilities": { | |
"logging": {}, | |
"prompts": {}, | |
"resources": {}, | |
"tools": { | |
"listChanged": true | |
} | |
}, | |
"serverInfo": { | |
"name": "mcp_sample", | |
"version": "0.1.0" | |
} | |
}~'); | |
/* | |
l_si := l_result_json.get_object('serverInfo'); | |
l_si.put('username', p_username); | |
*/ | |
p_result := l_result_json.to_clob(); | |
end initialize; | |
procedure notifications_initialized( | |
p_username in varchar2 | |
,p_params in clob | |
,p_context in varchar2 | |
,p_result out clob | |
,p_error out clob | |
,p_status_code out number | |
) | |
as | |
begin | |
/* 通知なのでp_status_codeは204を返す。 */ | |
p_status_code := 204; | |
p_error := null; | |
p_result := null; | |
end notifications_initialized; | |
procedure tools_list( | |
p_username in varchar2 | |
,p_params in clob | |
,p_context in varchar2 | |
,p_result out clob | |
,p_error out clob | |
,p_status_code out number | |
) | |
as | |
l_tool_json json_object_t; | |
l_tools_arr json_array_t; | |
l_input_schema_json json_object_t; | |
l_result_json json_object_t; | |
begin | |
/* | |
* 表OPENAI_TOOLSは、OpenAI Chat Completions APIのツール呼び出し向けに作成したもの。 | |
* それを流用している。 | |
* OpenAIがargumentsのところをAnthropicではinputSchemaになっている。 | |
*/ | |
l_tools_arr := json_array_t(); | |
for r in ( | |
select tool_name, description, parameters from openai_tools where tool_set = p_context and tool_type = 'function' | |
) | |
loop | |
l_tool_json := json_object_t(); | |
l_tool_json.put('name', r.tool_name); | |
l_tool_json.put('description', r.description); | |
l_input_schema_json := json_object_t(r.parameters); | |
l_tool_json.put('inputSchema', l_input_schema_json); | |
l_tools_arr.append(l_tool_json); | |
end loop; | |
l_result_json := json_object_t(); | |
l_result_json.put('tools', l_tools_arr); | |
p_result := l_result_json.to_clob(); | |
p_error := null; | |
p_status_code := 200; | |
end tools_list; | |
procedure tools_call( | |
p_username in varchar2 | |
,p_params in clob | |
,p_context in varchar2 | |
,p_result out clob | |
,p_error out clob | |
,p_status_code out number | |
) | |
as | |
l_params json_object_t; | |
l_name varchar2(128); | |
l_args_obj json_object_t; | |
l_args clob; | |
l_sql varchar2(256); | |
l_out clob; | |
l_result_json json_object_t; | |
l_content_arr json_array_t; | |
l_out_obj json_object_t; | |
l_response_json json_object_t; | |
l_error_json json_object_t; | |
begin | |
/* | |
* nameは必ずなければならないので、p_paramsがnullは許容しない。 | |
*/ | |
if p_params is null then | |
l_error_json := json_object_t(); | |
l_error_json.put('code', mcp_http_server_pkg.C_INVALID_PARAMS); | |
l_error_json.put('message', 'Invalid parameters: p_params is null'); | |
p_error := l_error_json.to_clob(); | |
p_result := null; | |
p_status_code := 400; | |
return; | |
end if; | |
/* | |
* パラメータをパースしてnameとargumentsを取得する。 | |
*/ | |
l_params := json_object_t.parse(p_params); | |
l_name := l_params.get_string('name'); | |
/* | |
* nameは必須なので、nullの場合はエラーを返す。 | |
*/ | |
if l_name is null then | |
l_error_json := json_object_t(); | |
l_error_json.put('code', mcp_http_server_pkg.C_INVALID_PARAMS); | |
l_error_json.put('message', 'Invalid parameters: name is required'); | |
p_error := l_error_json.to_clob(); | |
p_result := null; | |
p_status_code := 400; | |
return; | |
end if; | |
/* | |
* 引数argumentsはオプション。 | |
*/ | |
l_args_obj := l_params.get_object('arguments'); | |
if l_args_obj is not null then | |
l_args := l_args_obj.to_clob(); | |
end if; | |
/* | |
* ツールの実行。 | |
*/ | |
l_sql := 'begin :a := ' || l_name || '(:b); end;'; | |
execute immediate l_sql using out l_out, in l_args; | |
/* 出力をフォーマットする。 */ | |
l_result_json := json_object_t(); | |
l_content_arr := json_array_t(); | |
l_out_obj := json_object_t(); | |
l_out_obj.put('type', 'text'); | |
l_out_obj.put('text', l_out); | |
l_content_arr.append(l_out_obj); | |
l_result_json.put('content', l_content_arr); | |
l_result_json.put('isError', false); | |
p_result := l_result_json.to_clob(); | |
p_error := null; | |
p_status_code := 200; | |
exception | |
when others then | |
l_error_json := json_object_t(); | |
l_error_json.put('code', mcp_http_server_pkg.C_INTERNAL_ERROR); | |
l_error_json.put('message', 'Error in tools_call: ' || sqlerrm); | |
p_error := l_error_json.to_clob(); | |
p_result := null; | |
p_status_code := 500; | |
end tools_call; | |
end mcp_sample; | |
/ |
これらのパッケージやパッケージを登録する表MCP_HTTP_SERVERSの作成を、サポート・スクリプトとして含んだAPEXアプリケーションMCP Handlerのエクスポートを以下の置きました。
https://github.com/ujnak/apexapps/blob/master/exports/mcp_handler.zip
このアプリケーションをインストールして実行すると、以下のような画面が開きます。初期状態では、レポートは空です。
ORDS URIにORDSのRESTサービスのURIを設定します。/ords/ORDS別名/モジュール名になります。リモートMCPサーバーのエンドポイントの末尾は/mcpになるので、テンプレート名は必ずmcpになります。ORDS URIからはmcpの部分は除きます。
methodのinitizliazeを受け付けたときに開始するセッションとして、APEXのセッションを流用しています。そのため、APEX_SESSION.CREATE_SESSIONに与えるアプリケーションIDとページIDを、Apex App ID、Apex Page IDに設定します。これはデフォルトで、このアプリ自体のアプリケーションIDとページIDを割り当てているので変更は不要です。
Package NameにリモートMCPサーバーの処理を実装しているパッケージ名を設定します。methodがinitializeであれば、このパッケージに含まれるプロシージャINITIALIZEが呼び出されます。methodがtools/listであれば、/を_に置き換えたプロシージャTOOLS_LISTを呼び出します。tools/callはプロシージャTOOLS_CALLが呼び出されます。MCPサーバーにpromptsやresourcesは実装していませんが、initializeのレスポンスのcapabilitiesに含めて、パッケージにpromptsやresourcesをプロシージャとして実装することで、フレームワークを変更することなく機能を追加できます。
Log LevelはAPEX_DEBUG.ENABLEに与える数値です。しかし、何故かAPEX_DEBUG.INFOの出力をビューAPEX_DEBUG_MESSAGESから確認できていません。
Tool Setの指定は、Chat with Generative AIのアプリケーションのToolsに登録した、Tool Setを指定します。今回のテストでは、ファンクションget_schemaとrun_sqlをまとめたCountriesを選択しています。
続いて、ORDSのRESTサービスの設定を行います。こちらについてはインストール・スクリプトを先ほどのmcp_handler.zipに含めているので、APEXアプリケーションを実行するとRESTサービスも作成されます。
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- Generated by ORDS REST Data Services 25.1.0.r1001652 | |
-- Schema: WKSP_APEXDEV Date: 火 5月 27 08:00:03 2025 | |
-- | |
DECLARE | |
l_roles OWA.VC_ARR; | |
l_modules OWA.VC_ARR; | |
l_patterns OWA.VC_ARR; | |
BEGIN | |
/* | |
* ORDS.ENABLE_SCHEMAは実施済みが前提。 | |
* | |
ORDS.ENABLE_SCHEMA( | |
p_enabled => TRUE, | |
p_schema => 'WKSP_APEXDEV', | |
p_url_mapping_type => 'BASE_PATH', | |
p_url_mapping_pattern => 'apexdev', | |
p_auto_rest_auth => FALSE); | |
*/ | |
ORDS.DEFINE_MODULE( | |
p_module_name => 'sampleserver', | |
p_base_path => '/sampleserver/', | |
p_items_per_page => 25, | |
p_status => 'PUBLISHED', | |
p_comments => NULL); | |
ORDS.DEFINE_TEMPLATE( | |
p_module_name => 'sampleserver', | |
p_pattern => 'mcp', | |
p_priority => 0, | |
p_etag_type => 'HASH', | |
p_etag_query => NULL, | |
p_comments => NULL); | |
ORDS.DEFINE_HANDLER( | |
p_module_name => 'sampleserver', | |
p_pattern => 'mcp', | |
p_method => 'POST', | |
p_source_type => 'plsql/block', | |
p_mimes_allowed => 'application/json', | |
p_comments => NULL, | |
p_source => | |
'declare | |
l_response blob; | |
l_session_id varchar2(128); | |
l_status_code number; | |
l_request blob; | |
begin | |
l_request := :body; | |
mcp_http_server_pkg.ords_handler( | |
p_script_name => owa_util.get_cgi_env(''SCRIPT_NAME'') | |
,p_username => :current_user | |
,p_request => l_request | |
,p_response => l_response | |
,p_session_id => l_session_id | |
,p_status_code => l_status_code | |
); | |
/* | |
* レスポンスを呼び出し元に返す。 | |
*/ | |
:status_code := l_status_code; | |
sys.htp.init; | |
sys.htp.p(''Content-Type: application/json''); | |
if l_session_id is not null then | |
sys.htp.p(''Mcp-Session-Id: '' || l_session_id); | |
end if; | |
if l_response is not null and dbms_lob.getlength(l_response) > 0 then | |
sys.htp.p(''Content-Length: '' || dbms_lob.getlength(l_response)); | |
sys.owa_util.http_header_close; | |
sys.wpg_docload.download_file(l_response); | |
else | |
sys.owa_util.http_header_clo' || 'se; | |
end if; | |
commit; | |
end;'); | |
ORDS.DEFINE_HANDLER( | |
p_module_name => 'sampleserver', | |
p_pattern => 'mcp', | |
p_method => 'DELETE', | |
p_source_type => 'plsql/block', | |
p_mimes_allowed => NULL, | |
p_comments => NULL, | |
p_source => | |
'declare | |
l_session_id varchar2(128); | |
begin | |
l_session_id := owa_util.get_cgi_env(''Mcp-Session-Id''); | |
if l_session_id is not null then | |
apex_session.delete_session(l_session_id); | |
end if; | |
:status_code := 200; | |
end if;'); | |
ORDS.CREATE_ROLE(p_role_name => 'MCP Server Role'); | |
l_roles(1) := 'MCP Server Role'; | |
l_modules(1) := 'sampleserver'; | |
ORDS.DEFINE_PRIVILEGE( | |
p_privilege_name => 'oracle.example.mcp', | |
p_roles => l_roles, | |
p_patterns => l_patterns, | |
p_modules => l_modules, | |
p_label => 'MCP Server', | |
p_description => NULL, | |
p_comments => NULL); | |
l_roles.DELETE; | |
l_modules.DELETE; | |
l_patterns.DELETE; | |
COMMIT; | |
END; |
RESTサービスとしてsampleserverが作成されますが、作成した時点でOAuth2で保護されています。そのため、RESTサービスにアクセスするためのOAuthクライアントを作成します。
以下のスクリプトを実行し、クライアントとしてmcp_clientを作成し、ロールMCP Server Roleを割り当てます。
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
begin | |
-- OAuth2 clientとしてmcp_clientを作成します。 | |
oauth.create_client( | |
p_name => 'mcp_client' | |
,p_grant_type => 'client_credentials' | |
,p_description => 'Sample MCP Client' | |
,p_support_email => 'yuXX@acme.com' | |
,p_privilege_names => NULL | |
); | |
-- mcp_clientにロールMCP Server Roleを割り当てる。 | |
oauth.grant_client_role( | |
p_client_name => 'mcp_client' | |
,p_role_name => 'MCP Server Role' | |
); | |
end; |
Bearerトークンを取得するために、作成したOAuthクライアントmcp_clientのclient_idとclient_secretを検索します。
以下のSELECT文を実行します。
select name, client_id, client_secret from user_ords_clients where name = 'mcp_client'
取り出したclient_idおよびclient_secretを使って、ORDSのトークンURLを呼び出します。ORDSのトークンURLは、ORDS別名以降に/oauth/tokenを加えたものになります。
curl -X POST https://[ホスト名]/ords/[ORDS別名]/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-u "[client_id]:[client_secret]" \
-d "grant_type=client_credentials"
% curl -X POST https://***********-apexdev.adb.us-ashburn-1.oraclecloudapps.com/ords/apexdev/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-u "rJc************Qjg..:-FnhL***********eg.." \
-d "grant_type=client_credentials"
{"access_token":"KhDE2WU*********38oOg","token_type":"bearer","expires_in":3600}
%
ベーシック認証の情報としてclient_idとclient_secretを与えてトークンURLを呼び出すと、access_tokenが返されます。access_tokenの値をコピーし、MCP InspectorのBearer Tokenに設定します。
リモートMCPサーバーの保護についてきちんと調べ切れていないのですが、この方法による保護はユーザーが固定になり、アプリケーションの利用者に紐づきません。おそらく、こちらの記事「ORDS 23.3で追加されたOAUTH.CREATE_JWT_PROFILEを使ってRESTサービスを保護する」で使用しているOAUTH.CREATE_JWT_PROFILEによる保護が必要なのではないかと思われます。
実際にはClaude DesktopやClaude.aiに統合した上でテストを行う必要はありますが、何とかなりそうな印象はあります。ただし、アプリケーションを利用しているユーザーに認証を紐づけるのは、難度が高そうだとは思います。
今回の記事は以上になります。
Oracle APEXのアプリケーション作成の参考になれば幸いです。
完