はじめに
こんにちは。株式会社船井総合研究所の平尾です。
今回は、前回の記事 MCPを使って機械学習してみた!の続きです。
前回はClaudeのチャット入力欄で数値計算に必要な情報を与え、MCPで計算させていましたが、今回はその情報をあらかじめDBに格納します。
ClaudeをMCPに繋いで、DBの情報をもとに数値計算させてみるというのが今回の内容です。
※scikit-learnのdiabetesデータを用いて作成した機械学習モデルを用いて計算します。適宜以下の記事を参考にしてください。
モデルを格納したpickleファイルの準備:MCPを使って機械学習してみた!
モデル構築の流れ:グリッドサーチとOptunaを実際に使って比較してみた!
環境を準備する
MCP実行環境の準備
# Pythonプロジェクト作成
uv init hellomcp
cd hellomcp
# 仮装環境作成
uv venv
source .venv/bin/activate
# MCPサーバーのPython SDKをインストール
uv add "mcp[cli]"
※MCPサーバーのテストツール"MCP Inspector"の使い方より引用
uvのインストールに関しては、uvの使い方: Pythonパッケージ&プロジェクトマネージャーをご覧ください。
必要なライブラリのインポート
uv pip install pandas
uv pip install xgboost
DBを準備する
SQliteを用いてDBを準備します。
参考記事:python3でsqlite3の操作。作成や読み出しなどの基礎。
データベースを作成する
import sqlite3
# TEST.dbを作成する
# すでに存在していれば、それにアスセスする。
dbname = 'TEST.db'
conn = sqlite3.connect(dbname)
# データベースへのコネクションを閉じる。(必須)
conn.close()
テーブルを作成する
import sqlite3
# TEST.dbを作成する
# すでに存在していれば、それにアクセスする。
dbname = 'TEST.db'
conn = sqlite3.connect(dbname)
# sqliteを操作するカーソルオブジェクトを作成
cur = conn.cursor()
# personsというtableを作成してみる
# 大文字部はSQL文。小文字でも問題ない。
cur.execute(
'CREATE TABLE persons(' \
'id INTEGER PRIMARY KEY AUTOINCREMENT,' \
'name STRING,' \
'age INTEGER,' \
'sex STRING,' \
'bmi REAL,' \
'bp REAL,' \
'tc REAL,' \
'ldl REAL,' \
'hdl REAL,' \
'tch REAL,' \
'ltg REAL,' \
'glu REAL)')
# データベースへコミット。これで変更が反映される。
conn.commit()
conn.close()
import sqlite3
dbname = 'TEST.db'
conn = sqlite3.connect(dbname)
cur = conn.cursor()
person_data = [
('Taro', 24, '男性', 20.0, 30.4, 100.0, 150.9, 90.3, 5.6, 3.2, 80.0),
('Hanako', 40, '女性', 30.0, 90.4, 200.0, 120.9, 78.3, 8.6, 2.2, 70.0),
('Bob', 30, '男性', 50.0, 40.4, 150.0, 130.9, 78.3, 9.6, 3.2, 100.0)
]
insert_sql = """
INSERT INTO persons (name, age, sex, bmi, bp, tc, ldl, hdl, tch, ltg, glu)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
"""
cur.executemany(insert_sql, person_data)
conn.commit()
cur.close()
conn.close()
MCPサーバーの構築
from mcp.server.fastmcp import FastMCP
import logging
import pickle
import sqlite3
import pandas as pd
from typing import List, Dict
import xgboost
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
# サーバーインスタンスの作成
mcp = FastMCP(
name="calculator",
description="糖尿病進行度を予測します",
)
@mcp.resource(uri="db://persons")
async def get_persons_resource() -> List[Dict]:
dbname = "TEST.db"
conn = sqlite3.connect(dbname)
cur = conn.cursor()
# dbをpandasで読み出す。
df = pd.read_sql('SELECT * FROM persons', conn)
dict1= df.to_dict(orient='records')
cur.close()
conn.close()
return dict1
@mcp.tool()
async def calculate() -> List[Dict]:
dict1 = await get_persons_resource()
df2 = pd.DataFrame(dict1)
df3 = df2.drop(columns=['id', 'name'], errors='ignore')
map = {'男性': 1, '女性': 2}
df3['sex'] = df2['sex'].map(map)
# データの正規化
df3['age'] = (df3['age'] -48.5180995)/13.1090278
df3['sex'] = (df3['sex']-1.4683258)/0.4995612
df3['bmi']= (df3['bmi']-26.3757919)/4.4181216
df3['bp'] = (df3['bp']-94.6470136)/13.8312834
df3['tc'] = (df3['tc']-189.1402715)/34.6080517
df3['ldl'] = (df3['ldl']-115.4391403)/30.4130810
df3['hdl']= (df3['hdl']-49.7884615)/12.9342022
df3['tch']= (df3['tch']-4.0702489)/1.2904499
df3['ltg']= (df3['ltg']-4.6414109)/0.5223906
df3['glu'] = (df3['glu']-91.2601810)/11.4963347
loaded_model = pickle.load(open("model.pickle", "rb"))
prediction = loaded_model.predict(df3)
#出力値の辞書の作成
dict2 = dict(enumerate(prediction))
df4 = df2['name']
dict3 = df4.to_dict()
output_data = {dict3[key]: dict2[key] for key in dict2}
#入力値(説明変数)の辞書の作成
dict4 = df2.to_dict()
dict5 = {
dict4['name'][i]: {
key: dict4[key][i] for key in dict4
}
for i in dict4['id'].keys()
}
keys_to_remove = {'id', 'name'}
input_data = {
name: {
k: v for k, v in inner_dict.items()
if k not in keys_to_remove
}
for name, inner_dict in dict5.items()
}
#上で作成した2つの辞書(input_data, output_data)の統合
merged_data = [
{
'name': name,
'input': input_data[name],
'output': float(output_data[name])
}
for name in input_data
]
return merged_data
def main():
mcp.run(transport='sse')
if __name__ == "__main__":
logger.info("糖尿病進行度を予測します")
main()
resourceでDBを呼び出し、toolでDBを用いて数値計算させます。
コーディングは以下の記事を参考にしています。
Part1 : Azure AI Foundry で MCPを使ってみた【深掘りと最新動向調査】
リモートで動作する MCP Server を実装し、Claude アプリから呼び出してみる
また、データの正規化に用いる平均値、標準偏差に関しては、Diabetes dataの「Proc Means and Proc Print Output」のデータを用いています。
性別に関しては、元データが1,2というように表されており、それぞれの数字がどちらの性別を示すかは調べてもわかりませんでした。
そのため、ここでは男性を1、女性を2として設定しました。
MCPをClaudeと接続する。
それでは、先ほど作ったmcp_sse.pyを実行してみます。
python mcp_sse.py
続いて、ClaudeをMCPに接続します。
接続方法に関しては、MCPを使って機械学習してみた!の「MCPサーバーをClaudeに接続」の「Claudeを開き~」をご確認ください。
Claudeに計算指示をする
チャットで「MCPを用いて糖尿病進行度を予測してください。」と指示をします。
すると以下の内容が返ってきます。
このように、計算を実行してくれました。
計算結果が正しいか確認する
Claudeが適当な数値を出力していないか確認するコードがこちらです。ここではMCPとしての機能は与えていません。
import pickle
import sqlite3
import pandas as pd
import xgboost
import pprint
dbname = "TEST.db"
conn = sqlite3.connect(dbname)
cur = conn.cursor()
df = pd.read_sql('SELECT * FROM persons', conn)
dict1= df.to_dict(orient='records')
cur.close()
conn.close()
df2 = pd.DataFrame(dict1)
df3 = df2.drop(columns=['id', 'name'], errors='ignore')
map = {'男性': 1, '女性': 2}
df3['sex'] = df2['sex'].map(map)
# データの正規化
df3['age'] = (df3['age'] -48.5180995)/13.1090278
df3['sex'] = (df3['sex']-1.4683258)/0.4995612
df3['bmi']= (df3['bmi']-26.3757919)/4.4181216
df3['bp'] = (df3['bp']-94.6470136)/13.8312834
df3['tc'] = (df3['tc']-189.1402715)/34.6080517
df3['ldl'] = (df3['ldl']-115.4391403)/30.4130810
df3['hdl']= (df3['hdl']-49.7884615)/12.9342022
df3['tch']= (df3['tch']-4.0702489)/1.2904499
df3['ltg']= (df3['ltg']-4.6414109)/0.5223906
df3['glu'] = (df3['glu']-91.2601810)/11.4963347
loaded_model = pickle.load(open("model.pickle", "rb"))
prediction = loaded_model.predict(df3)
#出力値の辞書の作成
dict2 = dict(enumerate(prediction))
df4 = df2['name']
dict3 = df4.to_dict()
output_data = {dict3[key]: dict2[key] for key in dict2}
#入力値(説明変数)の辞書の作成
dict4 = df2.to_dict()
dict5 = {
dict4['name'][i]: {
key: dict4[key][i] for key in dict4
}
for i in dict4['id'].keys()
}
keys_to_remove = {'id', 'name'}
input_data = {
name: {
k: v for k, v in inner_dict.items()
if k not in keys_to_remove
}
for name, inner_dict in dict5.items()
}
#上で作成した2つの辞書(input_data, output_data)の統合
merged_data = [
{
'name': name,
'input': input_data[name],
'output': float(output_data[name])
}
for name in input_data
]
pprint.pprint(merged_data)
実行すると、以下の結果が出力されます。
[{'input': {'age': 24,
'bmi': 20.0,
'bp': 30.4,
'glu': 80.0,
'hdl': 90.3,
'ldl': 150.9,
'ltg': 3.2,
'sex': '男性',
'tc': 100.0,
'tch': 5.6},
'name': 'Taro',
'output': 98.32352447509766},
{'input': {'age': 40,
'bmi': 30.0,
'bp': 90.4,
'glu': 70.0,
'hdl': 78.3,
'ldl': 120.9,
'ltg': 2.2,
'sex': '女性',
'tc': 200.0,
'tch': 8.6},
'name': 'Hanako',
'output': 143.7842254638672},
{'input': {'age': 30,
'bmi': 50.0,
'bp': 40.4,
'glu': 100.0,
'hdl': 78.3,
'ldl': 130.9,
'ltg': 3.2,
'sex': '男性',
'tc': 150.0,
'tch': 9.6},
'name': 'Bob',
'output': 158.55596923828125}]
計算結果のみならず、年齢、性別、BMIなどといった説明変数の値も、Claudeが出力したものと同じになっています。(Bobのtchをhba1cと認識しているのは間違いですが、数値に間違いはありません。)
Claudeに色々お願いしてみる
おわりに
Claudeに様々な指示をするためのポイントは、
MCPのtoolの設定において、説明変数も込みで出力させるようコーディングすることです。
そうすることで、Claudeが元データを認識してくれます。
以上です。
最後までお読みいただきありがとうございました!