2025年5月27日火曜日

Oracle REST Data ServicesのRESTサービスとしてStreamable HTTPを話すMCP Serverを実装してみる

2025年11月27日更新:新しいMCPのバージョンで動作確認を実施。

Model Context Protocolの2025-03-26版より、MCPクライアントとMCPサーバー間の通信にStreamable HTTPが使えるようになりました。それまでのSSE - Server Side Eventを使った通信はOracle Databaseで扱えなかったため、リモートMCPサーバーを直接データベース上に実装できませんでした。Streamable HTTPは通常のHTTPによる通信なので、リモートMCPサーバーをOracle REST Data ServicesのRESTサービスとして実装できそうです。

実際にOracle REST Data ServicesのRESTサービスとして、簡単なリモートMCPサーバーを実装してみました。以下の記事でツール呼び出し向けに実装しているget_schemaとrun_sqlを、リモートMCPサーバーのtools/listで一覧し、tools/callで呼び出せるようにします。

Qwen3 30B A3B MLXをMacのLM Studioで実行しAPEXアプリケーションからツール呼び出しを行う

ツールとして使用する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 Inspectorで動作確認を行なっています。Claude Desktopのカスタムコネクタとしても登録できますが、ORDSではOAuth2.1 + PKCEのAuthorization Codeフローを実装できないため、認証は外す必要があります。

MCP Inspectorで動作を行った際のGIF動画です。動作確認手順を紹介した後に、リモートMCPサーバーの構成手順を説明します。


MCP Inspectorを起動します。

npx @modelcontextprotocol/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 TypeStreamable HTTPを選択し、URLにORDSのRESTサービスのURLを入力します。ORDSのRESTサービスをOAuth2で保護している場合、Custom HeadersとしてAuthorizationBearerに加えてORDSのトークンURLを呼び出して得られたトークンのペアを設定します。

Connectをクリックすると、methodinitializeのリクエストが送信されます。(logging/setloglevelも送信されています)。そのレスポンスを受けて、接続が確立します。レスポンスに含まれるcapabilitiestoolsだけです。


Toolsタブを開き、List Toolsをクリックします。


methodtools/listのリクエストが送信され、そのレスポンスとしてget_schemaとrun_sqlが呼び出し可能なツールとして返されます。


ツールからget_schemaを選択して、Run Toolをクリックします。methodtools/callのリクエストが送信され、そのレスポンスとしてget_shemaの出力が返されます。


同様にツールからrun_sqlを選択します。パラメータのsqlに以下を入力し、Run Toolをクリックします。

select * from eba_countries_v

run_sqlが呼び出され、上記のSELECT文の出力が返されます。


Disconnectをクリックします。v0.13.0では切断されたのですが、v0.17.2では、何の変化も起こりません。


JavaScriptコンソールを開いて確認すると、HTTPのDELETEリクエストがステータス・コード400で失敗していることが確認できます。ORDSのRESTサービスとして登録したDELETEハンドラが呼び出される前にエラーとなっているため、アプリケーション側では対応できません。

調べた範囲では、MCP InspectorはDELETEリクエストを送信する際にContent-Typeヘッダーにapplication/jsonを設定しているのですが、リクエスト本体は送信していません。ORDSはContent-Typeヘッダーとしてapplication/jsonが設定されている場合、リクエスト本体を必要としているため、エラーが発生している模様です。結果としてMCPサーバーの切断ができません。これは、Claude Desktopにカスタムコネクタとして設定したときも同様で、切断できません。


以下よりMCP Inspectorから呼び出している、リモートMCPサーバーについて説明します。

MCPサーバーはAlways FreeのAutonomous Databaseに実装しています。MCP Inspectorで確認する範囲であれば、パブリックIPやDNSに登録されたホスト名、HTTPSといった条件は不要かと思います。

主にJSONRPCを扱うパッケージとしてMCP_HTTP_SERVER_PKGを作成しています。プロシージャORDS_HANDLERを、ORDSのRESTサービスから呼び出します。


MCPサーバーとして必要なinitializetools/listtools/callの処理は、パッケージMCP_SAMPLEに実装しています。


これらのパッケージやパッケージを登録する表MCP_HTTP_SERVERSの作成を、サポート・スクリプトとして含んだAPEXアプリケーションMCP Handlerのエクスポートを以下の置きました。
https://github.com/ujnak/apexapps/blob/master/exports/mcp_handler.zip

このアプリケーションをインストールして実行すると、以下のような画面が開きます。初期状態では、レポートは空です。


表MCP_HTTP_SERVERSに、RESTサービスとリモートMCPサーバーを紐づける情報を設定します。

ORDS URIにORDSのRESTサービスのURIを設定します。/ords/ORDS別名/モジュール名になります。リモートMCPサーバーのエンドポイントの末尾は/mcpになるので、テンプレート名は必ずmcpになります。ORDS URIからはmcpの部分は除きます。

methodのinitizliazeを受け付けたときに開始するセッションとして、APEXのセッションを流用しています。そのため、APEX_SESSION.CREATE_SESSIONに与えるアプリケーションIDとページIDを、Apex App IDApex Page IDに設定します。これはデフォルトで、このアプリ自体のアプリケーションIDとページIDを割り当てているので変更は不要です。

Package NameにリモートMCPサーバーの処理を実装しているパッケージ名を設定します。methodinitializeであれば、このパッケージに含まれるプロシージャINITIALIZEが呼び出されます。methodtools/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_schemarun_sqlをまとめたCountriesを選択しています。


続いて、ORDSのRESTサービスの設定を行います。こちらについてはインストール・スクリプトを先ほどのmcp_handler.zipに含めているので、APEXアプリケーションを実行するとRESTサービスも作成されます。

RESTサービスとしてsampleserverが作成されますが、作成した時点でOAuth2で保護されています。そのため、RESTサービスにアクセスするためのOAuthクライアントを作成します。

ORDSのOAuth2による保護については、以前に書いたこちらの記事「ORDS REST APIのOAuth2による保護とAPEXからの呼び出し」が参考になります。

以下のスクリプトを実行し、クライアントとしてmcp_clientを作成し、ロールMCP Server Roleを割り当てます。



Bearerトークンを取得するために、作成したOAuthクライアントmcp_clientclient_idclient_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サーバーのセッション管理はAPEXセッションを流用しているため、OAuthのCLIENT_IDとAPEXユーザーの紐付けが必要です。

OAuthのクライアントは名前をmcp_clientとしています。これと同名のAPEXユーザーを作成します。

ワークスペースの管理からユーザーとグループの管理を開きます。


ユーザーの作成をクリックします。


ユーザーが登録されていれば十分です。ユーザー名MCP_CLIENTとし、後は必須項目の電子メール・アドレスおよびパスワードを設定します。以上でユーザーを作成します。


ユーザーMCP_CLIENTが作成されます。APEXセッションは、このユーザーで開始されます。


以上でMCP Inspectorによる動作確認ができる状態になっています。

今回の記事は以上になります。

Oracle APEXのアプリケーション作成の参考になれば幸いです。