【AI Shift Advent Calendar 2024】MCP(Model Context Protocol)を用いた予約対話AIエージェントの構築と動作のトレース

(サムネイルはDALLE-3によって生成)

こんにちは、AI Shiftの友松です。 この記事はAI Shift Advent Calendar の4日目の記事です。
今回はAnthropic社から発表されたMCP(Model Context Protocol)について取り上げたいと思います。

MCPはLLMと外部データソース、ツールのシームレスな連携を行うためのプロトコルです。LLMは人間で言えば脳みそに当たる部分です。その能力を引き出すためには外部のデータとの連携が不可欠です。人間も頭で考えるだけではなく、必要な情報にアクセスしながらあらゆる問題解決を行っています。

これまでもLLMと外部データを組み合わせる取り組みはされてきており、Function Callingと呼ばれる機構を用いることでツールの選択、外部情報に問い合わせるための情報抽出などを定義して実行することができました。一方でFunction Callingでは都度外部情報にアクセスするために関数の定義を詳細にする必要がありました。

MCPでは、各情報ソースに対して実行可能な処理を"ツール"という形で定義し、そのツールをどのように使うかというのはLLMに判断させて実行を進行します。

"AIエージェント"を構築するにあたって、自分自身で判断し、行動選択をし、自ら外部の情報にアクセスし、次の意思決定につなげていく。といった一連のアプローチをMCPを用いることで可能になります。

具体的なセットアップについては他の記事でも既に参考になるものが多数出てきているため、そこに関しては簡潔にまとめます。

本記事ではMCPの動作を理解するためにSQLiteとの接続を行い、予約対話を行うAIエージェントの構築とトレースを行います。

今回の記事では以下の内容に触れます。

  • MCPを用いてローカルに構築したSQLiteにアクセス
  • 予約管理を行うための最小限のデータベースの構築
  • 上記による予約処理を自律的に実行するようなAIエージェントの構築
  • MCPが具体的にどんな処理を行っているかをトレース

1. 準備

MCPのQuickstartに準拠します。

差分としては、予約対話システムを構築するためにSQLiteで予約管理DBの構築を行います。

Macによるセットアップをしていますが、Windowsでやる場合は公式情報を参考にしつつ、予約DBの構築部分だけ本記事を参照ください。

1.1 環境

MacBook Pro 16インチ, 2021
Apple M1 Pro
MacOS Ventura(13.6.8)

1.2 前提条件

  • Claude Desktopの最新版のインストール
  • uv 0.4.18以上
  • Git
  • SQLite ( to check)
$ brew install uv git sqlite3

1.3 予約DBの構築

予約管理を行うためのDBを設定します。DB設計に関してはLLMとのやり取りによって作成しました。

1.3.1 DBへのアクセス

sqlite3 ~/test.db <<EOF

1.3.2 予約枠テーブル

予約枠を取り扱うテーブルです。◯月◯日(date)の何時(start_time)から何時(end_time)までの予約枠が何枠(capacity)あるかという情報を管理します。

CREATE TABLE reservation_frames (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    date DATE NOT NULL,
    start_time TIME NOT NULL,
    end_time TIME NOT NULL,
    capacity INTEGER NOT NULL
);

1.3.3 予約テーブル

予約情報を扱います。

どの予約枠(reservation_frame_id)に対して誰(customer_name, customer_contact)が予約をしているか、そして予約のステータス(confirmed, canceled, pending)を扱います。

CREATE TABLE reservations (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    reservation_frame_id INTEGER NOT NULL,
    customer_name TEXT NOT NULL,
    customer_contact TEXT NOT NULL,
    status TEXT NOT NULL CHECK(status IN ('confirmed', 'cancelled', 'pending')),
    FOREIGN KEY (reservation_frame_id) REFERENCES reservation_frames(id)
);

1.3.4 予約枠情報の追加

12月5日から12月11日の10:00(start_time)から16:00(end_time)まで1時間毎の枠をそれぞれ空き枠(capacity)1で追加します。

INSERT INTO reservation_frames (date, start_time, end_time, capacity) VALUES
('2024-12-05', '10:00:00', '11:00:00', 1),
('2024-12-05', '11:00:00', '12:00:00', 1),
('2024-12-05', '12:00:00', '13:00:00', 1),
('2024-12-05', '13:00:00', '14:00:00', 1),
('2024-12-05', '14:00:00', '15:00:00', 1),
('2024-12-05', '15:00:00', '16:00:00', 1),
('2024-12-05', '16:00:00', '17:00:00', 1),
('2024-12-06', '10:00:00', '11:00:00', 1),
('2024-12-06', '11:00:00', '12:00:00', 1),
('2024-12-06', '12:00:00', '13:00:00', 1),
('2024-12-06', '13:00:00', '14:00:00', 1),
('2024-12-06', '14:00:00', '15:00:00', 1),
('2024-12-06', '15:00:00', '16:00:00', 1),
('2024-12-06', '16:00:00', '17:00:00', 1),
('2024-12-07', '10:00:00', '11:00:00', 1),
('2024-12-07', '11:00:00', '12:00:00', 1),
('2024-12-07', '12:00:00', '13:00:00', 1),
('2024-12-07', '13:00:00', '14:00:00', 1),
('2024-12-07', '14:00:00', '15:00:00', 1),
('2024-12-07', '15:00:00', '16:00:00', 1),
('2024-12-07', '16:00:00', '17:00:00', 1),
('2024-12-08', '10:00:00', '11:00:00', 1),
('2024-12-08', '11:00:00', '12:00:00', 1),
('2024-12-08', '12:00:00', '13:00:00', 1),
('2024-12-08', '13:00:00', '14:00:00', 1),
('2024-12-08', '14:00:00', '15:00:00', 1),
('2024-12-08', '15:00:00', '16:00:00', 1),
('2024-12-08', '16:00:00', '17:00:00', 1),
('2024-12-09', '10:00:00', '11:00:00', 1),
('2024-12-09', '11:00:00', '12:00:00', 1),
('2024-12-09', '12:00:00', '13:00:00', 1),
('2024-12-09', '13:00:00', '14:00:00', 1),
('2024-12-09', '14:00:00', '15:00:00', 1),
('2024-12-09', '15:00:00', '16:00:00', 1),
('2024-12-09', '16:00:00', '17:00:00', 1),
('2024-12-10', '10:00:00', '11:00:00', 1),
('2024-12-10', '11:00:00', '12:00:00', 1),
('2024-12-10', '12:00:00', '13:00:00', 1),
('2024-12-10', '13:00:00', '14:00:00', 1),
('2024-12-10', '14:00:00', '15:00:00', 1),
('2024-12-10', '15:00:00', '16:00:00', 1),
('2024-12-10', '16:00:00', '17:00:00', 1),
('2024-12-11', '10:00:00', '11:00:00', 1),
('2024-12-11', '11:00:00', '12:00:00', 1),
('2024-12-11', '12:00:00', '13:00:00', 1),
('2024-12-11', '13:00:00', '14:00:00', 1),
('2024-12-11', '14:00:00', '15:00:00', 1),
('2024-12-11', '15:00:00', '16:00:00', 1),
('2024-12-11', '16:00:00', '17:00:00', 1);

1.3.5 予約情報の追加

既に全体の7割が埋まっている状況を作るために予約枠情報をランダムでLLMに作ってもらいました。

INSERT INTO reservations (id, reservation_frame_id, customer_name, customer_contact, status) VALUES
(1, 1, 'Customer_1', 'customer_1@example.com', 'confirmed'),
(2, 3, 'Customer_2', 'customer_2@example.com', 'confirmed'),
(3, 4, 'Customer_3', 'customer_3@example.com', 'confirmed'),
(4, 5, 'Customer_4', 'customer_4@example.com', 'confirmed'),
(5, 6, 'Customer_5', 'customer_5@example.com', 'confirmed'),
(6, 8, 'Customer_6', 'customer_6@example.com', 'confirmed'),
(7, 10, 'Customer_7', 'customer_7@example.com', 'confirmed'),
(8, 11, 'Customer_8', 'customer_8@example.com', 'confirmed'),
(9, 12, 'Customer_9', 'customer_9@example.com', 'confirmed'),
(10, 14, 'Customer_10', 'customer_10@example.com', 'confirmed'),
(11, 15, 'Customer_11', 'customer_11@example.com', 'confirmed'),
(12, 17, 'Customer_12', 'customer_12@example.com', 'confirmed'),
(13, 18, 'Customer_13', 'customer_13@example.com', 'confirmed'),
(14, 19, 'Customer_14', 'customer_14@example.com', 'confirmed'),
(15, 21, 'Customer_15', 'customer_15@example.com', 'confirmed'),
(16, 22, 'Customer_16', 'customer_16@example.com', 'confirmed'),
(17, 23, 'Customer_17', 'customer_17@example.com', 'confirmed'),
(18, 25, 'Customer_18', 'customer_18@example.com', 'confirmed'),
(19, 26, 'Customer_19', 'customer_19@example.com', 'confirmed'),
(20, 27, 'Customer_20', 'customer_20@example.com', 'confirmed'),
(21, 29, 'Customer_21', 'customer_21@example.com', 'confirmed'),
(22, 30, 'Customer_22', 'customer_22@example.com', 'confirmed'),
(23, 31, 'Customer_23', 'customer_23@example.com', 'confirmed'),
(24, 33, 'Customer_24', 'customer_24@example.com', 'confirmed'),
(25, 34, 'Customer_25', 'customer_25@example.com', 'confirmed'),
(26, 35, 'Customer_26', 'customer_26@example.com', 'confirmed'),
(27, 37, 'Customer_27', 'customer_27@example.com', 'confirmed'),
(28, 38, 'Customer_28', 'customer_28@example.com', 'confirmed'),
(29, 39, 'Customer_29', 'customer_29@example.com', 'confirmed'),
(30, 41, 'Customer_30', 'customer_30@example.com', 'confirmed'),
(31, 42, 'Customer_31', 'customer_31@example.com', 'confirmed'),
(32, 43, 'Customer_32', 'customer_32@example.com', 'confirmed'),
(33, 45, 'Customer_33', 'customer_33@example.com', 'confirmed'),
(34, 46, 'Customer_34', 'customer_34@example.com', 'confirmed'),
(35, 47, 'Customer_35', 'customer_35@example.com', 'confirmed'),
(36, 49, 'Customer_36', 'customer_36@example.com', 'confirmed');

1.4 configファイルの確立

~/Library/Application Support/Claude/claude_desktop_config.json を編集。

{
  "mcpServers": {
    "sqlite": {
      "command": "uvx",
      "args": ["mcp-server-sqlite", "--db-path", "/Users/<YOUR_USERNAME>/test.db"]
    }
  }
}

1.5 MCPサーバーの起動

$ uvx mcp-server-sqlite

MCPの設定が完了し、Claudeの Start new chat をクリックするとチャット入力欄の右下に金槌のマークと6という数字が表示されています。クリックするとMCPが操作可能なツールの一覧が出てきます。今回はSQLiteに関する設定を行ったので、以下の操作が許可されています。詳しくはこちらを御覧ください

  • Query Tools
    • read-query: SELECTクエリを実行してデータベースからデータを読み取ります
    • write-query: INSERT、UPDATE、またはDELETEクエリを実行する
    • create-table: データベースに新しいテーブルを作成する
  • Schema Tools
    • list-table: データベース内のすべてのテーブルのリストを取得する
    • describe-table: 特定のテーブルのスキーマ情報を表示する
  • Analysis Tools
    • append-insight: メモリソースに新しいビジネスインサイトを追加する

2. 予約対話の実行

まずは構築した予約対話AIエージェントとのやり取りを動画で御覧ください。このあとそれぞれのやり取りに関して細かくトレースを行います。

3. 動作のトレース

これらのツールをユーザーからのQueryに合わせてToolを使い分け、タスクの実行を行います。実際に動画の中から一部のやり取りを切り出しながらMCPによる内部動作のトレースを行っていきます。

① 「SQLiteのテーブルを参照して予約を取りたいです。」

list-table

質問に対してテーブルの一覧を参照しています。ここでは3つのテーブル(reservations, sqlite_sequence, reservation_frames)があることがわかります。

  • Request
{}
  • Response
[{'name': 'reservations'}, {'name': 'sqlite_sequence'}, {'name': 'reservation_frames'}]

describe-data

テーブルの一覧に対して、関連しそうなreservationsテーブルとreservation_framesテーブルのスキーマ定義を見に行っています。

  • reservationsテーブルに対するRequest
{
  `table_name`: `reservations`
}
  • reservationsテーブルに対するResponse
[{'cid': 0, 'name': 'id', 'type': 'INTEGER', 'notnull': 0, 'dflt_value': None, 'pk': 1}, {'cid': 1, 'name': 'reservation_frame_id', 'type': 'INTEGER', 'notnull': 1, 'dflt_value': None, 'pk': 0}, {'cid': 2, 'name': 'customer_name', 'type': 'TEXT', 'notnull': 1, 'dflt_value': None, 'pk': 0}, {'cid': 3, 'name': 'customer_contact', 'type': 'TEXT', 'notnull': 1, 'dflt_value': None, 'pk': 0}, {'cid': 4, 'name': 'status', 'type': 'TEXT', 'notnull': 1, 'dflt_value': None, 'pk': 0}]

  • reservation_framesに対するRequest
{
  `table_name`: `reservation_frames`
}
  • reservation_framesに対するResponse
[{'cid': 0, 'name': 'id', 'type': 'INTEGER', 'notnull': 0, 'dflt_value': None, 'pk': 1}, {'cid': 1, 'name': 'date', 'type': 'DATE', 'notnull': 1, 'dflt_value': None, 'pk': 0}, {'cid': 2, 'name': 'start_time', 'type': 'TIME', 'notnull': 1, 'dflt_value': None, 'pk': 0}, {'cid': 3, 'name': 'end_time', 'type': 'TIME', 'notnull': 1, 'dflt_value': None, 'pk': 0}, {'cid': 4, 'name': 'capacity', 'type': 'INTEGER', 'notnull': 1, 'dflt_value': None, 'pk': 0}]

上記のツール操作の結果をもとに2つのテーブルがあることとそのテーブル定義についての回答。またreservationsテーブルにcustomer_nameとcustomer_contactというフィールドがあることから予約には名前と連絡先が必要であることを理解して、情報提供をユーザーにリクエストしています。

② 直近で予約可能な場所を4つ教えて。

read-query:

予約の空き枠情報を取得するためにクエリを発行しています。

  • Request
{
  `query`: `
WITH reservation_counts AS (
  SELECT 
    reservation_frame_id,
    COUNT(*) as reserved_count
  FROM reservations
  WHERE status != 'cancelled'
  GROUP BY reservation_frame_id
)
SELECT 
  rf.id,
  rf.date,
  rf.start_time,
  rf.end_time,
  rf.capacity,
  COALESCE(rc.reserved_count, 0) as current_reservations,
  (rf.capacity - COALESCE(rc.reserved_count, 0)) as available_seats
FROM reservation_frames rf
LEFT JOIN reservation_counts rc ON rf.id = rc.reservation_frame_id
WHERE rf.date >= DATE('now')
  AND (rf.capacity - COALESCE(rc.reserved_count, 0)) > 0
ORDER BY rf.date, rf.start_time
LIMIT 4;
`
}
  • Response
Error: Only SELECT queries are allowed for read-query

read-query:

①で発行したSQLを実行するとエラーが発生することがわかりエラー内容をもとにSQLを修正して再トライしています。

  • Request
{
  `query`: `
SELECT 
  rf.id,
  rf.date,
  rf.start_time,
  rf.end_time,
  rf.capacity,
  (SELECT COUNT(*) 
   FROM reservations r 
   WHERE r.reservation_frame_id = rf.id 
   AND r.status != 'cancelled') as reserved_count
FROM reservation_frames rf
WHERE rf.date >= DATE('now')
ORDER BY rf.date, rf.start_time
LIMIT 4;
`
}
  • Response
[{'id': 1, 'date': '2024-12-05', 'start_time': '10:00:00', 'end_time': '11:00:00', 'capacity': 1, 'reserved_count': 1}, {'id': 2, 'date': '2024-12-05', 'start_time': '11:00:00', 'end_time': '12:00:00', 'capacity': 1, 'reserved_count': 0}, {'id': 3, 'date': '2024-12-05', 'start_time': '12:00:00', 'end_time': '13:00:00', 'capacity': 1, 'reserved_count': 1}, {'id': 4, 'date': '2024-12-05', 'start_time': '13:00:00', 'end_time': '14:00:00', 'capacity': 1, 'reserved_count': 1}]

再トライの結果、予約枠の情報と予約の空きの有無がわかるようなデータが返ってきています。その内容をもとにユーザーに予約枠情報をユーザーに伝えています。(ただし、ここでは、空きが無い予約枠も返しているので、SQLが不完全であることがわかります。)

③ 空きありのものを4つ教えて

read-query:

ユーザーの要件に合わせて、件数を4件にしたり、Where句でstatusを考慮していることがわかります。(先程のクエリでstatusが考慮されていなかったことをLLM側で修正しています。)

  • Request
{
  `query`: `
SELECT 
  rf.id,
  rf.date,
  rf.start_time,
  rf.end_time,
  rf.capacity,
  (SELECT COUNT(*) 
   FROM reservations r 
   WHERE r.reservation_frame_id = rf.id 
   AND r.status != 'cancelled') as reserved_count
FROM reservation_frames rf
WHERE rf.date >= DATE('now')
  AND (SELECT COUNT(*) 
       FROM reservations r 
       WHERE r.reservation_frame_id = rf.id 
       AND r.status != 'cancelled') < rf.capacity
ORDER BY rf.date, rf.start_time
LIMIT 4;
`
}
  • Response
[{'id': 2, 'date': '2024-12-05', 'start_time': '11:00:00', 'end_time': '12:00:00', 'capacity': 1, 'reserved_count': 0}, {'id': 7, 'date': '2024-12-05', 'start_time': '16:00:00', 'end_time': '17:00:00', 'capacity': 1, 'reserved_count': 0}, {'id': 9, 'date': '2024-12-06', 'start_time': '11:00:00', 'end_time': '12:00:00', 'capacity': 1, 'reserved_count': 0}, {'id': 13, 'date': '2024-12-06', 'start_time': '15:00:00', 'end_time': '16:00:00', 'capacity': 1, 'reserved_count': 0}]

上記の情報をもとにユーザーに予約枠の提示と追加の情報を求めるような応答をしています。

④ 12月6日の11時から予約したいです

read-sql:

指定した時間に対して改めてまだ予約枠の空きがあるかをチェックしています。

  • Request
{
  `query`: `
SELECT 
  rf.id,
  rf.date,
  rf.start_time,
  rf.end_time,
  rf.capacity,
  (SELECT COUNT(*) 
   FROM reservations r 
   WHERE r.reservation_frame_id = rf.id 
   AND r.status != 'cancelled') as reserved_count
FROM reservation_frames rf
WHERE rf.id = 9;
`
}
  • Response
[{'id': 9, 'date': '2024-12-06', 'start_time': '11:00:00', 'end_time': '12:00:00', 'capacity': 1, 'reserved_count': 0}]

予約枠の空きが確認できたため、実際の予約実行処理に進みます。

⑤ <名前>です

ユーザーが名前を話した際、ここではツールの選択は行われませんでした。次に予約を実行するためには連絡先を聞く必要があると判断し、ここでは追加のヒアリングにとどまっています。

⑥ 連絡先は<メールアドレス>です

予約に必要な情報がすべて揃ったので、予約情報をreservationsテーブルに書き込みを行います。

write-query

  • Request
{
  `query`: `
INSERT INTO reservations (reservation_frame_id, customer_name, customer_contact, status)
VALUES (9, '友松祐太', 'tomomatsu@example.com', 'confirmed');
`
}
  • Response
[{'affected_rows': 1}]

DBからのレスポンスが正常だったので、予約が完了したことと、予約に使用した情報をユーザーに伝えています。また一通り予約実行全体のタスクが完了したのでクロージングのメッセージを伝えています。

このときのreservationsのテーブルを見てみると、最後の行に予約が追加されたことがわかります。

⑦ やっぱり予約変更したいです。

read-query

予約の変更が必要ということで予約枠の提示が必要と判断し、予約枠検索用のQueryを発行します。

  • Request
{
  `query`: `
SELECT 
  rf.id,
  rf.date,
  rf.start_time,
  rf.end_time,
  rf.capacity,
  (SELECT COUNT(*) 
   FROM reservations r 
   WHERE r.reservation_frame_id = rf.id 
   AND r.status != 'cancelled') as reserved_count
FROM reservation_frames rf
WHERE rf.date >= DATE('now')
  AND (SELECT COUNT(*) 
       FROM reservations r 
       WHERE r.reservation_frame_id = rf.id 
       AND r.status != 'cancelled') < rf.capacity
  AND rf.id != 9
ORDER BY rf.date, rf.start_time
LIMIT 4;
`
}
  • Response
[{'id': 2, 'date': '2024-12-05', 'start_time': '11:00:00', 'end_time': '12:00:00', 'capacity': 1, 'reserved_count': 0}, {'id': 7, 'date': '2024-12-05', 'start_time': '16:00:00', 'end_time': '17:00:00', 'capacity': 1, 'reserved_count': 0}, {'id': 13, 'date': '2024-12-06', 'start_time': '15:00:00', 'end_time': '16:00:00', 'capacity': 1, 'reserved_count': 0}, {'id': 16, 'date': '2024-12-07', 'start_time': '11:00:00', 'end_time': '12:00:00', 'capacity': 1, 'reserved_count': 0}]

Responseの予約枠情報をもとに、ユーザーに提示しています。先程予約実行した12月6日の11:00からの枠は空き枠が無くなったため、提示されていないことがわかります。また、これまでのやり取りから4件表示するのが適切と判断しqueryにも反映されています。

⑧ 12月8日以降は?

read-query

先ほどの条件だとユーザー側の条件に合わなかったため、条件の変更を求めています。QueryのWhere句の条件に2024-12-08以降という条件が付与されていることがわかります。

  • Request
{
  `query`: `
SELECT 
  rf.id,
  rf.date,
  rf.start_time,
  rf.end_time,
  rf.capacity,
  (SELECT COUNT(*) 
   FROM reservations r 
   WHERE r.reservation_frame_id = rf.id 
   AND r.status != 'cancelled') as reserved_count
FROM reservation_frames rf
WHERE rf.date >= '2024-12-08'
  AND (SELECT COUNT(*) 
       FROM reservations r 
       WHERE r.reservation_frame_id = rf.id 
       AND r.status != 'cancelled') < rf.capacity
ORDER BY rf.date, rf.start_time
LIMIT 4;
`
}
  • Response
[{'id': 24, 'date': '2024-12-08', 'start_time': '12:00:00', 'end_time': '13:00:00', 'capacity': 1, 'reserved_count': 0}, {'id': 28, 'date': '2024-12-08', 'start_time': '16:00:00', 'end_time': '17:00:00', 'capacity': 1, 'reserved_count': 0}, {'id': 32, 'date': '2024-12-09', 'start_time': '13:00:00', 'end_time': '14:00:00', 'capacity': 1, 'reserved_count': 0}, {'id': 36, 'date': '2024-12-10', 'start_time': '10:00:00', 'end_time': '11:00:00', 'capacity': 1, 'reserved_count': 0}]

Responseの予約枠情報をもとに、ユーザーに提示しています。

⑨ 12月9日のやつでお願いします

write-query①

まずは、既に入っている予約に対してキャンセル処理をかけます。

  • Request
{
  `query`: `
UPDATE reservations 
SET status = 'cancelled'
WHERE reservation_frame_id = 9 
AND customer_name = '友松祐太' 
AND customer_contact = 'tomomatsu@example.com'
AND status = 'confirmed';
`
}
  • Response
[{'affected_rows': 1}]

write-query②

続いて予約の実行処理を行います。ユーザーは12月9日のやつという曖昧な表現をしていますが、提示した候補は一つしかないので、12月9日の13:00-14:00ということを理解しています。また、すでに名前や連絡先の情報は聞けているため、ヒアリングがスキップされてreservationsテーブルへの書き込みを実行します。

  • Request
{
  `query`: `
INSERT INTO reservations (reservation_frame_id, customer_name, customer_contact, status)
VALUES (32, '友松祐太', 'tomomatsu@example.com', 'confirmed');
`
}
  • Response
[{'affected_rows': 1}]

予約が成功したので、新しい予約情報をユーザーに返却しています。ただし、ここでの一連のキャンセルと予約処理は、予約が成立しなかった場合に予約の削除だけが行われてしまうため、トランザクション管理は一部課題がありそうです。

一連の処理によって、DBの情報も更新されています。id=39の予約はcancelledになり、新たにid=40がconfirmedで予約がされていることがわかります。

⑩ ありがとうございます

ここでは特にツール選択の必要性がないため、クロージングの回答を返すのみとなっています。

⑪ 予約照会をしてください。以前にキャンセルした日付も合わせて教えて下さい。

read-sql

予約照会をするために、reservationsテーブルの照会を行います。指示通り、予約キャンセルしたものも対象になっています。

  • Request
{
  `query`: `
SELECT 
    r.id,
    rf.date,
    rf.start_time,
    rf.end_time,
    r.status,
    r.customer_name,
    r.customer_contact
FROM reservations r
JOIN reservation_frames rf ON r.reservation_frame_id = rf.id
WHERE r.customer_name = '友松祐太'
AND r.customer_contact = 'tomomatsu@example.com'
ORDER BY rf.date, rf.start_time;
`
}
  • Response
[{'id': 39, 'date': '2024-12-06', 'start_time': '11:00:00', 'end_time': '12:00:00', 'status': 'cancelled', 'customer_name': '友松祐太', 'customer_contact': 'tomomatsu@example.com'}, {'id': 40, 'date': '2024-12-09', 'start_time': '13:00:00', 'end_time': '14:00:00', 'status': 'confirmed', 'customer_name': '友松祐太', 'customer_contact': 'tomomatsu@example.com'}]

レスポンス内容をもとにユーザーにキャンセル済み、現在の予約をそれぞれ返却しています。

4. まとめ

MCPを使うことでこんなにも簡単に予約対話エージェントを作ることができました。また、LLM側が事前に知っている内容はSQLiteというDBがあり、そのDBに対して6つの操作ができるという設定しか知りません。LLMはユーザーとの対話が始まると自ら判断し、データベースのテーブルの一覧を取得したり、テーブルのスキーマ定義を取得したり、そのテーブル構造を理解したうえで、テーブルに対する読み込み処理、書き込み処理を柔軟に実施することができるということの一連をトレースすることができました。

外部情報へのアクセスはSQLiteだけでなく既にGitやローカルファイル、PostgreSQLなど実装があったり、自前でも構築することが可能です。これらを組み合わせることによってより高度なタスクを実行するエージェントの構築が可能になります。

今後のAIエージェントの発展にMCPが大きく関わっていくことはおそらく間違いないと思っています。AI Shift社としても周辺技術を引き続き調査・検証を進めていきたいと思います。

明日は、コンテンツチームの伊藤からの記事が上がる予定です。

ご精読いただきありがとうございました。

PICK UP

TAG