8
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【MCP】自然言語で競馬データを分析できるMCPサーバーを作りました

8
Last updated at Posted at 2025-12-05

画面収録 2025-12-05 21.34.13.gif

はじめに

JRA-VAN DataLabの競馬データベースは非常に巨大です。64テーブル、1,800以上のカラムがあり、レース情報、馬情報、騎手成績、オッズ、払戻金など、あらゆるデータが格納されています。

しかし、このデータを活用しようとすると、いくつかの壁にぶつかります:

  • SQLの文法を覚える必要がある
  • テーブル構造やカラム名を把握しなければならない
  • 分析ツールの使い方を学ぶ必要がある

そこで今回、MCP(Model Context Protocol) を使って、自然言語で競馬データを分析できるシステムを構築しました。

「東京芝2400mの人気別成績を教えて」

このように聞くだけで、AIが適切なSQLを生成し、結果を返してくれます。

本記事では、このMCPサーバーの設計と、1,800カラムのスキーマ情報をどのようにしてAIに理解させたかを解説します。

リポジトリ:


MCPとは

MCP(Model Context Protocol) は、2024年11月にAnthropic社が発表したオープンプロトコルです。AIモデルが外部ツールやデータソースにアクセスするための標準規格として設計されています。

┌─────────────┐     MCP Protocol     ┌─────────────┐
│   Claude    │ ◄──────────────────► │ MCP Server  │
│  (AI Model) │     JSON-RPC 2.0     │  (Tools)    │
└─────────────┘                      └─────────────┘

MCPには以下のような特徴があります:

  • プロトコルの標準化 - 各ツールが独自のAPI設計をする必要がありません
  • 双方向通信 - AIからの呼び出しだけでなく、サーバーからの通知も可能です
  • スキーマ情報の提供 - ツールの使い方をAIに伝える仕組みが用意されています

今回はこのMCPを使って、競馬データベースへのアクセス層を構築しました。


アーキテクチャ

全体構成

┌──────────────────┐
│  Claude Desktop  │
│  / Claude Code   │
└────────┬─────────┘
         │ MCP Protocol (stdio)
┌────────▼─────────┐
│  jvlink-mcp      │  ← Python + FastMCP
│  server          │
└────────┬─────────┘
         │ SQL
┌────────▼─────────┐
│  SQLite /        │  ← 64テーブル, 1,800+カラム
│  PostgreSQL      │
└──────────────────┘

技術スタック

レイヤー 技術
AIモデル Claude 3.5 Sonnet
MCPクライアント Claude Desktop / Claude Code
MCPサーバー Python 3.10+ / FastMCP
データベース SQLite(開発)/ PostgreSQL(本番)
データソース JRA-VAN DataLab(COM API)

課題:1,800カラムをどうAIに理解させるか

ここが今回のプロジェクトで最も苦労したポイントです。

JRA-VANのデータベースには KakuteiJyuniSyussoTosuChakuKaisuJyo0ChakuKaisu1 といった日本語ローマ字のカラム名が大量にあります。AIにとっては意味不明な文字列です。

単純にテーブル構造だけ渡しても、AIは適切なカラムを選ぶことができません。

解決策:パターンベースの説明生成

全カラムに対して説明を返す関数を実装しました。

def generate_column_description(table_name: str, column_name: str) -> str:
    col = column_name

    # 基本カラム
    if col == "KakuteiJyuni":
        return "確定着順(最終確定順位、0=着外)"
    if col == "Ninki":
        return "人気順位(確定後、1=1番人気)"
    if col == "SyussoTosu":
        return "出走頭数"

    # 配列パターン(着度数など)
    # ChakuKaisuJyo0ChakuKaisu1 → 札幌2着回数
    jyo_match = re.match(r'ChakuKaisuJyo(\d+)ChakuKaisu(\d+)', col)
    if jyo_match:
        jyo_idx = int(jyo_match.group(1))
        place_idx = int(jyo_match.group(2))
        jyo_names = ["札幌", "函館", "福島", "新潟", "東京",
                     "中山", "中京", "京都", "阪神", "小倉"]
        place_names = ["1着", "2着", "3着", "4着", "5着", "着外"]
        return f"{jyo_names[jyo_idx]}{place_names[place_idx]}回数"

    # ... 1,800パターン以上

正規表現でパターンマッチングし、動的に説明を生成しています。これにより:

  • 札幌1着回数東京3着回数といった組み合わせカラムにも対応できます
  • TanOdds0〜TanOdds17(単勝オッズ1番馬〜18番馬)のような配列にも対応できます
  • 新しいカラムが追加されても、パターンが合えば自動で説明が生成されます

最終的に1,800カラムの100%をカバーすることができました。


提供ツール

MCPサーバーが公開するツールは以下の通りです:

@mcp.tool()
def execute_sql(query: str) -> str:
    """任意のSELECT文を実行"""

@mcp.tool()
def get_horse_info(horse_name: str) -> dict:
    """馬名から詳細情報を取得"""

@mcp.tool()
def get_jockey_stats(jockey_name: str, year: int = None) -> dict:
    """騎手の成績を取得"""

@mcp.tool()
def get_race_results(year: int, month: int, day: int,
                     jyo_cd: str, race_num: int) -> dict:
    """レース結果を取得"""

@mcp.tool()
def list_tables() -> list:
    """テーブル一覧を取得"""

@mcp.tool()
def describe_table(table_name: str) -> dict:
    """テーブル構造と各カラムの説明を取得"""

execute_sql で自由度を確保しつつ、get_horse_info などの高レベルAPIで使いやすさも両立させています。


実行例

例1:コース別の人気別成績

入力:

東京競馬場の芝2400mで、人気別の成績を教えて

AIが生成したSQL:

SELECT
    se.Ninki as 人気,
    COUNT(*) as 出走,
    SUM(CASE WHEN se.KakuteiJyuni = 1 THEN 1 ELSE 0 END) as 勝利,
    SUM(CASE WHEN se.KakuteiJyuni <= 3 THEN 1 ELSE 0 END) as 複勝
FROM NL_SE se
JOIN NL_RA ra ON se.Year = ra.Year
    AND se.MonthDay = ra.MonthDay
    AND se.JyoCD = ra.JyoCD
    AND se.RaceNum = ra.RaceNum
WHERE ra.JyoCD = '05'           -- 東京
  AND ra.Kyori = 2400           -- 2400m
  AND ra.TrackCD LIKE '1%'      -- 芝
  AND se.KakuteiJyuni > 0
  AND se.Ninki > 0
GROUP BY se.Ninki
ORDER BY se.Ninki

出力:

人気 出走 勝利 3着内率
1番人気 30 14 76.7%
2番人気 30 7 70.0%
3番人気 30 2 40.0%

ここで注目してほしいのは、ユーザーは「東京」「芝」「2400m」といった自然な言葉で質問しているだけという点です。競馬場コードが「05」であることも、芝コースのトラックコードが「1x」で始まることも、知る必要がありません。AIがスキーマ情報をもとに適切なSQLを組み立ててくれます。


例2:騎手の勝率ランキング

入力:

2024年以降の騎手別勝率を教えて。騎乗数100回以上で上位10人。

出力:

騎手名 騎乗数 勝利 勝率
渡邊竜也 240 86 35.8%
モレイラ 122 35 28.7%
ルメール 536 145 27.1%
川田将雅 525 112 21.3%

例3:回収率シミュレーション

入力:

単勝オッズ1.5倍以下の馬の勝率と回収率を計算して

出力:

指標
対象レース 87件
勝率 81.6%
単勝回収率 89%

「勝率は高いが回収率はマイナス」という競馬の本質がデータで確認できます。


自然言語インターフェースのメリット

実際に使ってみて感じた、自然言語で検索できることのメリットをまとめます。

1. あいまいな入力でも検索できる

従来のSQLや検索ツールでは、正確なカラム名やコード値を知っている必要がありました。しかし自然言語なら、あいまいな表現でも意図を汲み取ってくれます。

「ダービーのコースの成績」
→ 東京芝2400mと解釈して検索

「ルメールの重馬場の成績」
→ 馬場状態コードを自動で判定

「去年のG1で1番人気が負けたレース」
→ 複数テーブルをJOINして該当レースを抽出

ユーザーが「重馬場」と言えば、それが馬場状態コード「3」であることをAIが理解します。「ダービーのコース」と言えば「東京芝2400m」と解釈します。

2. ツールの使い方を覚える必要がない

BIツールやデータ分析ソフトは、それぞれ独自のUIや操作方法を持っています。新しいツールを導入するたびに学習コストがかかります。

自然言語インターフェースなら、日本語で質問するだけです。「どのボタンを押せばいいか」「どのメニューを開けばいいか」を考える必要がありません。

3. 思いついた疑問をすぐに検証できる

データ分析で重要なのは、仮説を素早く検証することです。「こういう傾向があるのでは?」と思ったら、すぐに聞いてみることができます。

「前走1着馬の次走成績は?」
「外枠の馬は不利?」
「休み明けの馬の成績は?」

SQLを書く時間を省けるので、より多くの仮説を短時間で検証できます。


パフォーマンス

SQLiteでの実行時間の目安は以下の通りです:

クエリ種別 実行時間
単純SELECT < 100ms
JOINあり集計 100-500ms
全テーブルスキャン 1-3s

インデックスは主要な検索パターンに対して設定済みです。PostgreSQLを使えばさらに高速化できます。


セットアップ

1. データインポート

pip install git+https://github.com/miyamamoto/jrvltsql.git
python scripts/quickstart.py  # 対話形式でセットアップ

2. MCPサーバーインストール

mcpb install miyamamoto/jvlink-mcp-server

3. 環境変数

# SQLiteの場合
DB_TYPE=sqlite
DB_PATH=/path/to/keiba.db

# PostgreSQLの場合
DB_TYPE=postgresql
DB_HOST=localhost
DB_NAME=keiba
DB_USER=user
DB_PASSWORD=password

今後の展望

  • 予測モデル連携 - MCPツールとして機械学習モデルを呼び出し

まとめ

MCPを使うことで、64テーブル・1,800カラムの競馬データベースを自然言語で分析できるシステムを構築しました。

ポイントは以下の3つです:

  1. スキーマ説明の100%カバー - パターンマッチングで1,800カラムすべてに説明を付与しました
  2. 高レベルAPI + SQL直接実行 - 使いやすさと自由度を両立させています
  3. SQLite/PostgreSQL両対応 - 開発から本番まで同じコードで動作します

競馬データに限らず、業務システムのデータベースにも同様のアプローチが適用できると思います。


リンク


注意: JRA-VAN DataLabの利用には会員登録(有料)が必要です。

8
8
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?