はじめに
前回の記事(クオンツのための DuckDB 入門 — 研究サイクルを回すための「組み込みOLAP」という選択)では、DuckDB がクオンツ研究のどこに刺さるのか、特に「正本データは PostgreSQL、分析は DuckDB」というハイブリッド構成と、Parquet レイク上で DuckDB を走らせる 3 層アーキテクチャを整理した。ASOF JOIN の強さや、pandas / Polars との相互運用の良さにも触れた。
今回はその続きとして、mcp-server-motherduck を取り上げたい。
これは DuckDB / MotherDuck を MCP (Model Context Protocol) 経由で AI アシスタント (Claude Desktop、Claude Code、Cursor、VS Code など) から直接操作できるようにするローカル MCP サーバーである。公式 README の表現を借りれば、"SQL analytics and data engineering for AI Assistants and IDEs" だ。
前回の結論は「正本は RDB、分析は DuckDB」だったが、今回はその上に、AI を分析層の "操作者" として組み込むとどうなるか、という一段上の話をする。筆者の見立てとしては、これは単なる便利ツールではなく、研究サイクルの反復コストをもう一段削るための接続層である、という捉え方が一番しっくりくる。
なお本稿は、「AI に SQL を書かせる一般論」ではなく、約定×気配の ASOF JOIN、TCA (取引コスト分析)、特徴量の初動集計といった、クオンツ研究の反復を速くするための話として書いている。汎用的な BI 用途の話ではない。
はじめに整理: DuckDB / MotherDuck / MCP サーバー は別物である
この話題で最初に躓きやすいのは、名前が似ている 3 つのものが出てくる点だ。先に切り分けておきたい。
| 名称 | 正体 | 役割 |
|---|---|---|
| DuckDB | ローカルで動く組み込み型の分析用 DB エンジン | 手元の .duckdb ファイルや Parquet に対して SQL を実行する本体 |
| MotherDuck | DuckDB をクラウドに拡張したマネージドサービス | DB をクラウド側に置き、保存・共有・スケーリングを提供する |
mcp-server-motherduck |
MotherDuck 社が公開しているローカル MCP サーバー実装 | AI クライアントから DuckDB / MotherDuck に接続するための橋渡し。サーバー名に "motherduck" が入っているが、MotherDuck 専用ではない |
ここが一番重要な点だ。mcp-server-motherduck は MotherDuck 専用の道具ではない。README の Quick Start には「ローカル DuckDB ファイルに read-only で接続する例」と「MotherDuck に read-write で接続する例」が並んで載っていて、接続先として次の 4 つを選べる。
- ローカルの
.duckdbファイル - インメモリ DB (
:memory:) - S3 上の
.duckdbファイル - MotherDuck クラウド (
md:)
v1 系からはデフォルトの --db-path も md: ではなく :memory: に変更されており、MotherDuck を使うときだけ --db-path md: を明示する建て付けになっている。これは「ローカル利用が前提でも成立する」という設計意図をかなりはっきり示していると思う。
つまり、構成の選択肢は大きく 2 系統あり、クオンツの研究用途ではどちらも現実的だ。
-
ローカル構成: 手元の PC / サーバーに
.duckdbを置き、MCP サーバー経由で Claude Code などからアクセスする。MotherDuck クラウドは使わない。 - クラウド構成: MotherDuck 上に DB を置き、MCP サーバー経由で Claude Code などから接続する。
MCP サーバーは両方の接続先を扱える「同じバイナリ」で、起動オプションで切り替えるだけだ。個人〜小チームの研究であれば、まずローカル構成で始め、共有やチーム展開が要る段階で MotherDuck を足す、という順番が素直だと考えている。
なお、MCP 経由でできることは「ファイルを GUI でアップロードする」ような操作ではない点も補足しておきたい。MCP サーバーが提供するのは SQL 実行とカタログ参照 (execute_query / list_tables / list_columns など) であって、データ投入も「SQL で CREATE TABLE ... AS SELECT * FROM read_parquet(...) のように書いてロードする」という形になる。言い換えれば、"AI から解析用 DB を SQL で操作・構築・分析する" のが MCP の本質であり、ファイル置き場やアップローダーではない。
以下、本稿では特に断らない限り ローカル構成 (手元の .duckdb に MCP から繋ぐ) を前提に話を進める。前回記事の 3 層アーキテクチャとの整合もそのほうが取りやすいからだ。
mcp-server-motherduck とは何か — 最小限の事実整理
まず冷静に、README から読み取れる事実を整理しておく。憶測で盛りすぎても実務の役に立たないので、仕様を押さえた上で意味を考えたい。
- ローカル MCP サーバーである。uvx 経由で起動し、Claude Desktop / Claude Code / Cursor / VS Code などの MCP クライアントから使う
- 接続先として ローカル DuckDB ファイル、インメモリ、S3 上の DuckDB ファイル、MotherDuck を選べる
- 提供されるツールは
execute_query/list_databases/list_tables/list_columns/switch_database_connection(フラグ必要) の 5 つ -
デフォルトは read-only。書き込みを許可するには
--read-writeを明示する必要がある - デフォルトの
--db-pathは:memory:。MotherDuck に繋ぐには--db-path md:を明示する -
MotherDuck に read-only で繋ぐには read-scaling token が必要。通常トークンを使うなら
--read-writeが必須 - 結果はデフォルトで 1024 行 / 50,000 文字に制限される (
--max-rows,--max-charsで調整可) - ローカル DuckDB ファイルに read-only で繋ぐ場合、ファイルロックを保持しない (
--ephemeral-connectionsがデフォルト true)。つまり別の write 接続と共存できる
この最後の点は地味だが重要なので、あとでもう一度戻ってくる。
なお、MotherDuck には別途 Remote MCP (MotherDuck がホストする零構築版) もあるが、本稿ではローカル版を前提に話す。ローカルファイルシステムを跨ぐクエリやローカル Parquet レイクとの統合はローカル版の方が素直だからだ。
前回記事のどこに接続するのか
前回提案した 3 層アーキテクチャを思い出してほしい。
- データソース層: PostgreSQL (注文・約定・ポジション等の正本) + 市場データ / オルタナデータ
- Parquet データレイク層: ローカル SSD または S3 上の Parquet
- DuckDB 分析層: リサーチ・特徴量生成・検証
MCP サーバーが入るのは 3 段目 である。より正確には、3 段目の DuckDB に対する新しい操作インターフェースとして入る。Python ノートブックから duckdb.connect() で叩いていたものを、AI アシスタントからも叩けるようにする、という位置づけだ。繰り返しになるが、ここで想定しているのは 手元の .duckdb に MCP から繋ぐローカル構成であって、MotherDuck クラウドは必須ではない。
ここで勘違いしたくないのは、MCP は Python を置き換えるものではない、という点だ。実際にはこういう構図になる。
- Python / Polars / pandas: 依然として主戦場。モデル訓練、複雑な変換、可視化、実験管理はこちら
- DuckDB: Parquet レイクに対する SQL クエリエンジン
- MCP サーバー: DuckDB への「AI からの入り口」をもう一本増やすもの
つまり、既存のパイプラインを書き換える必要はない。Python から触っている DuckDB ファイルに、AI 側からもう一本 read-only の穴を開けるだけだ。前述の ephemeral-connections のおかげで、同じ DuckDB ファイルに対して、自分の書き込み接続と AI の読み取り接続を並存させられる。これは運用上とても素直で、前回構成にそのまま乗る。
クオンツ研究で何が変わるのか
ここからが本題だ。MCP が入ると、研究サイクルのどこが変わるのか。精神論ではなく、具体的な工程で考えたい。
1. 「スキーマ確認と初動集計」の摩擦がほぼ消える
新しいデータセットを Parquet で受け取ったとき、最初にやることは大抵決まっている。どんなテーブル・カラムがあるか、型は何か、時系列の粒度は揃っているか、欠損はどこに出ているか、結合可能なキーは何か。この初動は地味だが必須で、ここで疲れると仮説検証まで辿り着けない。
MCP があると、この初動を AI に投げて、list_tables → list_columns → execute_query の連鎖を自動で回させられる。人間側は「このデータで何を見たいか」に早く移れる。前回記事で「研究サイクルの摩擦を減らす」と書いたが、MCP はその摩擦をさらに一段削ってくれる、と言える。
2. ASOF JOIN の試行錯誤を AI に委ねられる
前回強調したとおり、クオンツ実務で DuckDB が光るのは ASOF JOIN である。スリッページ分析、イベントスタディ、ファクター生成、いずれも「時刻の完全一致しないデータ同士の結合」という共通構造を持つ。
ここで問題になるのは、ASOF の向き (>= か <= か)、どちらのテーブルを左に置くか、BY 句に何を入れるか、時刻の型は揃っているか、といった細かい詰めの作業だ。SQL としては 1 行で書けるが、正しく書けるまでに数回の試行が要ることが多い。この "数回の試行" を AI 側にオフロードできるのが MCP の価値である。
筆者の感覚としては、「約定ログに直前 bid/ask を紐付けてスリッページの分布を出してくれ」くらいの依頼を自然言語で投げて、AI が ASOF JOIN を書き、実行し、結果を見て side の条件分岐を直し、再実行する、という往復を人間が眺めるだけで済むようになる。これは、SQL をゼロから書くより仮説の粒度で考える時間が増えるということだ。
3. 層別比較の反復が軽くなる
クオンツ研究では、「平均で見ると効く」ことよりも、「どの条件で効いてどの条件で効かないか」を切り分けることの方が遥かに重要だと考えている。銘柄別、時間帯別、ボラティリティ水準別、イベント前後、学習期間 vs 検証期間、などの切り口で同じ指標を繰り返し見る作業が必須になる。
この層別比較は、SQL で書くと GROUP BY を差し替えるだけだが、差し替え回数が多いほど人間の集中力が削られるのが実情だ。MCP 経由で AI に「この特徴量のワークフォワード窓ごとのシャープを、ボラレジーム別にも切って」と頼めると、同じ作業の精神的コストがはっきり下がる。
4. データエンジニアリング寄りの作業を安全に任せられる
前回触れなかった論点として、クオンツの現場は研究本体よりデータ整形の方が時間を食うことが多い。重複除去、日付フォーマットの統一、タイムゾーン、欠損埋め、外れ値の確認、ソースを跨ぐキーの正規化、などだ。
MCP のデフォルトが read-only なのは、この文脈で見ると非常に合理的だ。整形・検査のうち 読むだけで完結する作業 (分布確認、NULL 率、値域チェック、重複検出) は read-only のまま AI に任せられる。実際に書き戻す工程 (例えば整形済みの特徴量 Parquet を作る) は、従来どおり Python パイプライン側で単一の書き込み接続から実行すればいい。この**「読みは AI、書きは自前パイプライン」という分離**は、運用の安全性と AI の使いやすさを両立させやすい。
具体例: 約定×気配の TCA を MCP 経由でやってみる
ここまで抽象論が続いたので、1 本だけ具体例を通しで示す。題材は実効スプレッドの TCA (Transaction Cost Analysis)、つまり「自分の約定がそのときの気配に対してどれだけ不利な位置で刺さったか」を層別に見る、というクオンツ実務で典型的なユースケースだ。
前提データ
Parquet レイクから DuckDB に読み込んだ (または read_parquet で直接参照する) 以下の 2 テーブルがあるとする。
-
fills: 約定ログ。ts, symbol, side, price, qtyを持つ -
quotes: 気配 (top-of-book)。ts, symbol, bid, askを持つ
quotes は fills より高頻度で、時刻は完全には一致しない。前回記事で書いたとおり、この手の結合は ASOF JOIN の出番だ。
従来のワークフロー (MCP なし)
人間が Jupyter で作業する場合、たとえば次のような往復になる。
- Parquet のスキーマを
DESCRIBEで目視確認 - ASOF の向き (
>=か<=か) を決めて 1 回書いてみる -
slippageをsideで符号反転させるのを忘れて結果がおかしいので書き直す - 銘柄別
GROUP BYを書く - 時間帯別に切りたくなって
EXTRACT(hour FROM ts)を足す - ボラレジーム別にも切りたくなって、別途計算したレジームラベルを JOIN する
- NULL になった行の原因を追う (結合キーのタイムゾーンずれに気付く)
手を動かしている人には馴染みのある流れだと思う。SQL 自体は難しくないが、詰めが多いので初回結果までに 20〜30 分はかかる、というのが筆者の体感値だ。
MCP 経由のワークフロー
MCP 接続済みの Claude Code に、次のように投げる (実際に通る粒度で書いている)。
fillsとquotesを ASOF JOIN で紐付けて、実効スプレッドを計算してほしい。BUY はprice - ask、SELL はbid - priceで符号を揃えて bps 換算。まず銘柄別の平均・中央値・P95 を出して、次に時間帯 (JST 1 時間刻み) 別にも切ってほしい。NULL 行があれば件数だけ教えて。
AI はまず list_columns で両テーブルのカラム型とタイムゾーンを確認し (ここで人間が気付きにくいタイムゾーン差異に先回りで触れてくれることがある)、そのうえで次のような SQL を組み立てて execute_query で流す。
WITH joined AS (
SELECT
f.ts AS fill_ts,
f.symbol,
f.side,
f.price,
q.bid,
q.ask,
CASE
WHEN f.side = 'BUY' THEN (f.price - q.ask) / q.ask * 1e4
WHEN f.side = 'SELL' THEN (q.bid - f.price) / q.bid * 1e4
END AS eff_spread_bps
FROM fills f
ASOF JOIN quotes q
ON f.symbol = q.symbol AND f.ts >= q.ts
)
SELECT
symbol,
COUNT(*) AS n,
AVG(eff_spread_bps) AS mean_bps,
QUANTILE_CONT(eff_spread_bps, 0.5) AS p50_bps,
QUANTILE_CONT(eff_spread_bps, 0.95) AS p95_bps
FROM joined
WHERE eff_spread_bps IS NOT NULL
GROUP BY symbol
ORDER BY mean_bps DESC;
返ってくる結果はこんな粒度になる (値は例示)。
| symbol | n | mean_bps | p50_bps | p95_bps |
|---|---|---|---|---|
| 7203.T | 1,284 | 2.1 | 1.8 | 6.4 |
| 6758.T | 932 | 2.8 | 2.3 | 9.1 |
| 9984.T | 417 | 4.6 | 3.7 | 14.2 |
ここから人間は「9984 の P95 が飛んでいるのはどの時間帯か」を自然言語で追加質問するだけで、AI が 2 本目のクエリを組み立てる (GROUP BY symbol, hour に差し替えるだけ)。ボラレジーム別の切り分けも同様に、条件の追加 1 回ぶんの会話で済む。
体感の差は次のとおりだ。
| 工程 | 従来 (Jupyter) | MCP 経由 |
|---|---|---|
| スキーマ確認・型チェック | 3〜5 分 | AI が先行して実施 |
| ASOF 向きと符号の試行錯誤 | 5〜10 分 | 1 回で通ることが多い |
| 初回結果まで | 20〜30 分 | 5 分前後 |
| 層別 (時間帯・レジーム) 追加 | 5〜10 分 / 軸 | 会話 1 往復 / 軸 |
もちろん AI が毎回 1 発で正しい SQL を書くわけではない。ASOF の向きを逆にすることもあるし、符号を取り違えることもある。ただ、間違えたときにすぐ結果を見て直せるのが MCP の本質だ。従来は「書く → 実行 → 確認 → 書き直す」の全工程を人間が担っていたのに対し、MCP 経由では人間が担うのは「確認」と「次の問いの方向付け」だけになる。
この例から持ち帰ってほしいこと
-
AI が SQL を書くのではなく、AI が DuckDB を操作するという捉え方のほうが正確だ。
list_columns→execute_query→ 結果確認 → 修正、のループを AI が自走する - 人間の仕事は、問いの設計と結果の解釈に寄る。SQL を指で打つ時間は減る
- 前回記事で強調した ASOF JOIN の強さは、MCP と組み合わせるとさらに効く。ASOF を書く摩擦そのものが減るため、書けば 1 行だが書くまでが面倒だった、という壁が低くなる
設計上のポイント: どう組み込むと素直か
ここは前回の「正本は RDB、分析は DuckDB」を踏まえた上で、MCP をどこに挿すかという話になる。筆者の今の考えをいくつか挙げる。
ポイント 1: AI が触るのは分析層の DuckDB だけにする
AI に正本データ (PostgreSQL 上の注文・約定・ポジション) を直接触らせる必然性はない。MCP サーバーが接続するのは Parquet レイクの上に立てた研究用 DuckDB ファイルに限定するのが一番素直だ。前回構成で言えば、DuckDB 分析層の内側にのみ MCP の入り口がある、という絵になる。
ポイント 2: デフォルト read-only を崩さない
README で強調されているとおり、書き込みを許可するには --read-write を明示する必要がある。研究補助としての使い方では、ほぼすべての用途が read-only で足りる。書き込みを許可したい明確な用途 (例: AI に一時的な中間テーブルを作らせたい) が出てきた場合だけ、別のインスタンスを立てる、あるいは書き込み用の一時 DuckDB ファイルを別に用意する、という形にするのが良いと思う。同じ DuckDB ファイルを read/write 両方で AI に開放する必要はほとんどない。
ポイント 3: 書き込み系パイプラインは従来どおり Python で単一化
前回強調した「DuckDB は同時書き込み 1 プロセス制約」は MCP を挟んでも変わらない。特徴量 Parquet の生成のような書き込みパイプラインは、今まで通り Python スクリプトから単一接続で回す。MCP はあくまで並行する別の読み取り経路として共存させる。ephemeral-connections のおかげでファイルロックを奪わないので、この共存は無理なく成立する。
ポイント 4: --max-rows と --query-timeout は意識して設定する
デフォルトは 1024 行 / 50,000 文字 / タイムアウト無効だ。研究データだと 1024 行で足りない場面はすぐ出てくるが、上げすぎると AI に大量のデータを流すことになりコンテキストと料金を食う。また、タイムアウトが無効 (-1) のままだと、意図せず重いクエリが走り続けるリスクがある。層別集計の結果を渡すには十分だが、生データを全部舐めるには足りない、くらいの水準に絞るのが実務的だと思う。
ポイント 5: MotherDuck クラウドは「必要になったら足す」で十分 — ただし read scaling の癖には注意
冒頭で整理したとおり、MCP サーバーはローカル DuckDB にもクラウドの MotherDuck にも繋がる。個人〜小チームの研究用途なら、まずはローカル .duckdb への接続で始めるのが素直だ。チームでの共有、複数端末からの同時参照、クラウド実行リソースの活用といった要件が出てきた段階で、同じ .duckdb を MotherDuck に載せ替え、--db-path md: に切り替えればよい。MCP サーバーそのものは共通なので、この移行はオプション変更レベルで済む。
ただし、MotherDuck に移った瞬間にクオンツ文脈で引っかかりやすい癖が 2 つある。
- read scaling は eventually consistent: MCP を read-only で MotherDuck に繋ぐには read-scaling token が必要になるが、read scaling レプリカは writer から数秒〜数分遅れることがある。共有研究環境で「さっきパイプラインで書いた特徴量テーブルが、別端末の Claude Code からまだ見えない」という現象は普通に起き得る。厳密な再現性が要る検証 (例: バックテスト結果の突き合わせ) の直後に AI から覗きに行く場合は、少し待つか、書き込み側と同じ writer 接続を使うのが無難だ
-
複数ユーザーでの
session_hint: MotherDuck 公式 docs では、複数エンドユーザーで read scaling を使う場合、レプリカ affinity を安定させるためにsession_hintの指定が推奨されている。mcp-server-motherduckは--motherduck-connection-parametersでこれを渡せるので、チームで同じ MCP 設定を配る場合はユーザー毎に異なるsession_hintを割り当てるのがよい。これをやらないと、同じクエリでもユーザーによってヒットするレプリカがバラけ、キャッシュ効率やラグの体感が揃わない
要するに、ローカル構成では意識しなくていい運用論が、MotherDuck に移った瞬間に増える。この差分を把握した上で、必要になったタイミングで載せ替えるのが現実的だと思う。
ポイント 6: サービス化するなら本稿の想定外と考える
README の "Securing for Production" セクションも注意喚起しているが、ローカル MCP サーバーを第三者に開放するのは別の話になる。読み取り専用にしてもローカルファイルシステムや DuckDB 設定に触れるため、SaaS モードや Remote MCP の検討が必要になる。本稿で扱っているのは、あくまで個人または小チームが自分の研究環境に挿す使い方だ。
誤解しておきたくないこと
最後に線引きを一段落で済ませておく。MCP は (a) AI に売買判断を委ねる話ではない (最終的な投資判断を LLM に委ねるべきではないという筆者の立場は変わらない)、(b) Python を置き換える話でもない (複雑な特徴量エンジニアリングや ML パイプラインは引き続き Python / Polars / scikit-learn が主戦場)、(c) 大規模 DWH の代わりでもない (DuckDB の主戦場は単一ノード分析のまま)。MCP が変えるのは、あくまで研究工程における AI の関与の仕方であって、それ以外の役割分担ではない。
向いているケース / 向いていないケース
前回と同じフォーマットで整理しておく。
向いているケース
- 個人〜小チームでクオンツ研究をしていて、既に DuckDB / Parquet レイクの構成を持っている (あるいは作ろうとしている)
- 研究の初動 (スキーマ把握・層別集計・分布確認) に AI の手を借りたい
- ASOF JOIN のような時系列結合の試行錯誤を減らしたい
- read-only を基本に、安全に AI をデータに近づけたい
向いていないケース
- 本番執行系の DB を AI から直接操作したい (そもそも推奨しない)
- 第三者にも開放するサービスとして MCP を立てたい (Remote MCP や SaaS モードの検討が先)
- 書き込みが主体の ETL 工程を AI に全面的に任せたい (同時書き込み 1 プロセス制約と衝突しやすい)
まとめ
前回記事の結論は、クオンツ研究では DuckDB を分析層に据え、正本は RDB に任せるのが素直、というものだった。今回の mcp-server-motherduck は、その構成に自然に乗る形で AI の手を生やすための接続層だと考えている。
ポイントを 3 つに絞ると次のようになる。
- 挿す場所は分析層の DuckDB だけ。正本データには触らせない
- デフォルトの read-only を基本にする。書きは従来どおり Python パイプラインで単一化する
- 狙いは研究サイクルの反復コスト削減。売買判断を任せる話とは別物
前回「まずローカルの Parquet を一枚 duckdb.connect() で開いてみるところから」と書いた。今回の次の一手としては、その DuckDB ファイルに、AI アシスタントからもう一本 read-only の入り口を開けてみるところから試すのがよいと思う。クオンツ研究の回転数を上げるための、地味だが効きが良い一手になるはずだ。