2026年4月16日木曜日

MCP AppsのサンプルアプリBudget Allocator Serverをデータベースで実行する

GitHubのリポジトリmodelcontextprotocol/ext-appsに含まれているBudget Allocator Serverをデータベースでホストしてみます。今回はClaude Desktopから呼び出せるように、Autonomous AI Databaseを使用します。Autonomous AI Databaseでの準備作業は、以下の記事で紹介しています。
Claude Desktopでは以下のように動作します。


Budget Allocator Serverのサーバーはserver.tsで実装されています。以下の方針で、データベースに実装し直しています。データベースで実行するスクリプトはClaude Sonnetに生成してもらいました。
  1. Zodによるスキーマ定義を切り出し、Oracle Database向けの表定義を生成させました。(スクリプト:bas_create_tables.sql
  2. サンプルデータを生成するファンクションseededRandomとgenerateHistoryを取り出し、PL/SQLで書き直してもらいました。(スクリプト:bas_generate_history.sql
  3. 表bas_budget_categories、bas_budget_analyticsへの初期データの投入を行なうINSERT文を生成させました。(スクリプト:bas_prepare_budget_categories.sqlbas_prepare_budget_analytics.sql
  4. 表bas_budget_config、bas_budget_preset_budgetsへの初期データの投入については、自分でINSERT文を書きました。(スクリプト:bas_prepare_budget_config.sql
  5. ツールが返す構造化出力を生成するファンクションbas_get_budget_data_responseを生成させました。(スクリプト:bas_get_budget_data_response.sql
本記事の目的はMCPアプリをデータベースでホストすることなので、PL/SQLで実装されたサーバーがTypeScriptの機能と完全に互換かどうかという点は確認していません。MCPアプリが動作すれば良しとしています。

CREATE TABLE文を生成する部分、INSERT文を生成するデータ準備部分、それとツール本体となるプロシージャを生成する部分に、作業を分けてコード生成させましたが、server.tsを丸投げしても良かったかもしれません。

これらを一括で実行するスクリプトbas_install.sqlを作成しています。リポジトリmcp-appにmcp-app/examples/budget-allocator-server/bas_install.sqlとして含まれているので、リモートMCPサーバーを実装しているスキーマに接続して実行します。

Autonomous AI Database向けの構築手順にそって作成した環境であれば、Autonomous AI Lakehouseの19cのデータベースがSALESADBとして作成され、APEXワークスペースのスキーマとしてWKSP_APEXDEVが作成されています。この環境を前提とすると、以下のコマンドでBuget Allocator Serverのサーバーを実装できます。

sql -cloudconfig Wallet_SALESADB.zip wksp_apexdev@salesadb_low
@mcp-app/examples/budget-allocator-server/bas_install.sql


データベースにサーバーが実装されたことを確認するために、スクリプト実行の最後にツール呼び出しのレスポンスを出力しています。

mcp-salesadb % sql -cloudconfig Wallet_SALESADB.zip wksp_apexdev@salesadb_low


SQLcl: 木 4月 16 12:39:12 2026のリリース25.4 Production


Copyright (c) 1982, 2026, Oracle.  All rights reserved.


パスワード (**********?) ****************

接続先:

Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production

Version 19.31.0.1.0


SQL> @mcp-app/examples/budget-allocator-server/bas_install.sql 


Table BAS_BUDGET_CATEGORIESは作成されました。



Table BAS_BUDGET_CONFIGは作成されました。



Table BAS_BUDGET_CONFIG_PRESET_BUDGETSは作成されました。



Table BAS_HISTORICAL_MONTHSは作成されました。



Table BAS_HISTORICAL_MONTH_ALLOCATIONSは作成されました。



Table BAS_BUDGET_ANALYTICSは作成されました。



Table BAS_BUDGET_ANALYTICS_STAGESは作成されました。



Table BAS_STAGE_BENCHMARKSは作成されました。



Table BAS_STAGE_BENCHMARK_PERCENTILESは作成されました。



Commentは作成されました。



Commentは作成されました。



Commentは作成されました。



Commentは作成されました。



Commentは作成されました。



Commentは作成されました。



Commentは作成されました。



Commentは作成されました。



Commentは作成されました。



Procedure BAS_GENERATE_HISTORYがコンパイルされました



Function BAS_GET_BUDGET_DATA_RESPONSEがコンパイルされました



1行挿入しました。



1行挿入しました。



1行挿入しました。



1行挿入しました。



1行挿入しました。



コミットが完了しました。



PL/SQLプロシージャが正常に完了しました。



PL/SQLプロシージャが正常に完了しました。



PL/SQLプロシージャが正常に完了しました。



コミットが完了しました。


{"config":{"categories":[{"id":"engineering","name":"Engineering","color":"#10b981","defaultPercent":35},{"id":"marketing","name":"Marketing","color":"#3b82f6","defaultPercent":25},{"id":"operations","name":"Operations","color":"#f59e0b","defaultPercent":15},{"id":"rd","name":"R&D","color":"#8b5cf6","defaultPercent":10},{"id":"sales","name":"Sales","color":"#ef4444","defaultPercent":15}],"presetBudgets":[50000,500000,250000,100000],"defaultBudget":100000,"currency":"USD","currencySymbol":"$"},"analytics":{"history":[{"month":"2024-05","allocations":{"engineering":35,"marketing":24.9,"operations":14.8,"rd":10.7,"sales":14.7}},{"month":"2024-06","allocations":{"engineering":33.4,"marketing":24.8,"operations":15.9,"rd":10.1,"sales":15.7}},{"month":"2024-07","allocations":{"engineering":33.9,"marketing":25.4,"operations":15.3,"rd":8.9,"sales":16.5}},{"month":"2024-08","allocations":{"engineering":34.1,"marketing":26.4,"operations":14.9,"rd":8.7,"sales":16}},{"month":"2024-09","allocations":{"engineering":35.6,"marketing":24.3,"operations":14.8,"rd":10.3,"sales":15}},{"month":"2024-10","allocations":{"engineering":33.1,"marketing":26,"operations":16.2,"rd":8.1,"sales":16.6}},{"month":"2024-11","allocations":{"engineering":33.6,"marketing":25.2,"operations":15.6,"rd":9.7,"sales":16}},{"month":"2024-12","allocations":{"engineering":34.5,"marketing":25.1,"operations":15.7,"rd":10,"sales":14.7}},{"month":"2025-01","allocations":{"engineering":33.1,"marketing":27.7,"operations":14.8,"rd":7.4,"sales":17}},{"month":"2025-02","allocations":{"engineering":34.4,"marketing":27.9,"operations":15,"rd":7.8,"sales":14.9}},{"month":"2025-03","allocations":{"engineering":33.1,"marketing":27.9,"operations":14.9,"rd":8.1,"sales":16}},{"month":"2025-04","allocations":{"engineering":34.6,"marketing":27,"operations":15.1,"rd":7.4,"sales":15.9}},{"month":"2025-05","allocations":{"engineering":34,"marketing":26.6,"operations":15.6,"rd":9.1,"sales":14.7}},{"month":"2025-06","allocations":{"engineering":32.2,"marketing":27.6,"operations":15.6,"rd":8.9,"sales":15.8}},{"month":"2025-07","allocations":{"engineering":33.5,"marketing":28.2,"operations":14.9,"rd":7.1,"sales":16.3}},{"month":"2025-08","allocations":{"engineering":34.2,"marketing":27.5,"operations":15.2,"rd":7.8,"sales":15.3}},{"month":"2025-09","allocations":{"engineering":32.7,"marketing":27.9,"operations":17.1,"rd":7.3,"sales":14.9}},{"month":"2025-10","allocations":{"engineering":33.3,"marketing":27.3,"operations":17.6,"rd":5.9,"sales":15.9}},{"month":"2025-11","allocations":{"engineering":33.7,"marketing":26.4,"operations":16.8,"rd":8,"sales":15}},{"month":"2025-12","allocations":{"engineering":32.3,"marketing":27.9,"operations":15,"rd":8,"sales":16.8}},{"month":"2026-01","allocations":{"engineering":33.5,"marketing":28,"operations":15.7,"rd":6.6,"sales":16.1}},{"month":"2026-02","allocations":{"engineering":31.4,"marketing":28.3,"operations":16.7,"rd":6.7,"sales":16.9}},{"month":"2026-03","allocations":{"engineering":32.4,"marketing":28.3,"operations":16.7,"rd":4.6,"sales":18}},{"month":"2026-04","allocations":{"engineering":32.8,"marketing":28.5,"operations":16,"rd":6.2,"sales":16.4}}],"benchmarks":[{"stage":"Seed","categoryBenchmarks":{"marketing":{"p25":15,"p50":20,"p75":25},"engineering":{"p25":40,"p50":47,"p75":55},"operations":{"p25":8,"p50":12,"p75":15},"sales":{"p25":10,"p50":15,"p75":20},"rd":{"p25":5,"p50":10,"p75":15}}},{"stage":"Series A","categoryBenchmarks":{"marketing":{"p25":20,"p50":25,"p75":30},"engineering":{"p25":35,"p50":40,"p75":45},"operations":{"p25":10,"p50":14,"p75":18},"sales":{"p25":15,"p50":20,"p75":25},"rd":{"p25":8,"p50":12,"p75":15}}},{"stage":"Series B","categoryBenchmarks":{"marketing":{"p25":22,"p50":27,"p75":32},"engineering":{"p25":30,"p50":35,"p75":40},"operations":{"p25":12,"p50":16,"p75":20},"sales":{"p25":18,"p50":23,"p75":28},"rd":{"p25":8,"p50":12,"p75":15}}},{"stage":"Growth","categoryBenchmarks":{"marketing":{"p25":25,"p50":30,"p75":35},"engineering":{"p25":25,"p50":30,"p75":35},"operations":{"p25":15,"p50":18,"p75":22},"sales":{"p25":20,"p50":25,"p75":30},"rd":{"p25":5,"p50":8,"p75":12}}}],"stages":["Seed","Series A","Series B","Growth"],"defaultStage":"Seed"}}



PL/SQLプロシージャが正常に完了しました。


Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production

Version 19.31.0.1.0から切断されました

mcp-salesadb %


サーバーの実装を削除するスクリプトbas_drop_all.sqlも用意しています。

以上でサーバー側の実装はできました。

APEXアプリケーションをインストールして、Budget AllocatorをMCPアプリとして登録します。

Autonomous AI DatabaseのAPEXに接続し、アプリケーション・ビルダーからインポートを実行します。


リポジトリmcp-appに含まれるMCP App Helperアプリmcp-app/app/mcp-app-helper.sqlをインポートします。

へ進みます。


アプリケーションのインストールを実行します。


アプリケーションがインストールされます。

アプリケーションの編集を開き、アプリケーションの名前別名を更新します。


アプリケーションの名前ext-apps別名ext-appsとします。


アプリケーションを実行します。

最初にHandlersを開き、リモートMCPサーバーのエンドポイントとなるORDS RESTサービスをインストールします。

作成をクリックします。


ORDSのRESTモジュール名はAPEXアプリの別名と同じにします。これはリモートMCPサーバーの実装上、必須になっています。

リモートMCPサーバーのエンドポイントURLは以下になります。

https://リバースプロキシのホスト/ords/ORDS別名/ext-apps/mcp

デフォルトの設定のまま、作成をクリックします。


ORDSのRESTモジュールとしてext-apps、テンプレートとしてmcp、そのテンプレートにPOSTハンドラとDELETEハンドラが作成されます。


Resourcesを開き、UIリソースとしてBudget Allocatorを作成します。

作成をクリックします。


以下の値でUIリソースBudget Allocator作成します。

Uri: ui://budget-allocator/mcp-app.html
Name: Budget Allocator
Description: Interactive Budget Allocator UI


作成したUIリソースBudget Allocatorに実際のアプリを登録します。Bundleを開きます。

Resource IDBudget Allocatorを選択し、bundle html fileにリポジトリext-appsに含まれるext-apps/examples/budget-allocator-server/dist/mcp-app.htmlを選択します。

Updateをクリックし、MCPアプリをデータベースにアップロードします。


Toolsを開き、ツールとしてget-budget-dataを作成します。

Code: get-budget-data
Description: Returns budget configuration with 24 months of historical allocations and industry benchmarks by company stage
Input Schema: {"$schema":"http://json-schema.org/draft-07/schema#","type":"object"}
Function Call: return bas_get_budget_data_response();
Tags: ext-apps
Resource Id: Budget Allocator

Output Schemaには、こちらの内容output_schema.jsonを記述します。

以上で作成をクリックします。


以上で、データベースにMCPアプリBudget Allocatorを登録できました。

Claude DesktopよりMCPアプリBudget Allocatorを呼び出してみます。

サイド・メニューよりCustomizeを開きます。


コネクタを開きます。


プラス()アイコンを開き、カスタムコネクタを追加を実行します。


コネクタの名前をBudget Allocator、URLにAutonomous AI Databaseへのリバース・プロキシを指すリモートMCPサーバーのエンドポイントURLを設定します。

追加をクリックします。


カスタムコネクタBudget Allocatorが追加されます。


動作を確認します。

新規チャットより「Budget Allocatorを呼び出して」と伝えます。実行の承認を求められ、実行を許可すると、MCPアプリのBudget Allocatorが表示されます。


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

2026年4月15日水曜日

MCP AppsのサンプルアプリMap Serverをデータベースで実行する

GitHubのリポジトリmodelcontextprotocol/ext-appsに含まれているMap Serverをデータベースでホストしてみます。CesiumJSを使って地図を表示するMCPアプリです。

オリジナルのMCPアプリを実行すると、以下のように表示されます。


データベースのリモートMCPサーバーとして実装し、MCPアプリをMCP Inspectorで呼び出してみました。


記事「MCP Appsのサンプルアプリbasic-server-vanillajsをデータベースで実行する」で作成したリモートMCPサーバーext-appにMap Serverを追加します。

オリジナルのMap Serverは2つのツールshow-mapおよびgeocodeを呼び出しますが、ツールgeocodeについては省略し、show-mapのみを実装します。

最初にツールshow-mapの本体となる、PL/SQLのファンクションshow_mapを作成します。


APEXのSQLコマンドから実行するとよいでしょう。TypeScriptの実装をPL/SQLに、そのまま書き直しただけです。


APEXアプリケーションを実行し、UIリソースを作成します。

Resourcesを開き、以下を設定してMap ServerのUIリソースを作成します。

Uri: ui://map-server/mcp-app.html
Name: Map Server
Description: Display an interactive world map zoomed to a specific bounding box.


Bundleを開き、Resource IDに先ほど作成したMap Serverを選択します。bundle html fileext-apps/examples/map-server/dist/mcp-app.htmlを選択し、Updateします。


以上でUIリソースが作成されました。

Toolsを開き、ツールとしてshow-mapを作成します。

Code: show-map
Description: Display an interactive world map zoomed to a specific bounding box.
Function Call: return show_map(:parameters);
Tags: ext-apps
Resource Id: Map Server

Input Schemaには以下を記述します。



MCPアプリのMap Serverは外部へのネットワーク接続を行うようです。そのため、CSPのconnectDomainおよびresourceDomainを設定します。

CSPを設定する表はデータベースに作成済みですが、ユーザー・インターフェースがありません。以下のSQLを実行し、CSP関連の設定を表OJ_MCP_CSP_DOMAINSに設定します。

APEXのSQLコマンドから実行します。


以上でMap Serverの実装は完了です。

MCP InspectorからMCPアプリを実行します。

npx -y @modelcontextprotocol/inspector

リモートMCPサーバーに接続し、Appsタブを開いてshow-mapを選択します。

東京近辺を表示するように、以下を入力します。

west: 138.5
south: 34.8
east: 140.9
north: 36.8

Open Appをクリックします。


CesiumJSの地図に東京近辺が表示されます。Back to Inputをクリックします。


シンガポール付近を表示してみます。以下の値を入力し、Open Appをクリックします。

west: 103.0
south: 0.8
east: 104.6
north: 1.9

シンガポール近辺が表示されます。


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

MCP Appsのサンプルアプリbasic-serverのPreact,React,Solid,Svelte,Vue版をデータベースで実行する

先日の記事「MCP Appsのサンプルアプリbasic-server-vanillajsをデータベースで実行する」では、VanillaJS(UIフレームワークを使用しない)で作成したMCPアプリをデータベースでホストしました。

GitHubのリポジトリmodelcontextprotocol/ext-appsには、VanillaJS以外にUIフレームワークとしてPreact、React、Solid、Svelte、Vueを使ったMCPアプリのサンプルが含まれています。これらのアプリケーションはすべてツールget-timeを呼び出しています。また、UI自体の見かけはVanillaJSのMCPアプリと同じになっています。

これらのMCPアプリをデータベースでホストしてみます。データベースでのリモートMCPサーバーの実装はresources/read呼び出しを受けて、データベースに保存しているバンドル化されたMCPアプリのソース(HTML+JavaScript+CSS)をクライアントに戻すだけです。そのため、ツールの実装さえ同じであればMCPアプリは一切変更なしで動作するはずです。

結果ですが、modelcontextprotocol/ext-appsにあるbasic-server-preact、basic-server-react、basic-server-solid、basic-server-svelte、basic-server-vueのすべてのMCPアプリが変更せずに動作しています。

作業自体は記事「MCP Appsのサンプルアプリbasic-server-vanillajsをデータベースで実行する」と同じ手順で、作成したリソースにそれぞれのMCPアプリのバンドルを登録します。MCPアプリのUIバンドルはext-apps/examples/basic-server-xxxx/dist/mcp-app.htmlとして作成されています。


Preact、React、Solid、Svelte、Vueの5つのMCPサーバーを登録しますが、ひとつひとつ手作業で登録するのはつらいので、以下のスクリプトを書きました。


APEXのSQLコマンドから上記のスクリプトを実行すると、ファンクションget_timeを呼び出すツールとしてget-time-preact、get-time-react、get-time-solid、get-time-svelte、get-time-vueが作成されます。また同名のリソースが作成され、ツールに紐づけられます。


以上でツールとリソースは作成されます。この後に、リソースにそれぞれのUIバンドルmcp-app.htmlを登録します。

登録後にMCP Inspectorを使って動作を確認します。

npx -y @modelcontextprotocol/inspector

MCPサーバーext-appsに接続し、MCP Appsを一覧します。全部で6つのアプリが選択できます。


アプリの作成に使用しているUIフレームワークが異なるだけで、MCPアプリ自体はどれでも同じ見かけと動作をします。


ただし、VanillaJSではServer Timeは以下のように表示されます。


他のアプリは以下のように表示されます。


コードを確認すると、VanillaJSでの時刻の取り出しは以下のファンクションで行われていました。構造化出力を優先して参照しています。
function extractTime(result: CallToolResult): string {
  const { time } = (result.structuredContent as { time?: string }) ?? {};
  return time ?? "[ERROR]";
}
Preactを確認すると以下でした。構造化出力を参照していません。
function extractTime(callToolResult: CallToolResult): string {
  const { text } = callToolResult.content?.find((c) => c.type === "text")!;
  return text;
}
現在時刻の表示の違いは、ファンクションget_timeのレスポンスの取得方法が異なることが原因でした。

この他Reactについては、Received a response for an unknown message IDというエラーが発生しています。Reactのみで発生していることと、エラーメッセージにあるmessage IDはMCPアプリとホストの間でやり取りするメッセージに割り振られているIDで、ホストとデータベースで実行されるMCPサーバー(ツール)の間のメッセージに割り振られているIDではありません。よって、これはMCPアプリ側の問題だと思われます。

以上でデータベースに実装したリモートMCPサーバーで、各種UIフレームワークで作成したMCPアプリをホストできることが確認できました。

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