MCP Apps(SEP-1865)はiframe中に描画されるため、外部URLへのアクセスが制限されています。そのため、メソッドresources/readのtextとして読み出すUIリソースは、HTMLにJavaScriptとCSSをバンドルして1つにまとめることが推奨されています。公式ドキュメントではVite(vite-plugin-singlefile)が紹介されていますが、Oracle APEXでは使用できません。
その代わりにAPEXのページ・レンダリングを流用して、JavaScriptとCSSをHTMLにバンドルしてみます。ただし、ESモジュールext-appsをバンドルするのは手間がかかりすぎるため、これだけはresources/readが返す_meta属性に、以下のcsp指定を含めることでJavaScriptの読み込みと実行を許可しています。
{ "ui": { "csp": { "resourceDomains": ["https://cdn.jsdelivr.net"] }}}
以下よりAPEXのページ・レンダリングの流用手順について説明します。
今回の作業に使用するAPEXアプリケーションとしてsampleserverが作成済みと仮定します。
MCP App向けのHTMLの生成に使用するページ・テンプレートを作成します。
共有コンポーネントを開きます。
ユーザー・インターフェースのテンプレートを開きます。
タイプが
ページの
テンプレートのうち、装飾の最も少ない
Minimal (No Navigation)を
コピーし、MCP App向けに改修します。
新規テンプレート名は
MCP2、
新規テンプレート識別子も
MCP2とします。(今回の記事を書くにあたって、すでにMCPを使っているためMCP2にしています)
コピーをクリックします。
テンプレートMCP2が作成されました。これを編集するために開きます。
ページ・テンプレートの定義のヘッダー、本体、フッターを以下に置き換えます。HTMLのレンダリング先はブラウザではなく、単にリージョンに配置したHTMLとページに定義したJavaScriptとCSSが、ひとつのページのHTMLとして出力されればよいだけです。そのため、使用しない定義はすべて削除します。
置換文字列の
&BROWSER_LANGUAGE.は
アプリケーション定義の
グローバリゼーションの、
アプリケーションのプライマリ言語として設定した言語に置き換えられます。
置換文字列
#TITLE#は、ページ・プロパティの
タイトルで置き換えられます。
置換文字列
#PAGE_CSS#は、ページ・プロパティの
CSSの
インラインで置き換えられます。
置換文字列
#HEAD#は、ページ・プロパティの
HTMLヘッダーで置き換えられます。
置換文字列#BODY#は、Bodyに配置したコンテンツで置き換えられます。APEXのコンポーネントはChatGPTやClaude Desktopでは扱えないため、配置できるのは静的コンテンツのリージョンに限られます。
置換文字列#PAGE_JAVASCRIPT#は、ページ・プロパティのJavaScriptのファンクションおよびグローバル変数の宣言に置き換えられます。
以上の設定が、生成されるHTMLのページに埋め込まれる情報です。
APEXのテンプレートによるレイアウトも行わないため、テンプレートのレイアウトについても装飾を除きます。
レイアウトのコンテナ・テンプレートを#ROWS#、行テンプレートを#COLUMNS#、列テンプレートを#CONTENT#に変更します。
MCP App向けのテンプレートは以上でとりあえず完成です。変更を保存します。
続いて、MCP App用のHTMLを生成するページを作成します。
ページの作成を開始します。
空白ページを選択します。
ページの名前はget-current-user-appとします。ページ・モードは標準、ナビゲーションは使用できないので両方ともオフにします。
ページの作成をクリックします。
空白のページが作成されます。
外観のページ・テンプレートとして、先ほど作成したページ・テンプレートMCP2を設定します。
ページ・プロパティの別名は、HTMLの生成時に引数として使用します。
MCP Appで使用するHTMLは、外部よりHTTPのGETメソッドでページを呼び出して生成します。そのため、ページ・プロパティのセキュリティの認証はパブリック・ページに変更し、認証せずにページにアクセスできるようにします。
JavaScriptの
ファンクションおよびグローバル変数の宣言に以下を記述します。
CSSのインラインに以下を記述します。
MCP Appに表示するHTMLを記述する静的コンテンツのリージョンを作成します。識別の名前はContentとします。
外観のテンプレートはなしを選択し、ソースのHTMLが修飾無しでBodyに記載されるようにします。
ソースのHTMLに以下を記述します。
以上でとりあえずページは完成です。
ページを実行すると生成されるHTMLを確認できます。
JavaScriptは動きません。HTMLとCSSの効果と生成されたHTMLに限り確認できます。
apex_web_service.make_rest_requestでこのページを呼び出し、取得したHTMLで表OJ_MCP_APP_RESOURCEの列TEXTをアップデートします。
この作業を行なうプロシージャUPDATE_APP_TEXT_FROM_APEX_PAGEをパッケージOJ_MCP_APP_UTILSに追加しています。処理内容は以下になります。
JavaScriptではext-appsをESモジュールとして扱います。APEXのページ・プロセスはページ・プロパティのJavaScriptのファンクションおよびグローバル変数の宣言に記載されたコードを単に<script>...</script>で埋め込みます。これだとESモジュールを扱えないため、APEXのページ・プロセスが生成したHTMLに含まれるscriptタグにtype="module"を追加しています。
あとはこのプロシージャを実行し、MCP AppのUIとなるリソースを登録します。
APEXのページを呼び出せるURLを引数p_page_urlに与えます。
begin
oj_mcp_app_utils.update_app_text_from_apex_page(
p_resource_name => 'get_current_user',
p_resource_uri => 'ui://get-current-user/mcp-app.html',
p_page_url => 'http://host.docker.internal:8181/ords/apexdev/sampleserver/get-current-user-app'
);
commit;
end;
/
登録されたUIリソースは、MCP InspectorのResourcesより確認できます。
Appsタブからアプリを実行し、APEXのページとして作成したHTMLでMCP Appを表示できます。
今回の記事は以上になります。