目的
先日AWS Summit Japanに参加してきました。
その際に参加した「Amazon S3 で切り開くクラウドストレージの未来」内で
Amazon S3 Metadataテーブルに対してkiroにバケット内の変更履歴を調べてと聞くだけで、クエリを発行して回答してくれるという内容がありました。
クエリを書かなくてもバケット内の変更履歴をメタデータから取得して監査できるという内容でした。
(AWS Summit Japanのオンデマンド視聴はどなたでも登録後に視聴可能です。)
私は普段の業務でIBM BobとSQL Serverを使用する機会が多いので、同じことが出来ないかなと検討してみました。
対象読者
・SQL Serverを日々の業務で使用している方
・IBM Bobを日々の業務で使用している方
・IBM Bobの導入を検討している方
前提条件として
SQL ServerとIBM Bobを使用できる環境をご準備ください。
IBM Bobの無料トライアルは下記の記事を参考にして頂くと、数十分で使用出来るようになります。
Gmail登録だとより簡単にスタート出来ます。
全体の流れ
SQL MCP Serverの公式手順に従ってSQL Server上にサンプルDBとテーブルを作成します。
次に、IBM Bob上でMCPサーバを構築して、サンプルテーブルへ問い合わせが出来る事を確認します。
その後にS3 Metadataテーブルの様に変更履歴データを持つ機能をSQL Server側に設定して、
そちらのテーブルについてもIBM Bobから問い合わせられるかを確認します。
目次
1章 SQL MCP Serverの設定
1-1 Data API Builder CLIのインストール
今回はMicrosoft公式のSQL MCP Serverを使用します。
基本的にはVisual Studio Code用の手順をそのまま進めていきます。
まず、IBM Bobを起動して作業用フォルダを開きます。
ターミナルを開いて、Data API Builder CLIをインストールします。
dotnet new tool-manifest
dotnet tool install microsoft.dataapibuilder
dotnet tool restore
私は既存でVer.6がインストールされていたので、新しくVer.9をインストールしました。
(Ver.8はランタイムとして別途インストール済みでしたが、SDKとしては入っていませんでした。)
PS C:\Bob_SQLServer> dotnet --version
9.0.315
PS C:\Bob_SQLServer> dotnet --list-sdks
6.0.428 [C:\Program Files\dotnet\sdk]
9.0.315 [C:\Program Files\dotnet\sdk]
PS C:\Bob_SQLServer> dotnet --list-runtimes
Microsoft.AspNetCore.App 6.0.36 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 8.0.28 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 9.0.17 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.NETCore.App 6.0.36 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 8.0.28 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 9.0.17 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.WindowsDesktop.App 6.0.36 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 9.0.17 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
1-2 サンプルDBとテーブルの作成
サンプルDBとテーブルをSQL Server上に作成します。
CREATE DATABASE ProductsDb;
GO
USE ProductsDb;
GO
CREATE TABLE dbo.Products (
Id INT PRIMARY KEY,
Name NVARCHAR(100) NOT NULL,
Inventory INT NOT NULL,
Price DECIMAL(10,2) NOT NULL,
Cost DECIMAL(10,2) NOT NULL
);
INSERT INTO dbo.Products (Id, Name, Inventory, Price, Cost)
VALUES
(1, 'Action Figure', 40, 14.99, 5.00),
(2, 'Building Blocks', 25, 29.99, 10.00),
(3, 'Puzzle 500 pcs', 30, 12.49, 4.00),
(4, 'Toy Car', 50, 7.99, 2.50),
(5, 'Board Game', 20, 34.99, 12.50),
(6, 'Doll House', 10, 79.99, 30.00),
(7, 'Stuffed Bear', 45, 15.99, 6.00),
(8, 'Water Blaster', 35, 19.99, 7.00),
(9, 'Art Kit', 28, 24.99, 8.00),
(10,'RC Helicopter', 12, 59.99, 22.00);
1-3 SQL MCP Serverの構成
本来であればここで環境ファイル(.env)を作成しますが、今回はmcpサーバ定義(mcp.json)内に環境変数として記載するため、ここでは作成しません。
サーバを初期化して構成するため、以下のコマンドを実行します。
dab init --database-type mssql --connection-string "@env('MSSQL_CONNECTION_STRING')" --host-mode Development --config dab-config.json
dab add Products --source dbo.Products --permissions "anonymous:read" --description "Toy store products with inventory, price, and cost."
実行すると下記の様な結果になり、dab-config.jsonファイルが作業用フォルダに作成されます。
PS C:\Bob_SQLServer> dab init --database-type mssql --connection-string "@env('MSSQL_CONNECTION_STRING')" --host-mode Development --config dab-config.json
info: Microsoft.DataApiBuilder 2.0.9
info: Generating user provided config file with name: dab-config.json
info: Config file generated.
info: SUGGESTION: Use 'dab add [entity-name] [options]' to add new entities in your config.
PS C:\Bob_SQLServer> dab add Products --source dbo.Products --permissions "anonymous:read" --description "Toy store products with inventory, price, and cost."
info: Microsoft.DataApiBuilder 2.0.9
info: Config not provided. Trying to get default config based on DAB_ENVIRONMENT...
info: Environment variable DAB_ENVIRONMENT is (null)
info: Added new entity: Products with source: dbo.Products and permissions: anonymous:read.
info: SUGGESTION: Use 'dab update [entity-name] [options]' to update any entities in your config.
1-4 列名の設定
SQL MCP Serverではdab-configファイルに参照するテーブルを1つずつ設定していく必要があります。
テーブルへの問い合わせ精度を向上させるために列名のメタデータを追加することが推奨されています。
また、SQL MCP Serverを利用するにあたって、主キーをdab-configファイルに設定しないとエラーになりますので、ご注意ください。
ここではサンプルテーブルの列名全てに説明を追加します。
dab update Products --fields.name Id --fields.primary-key true --fields.description "Product Id"
dab update Products --fields.name Name --fields.description "Product name"
dab update Products --fields.name Inventory --fields.description "Units in stock"
dab update Products --fields.name Price --fields.description "Retail price"
dab update Products --fields.name Cost --fields.description "Store cost"
実行結果が以下です。dab-config.jsonファイルに反映されます。
PS C:\Bob_SQLServer> dab update Products --fields.name Id --fields.primary-key true --fields.description "Product Id"
info: Microsoft.DataApiBuilder 2.0.9
info: Config not provided. Trying to get default config based on DAB_ENVIRONMENT...
info: Environment variable DAB_ENVIRONMENT is (null)
info: Updated the entity: Products.
PS C:\Bob_SQLServer> dab update Products --fields.name Name --fields.description "Product name"
info: Microsoft.DataApiBuilder 2.0.9
info: Config not provided. Trying to get default config based on DAB_ENVIRONMENT...
info: Environment variable DAB_ENVIRONMENT is (null)
info: Updated the entity: Products.
PS C:\Bob_SQLServer> dab update Products --fields.name Inventory --fields.description "Units in stock"
info: Microsoft.DataApiBuilder 2.0.9
info: Config not provided. Trying to get default config based on DAB_ENVIRONMENT...
info: Environment variable DAB_ENVIRONMENT is (null)
info: Updated the entity: Products.
PS C:\Bob_SQLServer> dab update Products --fields.name Price --fields.description "Retail price"
info: Microsoft.DataApiBuilder 2.0.9
info: Config not provided. Trying to get default config based on DAB_ENVIRONMENT...
info: Environment variable DAB_ENVIRONMENT is (null)
info: Updated the entity: Products.
PS C:\Bob_SQLServer> dab update Products --fields.name Cost --fields.description "Store cost"
info: Microsoft.DataApiBuilder 2.0.9
info: Config not provided. Trying to get default config based on DAB_ENVIRONMENT...
info: Environment variable DAB_ENVIRONMENT is (null)
info: Updated the entity: Products.
1-5 MCPサーバ定義を作成する
IBM Bobウィンドウ右下のBob設定をクリックします。
設定項目からMCPを選択し、「MCPサーバーを追加」の+ボタンをクリックします。
認証スコープで作業用フォルダを選択して、設定ファイルを開くをクリックします。
作業用フォルダ\bob\mcp.jsonが作成されますので、以下をコピー&ペーストして保存します。
公式ドキュメントからの変更点としては、typeの記載を削除し、dab-config.jsonの絶対パスを指定しています。
--loglevelについてもDAB CLI自体は大文字・小文字を区別するため、--LogLevelに修正しています。
また、envファイルの内容については本ファイル内に記載します。
Server名について、私の環境ではlocalhostの後ろにインスタンス名まで記載する必要があったため
「MSSQL_CONNECTION_STRING=Server=localhost\SQLEXPRESS」
としています。
ここではWindows認証を設定してます。SQL認証を用いる場合には公式ドキュメントを参照して編集してください。
セッションするポートについてもASPNETCORE_URLSの部分で指定しています。
{
"mcpServers": {
"sql-mcp-server": {
"command": "dab",
"args": [
"start",
"--mcp-stdio",
"role:anonymous",
"--LogLevel",
"error",
"--config",
"C:\\Bob_SQLServer\\dab-config.json"
],
"env": {
"MSSQL_CONNECTION_STRING": "Server=localhost\\SQLEXPRESS;Database=ProductsDb;Trusted_Connection=True;TrustServerCertificate=True",
"ASPNETCORE_URLS": "http://localhost:5050"
}
}
}
}
SQL Serverのインスタンス名については下記コマンドから確認できます。
出力結果に応じて.envファイル内のServerの値を編集してください。
Get-Service -Name "MSSQL*"
Status Name DisplayName
------ ---- -----------
Running MSSQL$SQLEXPRESS SQL Server (SQLEXPRESS)
Running MSSQLFDLauncher... SQL Full-text Filter Daemon Launche...
Running MSSQLLaunchpad$... SQL Server Launchpad (SQLEXPRESS)
ここまで完了しましたらmcp.jsonファイルを保存して閉じてください。
Bobの設定画面に戻って、更新ボタンをクリックしてMCPサーバが接続済みになっていれば準備完了です。
エラーが起きて接続できない場合はステータスが切断済みになります。
その場合はBobに聞いて、エラー原因を確認してください。
以下がBobに「Productsテーブルについて教えて」と問い合わせた結果です。
SQL Serverに接続して、テーブル情報を取得できることまで確認出来ましたので、
次の章ではテーブルの更新情報を保管する方法と、30日以内のデータにフィルターしたビューを作成します。
2章 SQL Serverでの更新・削除履歴の保存設定
この章では先ほど作成したProductsテーブルについて、
行の値が更新された場合と行が削除された場合に履歴テーブルへ元の値を含む行と削除される行が退避されるように設定します。
履歴テーブルから過去30日間に更新・削除があったデータを参照できるビューを作成します。
Productsテーブルについて以下の2列を追加します。
ValidFrom: 行の現在の値が有効になった日時
ValidTo: 行の現在の値が有効でなくなり、上書きされる日時
以下は各設定の内容です。
| 設定項目 / キーワード | 概要・役割 | 詳細・特徴 |
|---|---|---|
| GENERATED ALWAYS AS ROW START HIDDEN | ユーザーによる手動入力不可 | SQL Server が自動的に計算・更新します。 |
| HIDDEN | 通常クエリ時の非表示設定 |
SELECT * でクエリした時に新しい2列は含まれません。 |
| DEFAULT '9999-12-31 23:59:59.9999999' | 未来の終了日時の設定 | 現在有効な行について未来の日時を設定しています。 |
| PERIOD FOR SYSTEM_TIME (ValidFrom, ValidTo) | システム期間の定義 | SYSTEM_VERSIONING機能を使うための設定です。 |
SYSTEM_VERSIONING = ONにすることで、SQL Serverは自動的にdbo.Products_Historyという履歴テーブルを新規作成します。
USE ProductsDb;
GO
ALTER TABLE dbo.Products ADD
ValidFrom DATETIME2 GENERATED ALWAYS AS ROW START HIDDEN
NOT NULL DEFAULT SYSUTCDATETIME(),
ValidTo DATETIME2 GENERATED ALWAYS AS ROW END HIDDEN
NOT NULL DEFAULT CONVERT(DATETIME2, '9999-12-31 23:59:59.9999999'),
PERIOD FOR SYSTEM_TIME (ValidFrom, ValidTo);
GO
ALTER TABLE dbo.Products
SET (SYSTEM_VERSIONING = ON (
HISTORY_TABLE = dbo.Products_History
-- 保持期間は指定しない = 無期限累積
));
GO
以下が元テーブルと履歴テーブルの挙動です。
| 操作 | 現在テーブル (Products) | 履歴テーブル (Products_History) |
|---|---|---|
| INSERT | 新規行が追加される | 何も起きない |
| UPDATE | 新しい値に上書きされる | 変更前の値が1行、自動的にコピーされる |
| DELETE | 行が削除される | 削除直前の値が1行、自動的にコピーされる |
コマンドを実行して、Historyテーブルが作成されたことを確認します。
テーブル内のデータは以下です。
Id=1のInventoryの値を100に更新し、Id=9の行を削除するために以下のコマンドを実行します。
UPDATE dbo.Products SET Inventory = 100 WHERE Id = 1;
DELETE FROM dbo.Products WHERE Id = 9;
コマンドを実行後にProductsテーブルでId=1のInventory値が更新されていて、
Id=9の行が削除されていることを確認します。
Products_Historyテーブルには、更新・削除があったレコードが退避されていることを確認します。
次に、Products_Historyテーブルの中から30日以内のデータを参照できるビューを作成し、IBM Bobから問い合わせられるようにします。
履歴テーブルとProductsテーブルをIdで左外部結合し、そのIdに対応する行が元テーブルに存在せず、かつその履歴行がそのIdにおける最新の履歴行である場合を削除データ、それ以外(元テーブルに存在する、またはより新しい履歴行が他に存在する)を更新データとして判別し、2列目にDelete/Updateとして出力します。
CREATE VIEW dbo.vw_Products_ChangeHistory AS
SELECT
h.Id,
CASE
WHEN cur.Id IS NULL
AND h.ValidTo = (
SELECT MAX(h2.ValidTo)
FROM dbo.Products_History AS h2
WHERE h2.Id = h.Id
)
THEN 'Delete'
ELSE 'Update'
END AS OperationType,
h.ValidFrom AS ChangedFrom,
h.ValidTo AS ChangedAt,
h.Name,
h.Inventory,
h.Price,
h.Cost
FROM dbo.Products_History AS h
LEFT JOIN dbo.Products AS cur
ON cur.Id = h.Id
WHERE h.ValidTo >= DATEADD(DAY, -30, SYSUTCDATETIME());
GO
作成したビューを参照してId=1が更新、Id=9が削除として出力されることを確認します。
更新された行がその後に削除された場合についても履歴テーブルとビューに反映されることを確認するために以下のクエリを実行します。
DELETE FROM dbo.Products WHERE Id = 1;
以上がテーブルの更新と削除を履歴テーブルに退避させて期間を設けてビューを作成する方法です。
ここでは、どのレコードに更新または削除があったかを判別するのみで、どの列の値が更新されたかについては判別するロジックがないため、この部分は今後解決したいと思っています。
3章 IBM Bob側での設定変更と自然言語問い合わせ
SQL Serverでビューを作成出来たので、SQL MCP Server側のdab-config.jsonにビューの情報を追加したいと思います。
dab-config.json内の「"entities"」にテーブル情報を記載します。
Productsテーブルの情報が既にあるので、それに倣って追記します。
以下が追記部分です。
"ProductsChangeHistory": {
"description": "Update and delete history of dbo.Products within the last 30 days.",
"source": {
"object": "dbo.vw_Products_ChangeHistory",
"type": "view"
},
"fields": [
{
"name": "Id",
"description": "Product Id",
"primary-key": true
},
{
"name": "ChangedAt",
"description": "Timestamp when this version was changed or deleted",
"primary-key": true
},
{
"name": "OperationType",
"description": "Type of change: Update or Delete",
"primary-key": false
},
{
"name": "ChangedFrom",
"description": "Timestamp when this version became effective",
"primary-key": false
},
{
"name": "Name",
"description": "Product name at time of change",
"primary-key": false
},
{
"name": "Inventory",
"description": "Inventory count at time of change",
"primary-key": false
},
{
"name": "Price",
"description": "Price at time of change",
"primary-key": false
},
{
"name": "Cost",
"description": "Cost at time of change",
"primary-key": false
}
],
"graphql": {
"enabled": true,
"type": {
"singular": "ProductsChangeHistory",
"plural": "ProductsChangeHistories"
}
},
"rest": {
"enabled": true
},
"permissions": [
{
"role": "anonymous",
"actions": [
{
"action": "read"
}
]
}
]
}
付録(スクリプト)にdab-config.json全体を記載していますので、こちらを貼り付けていただいても同じ結果になります。
追記が完了しましたら、ファイルを保存してからMCPサーバを再起動します。
Bobの設定画面を開きMCPの項目で更新ボタンをクリックし、接続済みになることを確認します。
「過去1か月に削除されたデータを教えて」と聞いた所、テーブル側のデータを正確に教えてくれました。
「更新があったデータについても教えて」と聞いた所、更新があったデータは既に削除されたことと、更新時と削除時には値に変化があったことを教えてくれました。
あとがき
今回はSQL MCP Serverを使ってIBM BobからSQL Serverのデータを参照する方法をご紹介しました。
今回作成した設定では、どのデータが更新されたかどうかまでは判別できないため、この部分については今後解決したいと思っています。
MCPサーバを利用して、SQLクエリを書けない人でもテーブルデータを取得出来るようになるのは良い点かなと思いました。
1つ1つテーブル情報を入力しないといけない点も、セキュリティ面では良いのかなと思いました。
記事の内容につきましてご不明点、ご指摘ございましたらお気軽にコメントください。
拙い文章ではございますが、最後までお付き合いいただきありがとうございました。
付録(スクリプト)
dab-config.json
{
"$schema": "https://github.com/Azure/data-api-builder/releases/download/v2.0.9/dab.draft.schema.json",
"data-source": {
"database-type": "mssql",
"connection-string": "@env('MSSQL_CONNECTION_STRING')",
"options": {
"set-session-context": false
}
},
"runtime": {
"rest": {
"enabled": true,
"path": "/api",
"request-body-strict": false
},
"graphql": {
"enabled": true,
"path": "/graphql",
"allow-introspection": true
},
"mcp": {
"enabled": true,
"path": "/mcp"
},
"host": {
"cors": {
"origins": [],
"allow-credentials": false
},
"authentication": {
"provider": "Unauthenticated"
},
"mode": "development"
},
"telemetry": {
"open-telemetry": {
"enabled": true,
"endpoint": "@env('OTEL_EXPORTER_OTLP_ENDPOINT')",
"headers": "@env('OTEL_EXPORTER_OTLP_HEADERS')",
"service-name": "@env('OTEL_SERVICE_NAME')"
}
}
},
"autoentities": {},
"entities": {
"Products": {
"description": "Toy store products with inventory, price, and cost.",
"source": {
"object": "dbo.Products",
"type": "table"
},
"fields": [
{
"name": "Id",
"description": "Product Id",
"primary-key": true
},
{
"name": "Name",
"description": "Product name",
"primary-key": false
},
{
"name": "Inventory",
"description": "Units in stock",
"primary-key": false
},
{
"name": "Price",
"description": "Retail price",
"primary-key": false
},
{
"name": "Cost",
"description": "Store cost",
"primary-key": false
}
],
"graphql": {
"enabled": true,
"type": {
"singular": "Products",
"plural": "Products"
}
},
"rest": {
"enabled": true
},
"permissions": [
{
"role": "anonymous",
"actions": [
{
"action": "read"
}
]
}
]
},
"ProductsChangeHistory": {
"description": "Update and delete history of dbo.Products within the last 30 days.",
"source": {
"object": "dbo.vw_Products_ChangeHistory",
"type": "view"
},
"fields": [
{
"name": "Id",
"description": "Product Id",
"primary-key": true
},
{
"name": "ChangedAt",
"description": "Timestamp when this version was changed or deleted",
"primary-key": true
},
{
"name": "OperationType",
"description": "Type of change: Update or Delete",
"primary-key": false
},
{
"name": "ChangedFrom",
"description": "Timestamp when this version became effective",
"primary-key": false
},
{
"name": "Name",
"description": "Product name at time of change",
"primary-key": false
},
{
"name": "Inventory",
"description": "Inventory count at time of change",
"primary-key": false
},
{
"name": "Price",
"description": "Price at time of change",
"primary-key": false
},
{
"name": "Cost",
"description": "Cost at time of change",
"primary-key": false
}
],
"graphql": {
"enabled": true,
"type": {
"singular": "ProductsChangeHistory",
"plural": "ProductsChangeHistories"
}
},
"rest": {
"enabled": true
},
"permissions": [
{
"role": "anonymous",
"actions": [
{
"action": "read"
}
]
}
]
}
}
}















