Skip to main content
Glama
davidmrguo

tabulite-mcp

by davidmrguo

Tabulite MCP

スプレッドシートには大きすぎ、チャットに貼り付けるにも大きすぎるCSVファイルを分析するには、データそのものではなく、ローカルのSQLiteランタイムをAIアシスタントに渡します。

Tabulite MCP(略称Tabulite)は、ローカルMCPサーバーです。CSVファイルのフォルダを指定すると、デスクトップのAIクライアントがそれらをSQLiteにインポートし、カラムに実際に何が入っているかを調べ、SQLを書いて質問に答えることができます — データの1行たりともマシンの外に出たり、会話に入り込んだりすることはありません。

Desktop AI client  →  MCP  →  Tabulite  →  sqlite3  →  your CSV files
   (the reasoning)                (safe, deterministic tools)

サーバーの中にLLMはありません。考えるのはAIクライアントです。Tabuliteは、考えるためのメタデータ、探索のための読み取り専用SQLインターフェース、そして答えが文章ではなくデータセットである場合のディスクへの直接パスを提供します。


なぜ

500 MBのCSVについてAIに尋ねるとき、選択肢はどれも悪いものばかりです:サンプルを貼り付ければ答えを失い、全体をアップロードすればコンテキストウィンドウを消費し(そしてデータをどこかに送ることになり)、自分でスクリプトを書くことになります。

1台のマシンと1つのSQLiteファイルがあれば、このサイズでも余裕で処理できます。Tabuliteはそのランタイムをデータの隣に配置し、MCP経由で公開します。アシスタントは数百トークンのカラムプロファイルを読み、SQLを書き、集計結果を受け取ります。行データはディスクに残ったままです。

向いている用途: 自分のラップトップでのCSVエクスポート、ログダンプ、抽出データの単発分析 — Excelでは扱いきれなくなったが、1台のマシンに収まるべきファイル。 向いていない用途: 本番パイプライン、定期ETL、マルチユーザーアクセス、または本物のデータウェアハウスに属するものすべて。


Related MCP server: csv-mcp-server

クイックスタート

必要環境: Docker Desktop(またはDocker Engine + Compose)。これだけです — Pythonのセットアップは不要です。

git clone https://github.com/davidmrguo/tabulite-mcp.git
cd tabulite-mcp
docker compose up --build

サーバーは現在 http://localhost:8000/mcp で稼働しており、ヘルスチェックは http://localhost:8000/health にあります。

2つの小さなサンプルCSV(source/sales.csvsource/customers.csv)がリポジトリに同梱されているので、すぐに試せます。AIクライアントを接続し(下記)、次のように尋ねてみてください:

「sales.csvを分析して。どのチャネルが最も収益を生み出した?」

アシスタントは list_sources()import_source("sales.csv")profile_table("sales") を呼び出し、次のようなSQLを書きます:

SELECT channel,
       SUM(TRY_REAL(revenue)) AS revenue,
       COUNT(TRY_REAL(revenue)) AS valid_rows,
       COUNT(*) AS total_rows
FROM sales
GROUP BY channel
ORDER BY revenue DESC;

AIクライアントを接続する

Claude Code

claude mcp add --transport http tabulite http://localhost:8000/mcp

JSON設定を持つクライアント(Claude Desktop、Cursorなど):

{
  "mcpServers": {
    "tabulite": {
      "type": "http",
      "url": "http://localhost:8000/mcp"
    }
  }
}

stdioのみに対応したクライアント: URLの前に mcp-remote のようなブリッジを配置します。

自分のデータを使う

CSVファイルを source/ に置くだけです — 再起動は不要です:

cp ~/Downloads/huge_export.csv source/

ファイルは読み取り専用でマウントされ、gitignoreされているため、コミットされることも、サーバーが変更することもありません。Tabuliteが作成するもの(データベース、エクスポート)はすべて workspace/ に置かれます。


AIが使えるツール

Tool

機能

list_sources()

source/ 配下のCSVファイル(サイズとインポート状態付き)

inspect_source(path)

カラム、区切り文字、数件のサンプル行 — インポートなし

import_source(path, table_name?, delimiter?, force?)

CSVをSQLiteにストリーミングしてプロファイリングする

list_tables()

インポート済みテーブル(行数と由来付き)

profile_table(table_name, refresh?)

全カラムのコンパクトなプロファイル

profile_column(table_name, column_name)

1つのカラムの詳細(例付き)

sample_table(table_name, limit=20)

数行のデータ — データの見た目を確認するため

query_sql(sql)

読み取り専用の分析SQL(最大1,000行)

export_query(sql, file_name?, format="csv")

完全な結果をファイルにストリーミングする

特筆すべきは、ドメイン固有のものが一切ないことです。top_products()calculate_revenue() はありません。SQLを書くのはアシスタントであり、それがこの設計の要点です — 誰も予期しなかった質問にも答えられます。


仕組み

CSVフィールドは意図的にTEXTとして保存される

インポートされたすべてのカラムは TEXT です:

CREATE TABLE sales (
    transaction_id   TEXT,
    transaction_date TEXT,
    revenue          TEXT,
    quantity         TEXT
);

インポート時に型を推測すると、誰もデータを見る前にデータが壊れます:"1,234"1 になり、先頭ゼロの製品コードは整数になり、"2025-13-40" は黙ってNULLになります。そこでストレージはファイルの記述をそのまま保持し、解釈は後で、可視かつ可逆な形で行われます。

プロファイルがカラムの意味をAIに伝える

インポート後、すべてのカラムがプロファイリングされ、結果は workspace/catalog.sqlite に保存されます。以下は同梱サンプルでの実際の出力です:

column             logical_type  confidence  nulls  invalid  recommended_cast
transaction_id     TEXT          1.000       0      0        none
transaction_date   DATE          1.000       0      0        TRY_DATE
customer           TEXT          1.000       0      0        none
product            TEXT          1.000       0      0        none
channel            TEXT          1.000       9      0        none
quantity           INTEGER       0.996       0      2        TRY_INTEGER
revenue            REAL          0.996       36     2        TRY_REAL

profile_column("sales", "revenue") はさらに踏み込み、実際の問題値を表示します:invalid_examples: ["pending", "unknown"]

推論は控えめです — 非NULL値の99%以上がその型としてパースできる場合にのみ型が割り当てられます。プロファイルはAIへの証拠であり、ストレージ層への指示ではありません。インポート済みデータが推測に合わせて書き換えられることは決してありません。

CAST の代わりに TRY_* 関数

SQLiteの CAST は危険なほど寛容です:

CAST('unknown' AS REAL)    -- 0.0   ← quietly wrong
CAST('12 apples' AS REAL)  -- 12.0  ← quietly wrong

数千個の 'unknown' 値を含むカラムに対する AVG() は、黙ってゼロとして平均に含めます。そこでTabuliteはすべての接続で厳密な変換を登録します:

TRY_REAL('125.5')    -- 125.5
TRY_REAL('')         -- NULL
TRY_REAL('unknown')  -- NULL

他にも TRY_INTEGERTRY_DATETRY_DATETIMETRY_BOOLEAN が利用できます。SQLiteの集計関数はNULLをスキップするため、不正な値はゼロとして数えられるのではなく除外され、アシスタントは分母を確認できます:

SELECT AVG(TRY_REAL(revenue)) AS average_revenue,
       COUNT(TRY_REAL(revenue)) AS valid_rows,   -- 462
       COUNT(*)                 AS total_rows    -- 500
FROM sales;

欠損データと不正データは区別されたまま

設定された欠損値マーカーのみがSQL NULL になります。単にパースに失敗した値は、書かれたとおりに保持されます:

CSVの値

保存される値

125.40

"125.40"

(空)

NULL

N/A

NULL

unknown

"unknown"

-

"-"

デフォルトのマーカー:空文字列、NULLnullN/ANA。「このフィールドは空だった」と「このフィールドにはゴミが入っていた」は異なる発見であり、インポート時にそれらをまとめてしまうと、目に見える価値のあるデータ品質の問題が隠れてしまいます。

ファイルは名前ではなく内容で識別される

sales.csvsales_FINAL_v2.csv にリネームして再インポートすると、Tabuliteは内容を認識し、テーブルを複製せずに既存のものを再利用します。同一性はファイルのSHA-256であり、別途の読み取りではなくインポート処理の最中に計算されます。1バイト変えると、独自のテーブルを持つ新しいソースになります。

インポートされたものはすべて1つのデータベース(workspace/databases/main.sqlite)に格納されるため、アシスタントは通常のSQLでファイルをまたいでJOINできます。2つのファイルが同じテーブル名を名乗る場合 — たとえば異なる2つのフォルダに sales.csv がある場合 — 2つ目は自身のコンテンツハッシュから接尾辞を取得します(salessales_4b11d3)。つまり、特定のファイルはインポート順に関係なく常に同じテーブル名になります。

大きな結果はチャットではなくディスクへ

query_sql() は最大1,000行を返し、常にその旨を伝えます("truncated": true)。これは、大きな結果を会話にページ分割するのではなく、SQLで集約するよう促すものです。病的なクエリ — 意図しないデカルト積、無制限の再帰CTE — はタイムアウト後にキャンセルされます。

ユーザーが実際に行データを欲しい場合、export_query() は同じ読み取り専用SQLを行数制限なしで実行し、カーソルを workspace/exports/ 配下のファイルに直接ストリーミングします:

「2025年の$1,000超のメール取引をすべて取得して、エクスポートして」

アシスタントはクエリを作成し、export_query() を呼び出してパスを返します — これが同梱サンプルデータでの実際の結果です:

{"file_name": "email_2025_high_value.csv",
 "relative_path": "exports/email_2025_high_value.csv",
 "row_count": 58, "file_size_bytes": 3084}

サーバーも会話も結果全体を保持することはないため、58行でも500万行でも同じように動作します。


安全性

CSVが変更されることはありません。 source/ はDockerレベルで読み取り専用マウントされます。書き込まれるものはすべて workspace/ に置かれます。

AIが生成したすべてのクエリは読み取り専用であり、4つの層で強制されます:

  1. 接続は file:…?mode=ro として開かれるため、OSがファイルを読み取り専用で保持します;

  2. PRAGMA query_only=ON により、SQLite自体がそのハンドルへの書き込みを拒否します;

  3. 拡張機能の読み込みが明示的に無効化されます;

  4. set_authorizer() コールバックは SQLITE_SELECTSQLITE_READSQLITE_FUNCTION(ファイルシステムに到達する組み込み関数を除く)、SQLITE_RECURSIVE のみを許可し、それ以外のすべて — 書き込み、スキーマ変更、ATTACH/DETACH、すべての PRAGMA、トランザクション制御、メンテナンス — を拒否します。

第4層が実際のメカニズムです。これはステートメントの準備中にSQLite内部で実行されるため、クエリのテキストがどう綴られているかではなく、クエリが何を行うかを判断します。その前にSQLスクラバーが多層防御として配置され、モデルに素の not authorized ではなく読みやすいエラー(only read-only statements are allowed; found 'DROP')を返します。

この区別は両方向に働き、テストスイートがそれを固定しています:CASE … ENDreplace() スカラー関数は通常の分析SQLとして動作し続けますが、REPLACE INTOPRAGMA writable_schema = ONload_extension() は拒否されます。

パスは閉じ込められます。 サーバーは source/ 内でのみ読み取り、workspace/exports/ 内でのみ書き込みます。トラバーサル(../)、絶対パス、プロジェクト外を指すシンボリックリンクは拒否されます。エクスポートのファイル名はサニタイズされ、既存のエクスポートが上書きされることはありません。

認証はありません — 設計によるものです。コンテナは 127.0.0.1 にのみ公開され、同じマシン上のクライアントを想定しています。ネットワークに公開しないでください。


設定

すべてオプションです。compose.yaml で設定します。

変数

デフォルト

制御内容

TABULITE_SOURCE_DIR

/project/source

読み取り専用のソースディレクトリ

TABULITE_WORKSPACE_DIR

/project/workspace

書き込み可能なワークスペース

TABULITE_NULL_MARKERS

,NULL,null,N/A,NA

SQL NULLとしてインポートされる値

TABULITE_MAX_QUERY_ROWS

1000

対話型クエリの行数上限

TABULITE_QUERY_TIMEOUT

30

クエリがキャンセルされるまでの秒数

TABULITE_EXPORT_TIMEOUT

600

エクスポートがキャンセルされるまでの秒数

TABULITE_BATCH_SIZE

5000

インポート中の executemany() あたりの行数

TABULITE_HOST / TABULITE_PORT

0.0.0.0 / 8000

コンテナ内のバインドアドレス

TABULITE_ALLOWED_ORIGINS

localhost origins

オリジン許可リスト(DNSリバインディング保護)


プロジェクト構成

tabulite-mcp/
├── source/                  # your CSV files (read-only mount, gitignored)
├── workspace/               # everything generated (gitignored)
│   ├── catalog.sqlite       #   source, import, profile and export metadata
│   ├── databases/main.sqlite#   the imported analytical tables
│   └── exports/             #   query results written to disk
├── src/tabulite_mcp/
│   ├── server.py            # the MCP tools
│   ├── config.py            # paths and limits
│   ├── security.py          # path containment + read-only enforcement
│   ├── database.py          # connections, row caps, cancellation
│   ├── importer.py          # streaming CSV → SQLite
│   ├── profiler.py          # logical type inference
│   ├── casting.py           # TRY_* functions
│   ├── catalog.py           # catalog.sqlite
│   └── exporter.py          # streaming results to files
├── tests/
├── Dockerfile
└── compose.yaml

開発

Dockerなしで実行する:

python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
TABULITE_SOURCE_DIR=./source TABULITE_WORKSPACE_DIR=./workspace tabulite-mcp

テストを実行する:

pytest

211のテストが以下をカバーしています:ソースの検出とトラバーサル拒否、ストリーミングインポート、NULLと不正値の処理、SHA-256による同一性(リネームおよび変更されたファイルを含む)、決定論的なテーブル命名、プロファイリングと型推論、TRY_* 関数、不正値を無視する AVG、SELECT/GROUP BY/CTE/join/window クエリ、結果の制限、クエリキャンセル、スクラバー層とオーソライザー層の両方での読み取り専用の強制、CSVおよびJSONエクスポート、エクスポートのストリーミング、ファイル名のサニタイズ、そして実際のインプロセスMCPセッションでのツール呼び出し。

技術スタック: Python 3.11+、標準ライブラリの sqlite3、そして mcp==2.1.1 に固定された公式MCP Python SDK(v2 API:MCPServer、ホスト/ポートは run() で指定)。pandasもNumPyもORMもなし — 中核は一目でわかる普通のPythonです:sqlite3.connect()conn.executemany()conn.create_function()cursor.fetchmany()

スケール: 133 MB / 2,000,000行のCSVは、コンテナメモリが約100 MBでほぼ一定のまま、約2分でインポートおよびプロファイリングが完了します。集計には数秒かかります。インポートはRAMではなくディスクに依存します。


トラブルシューティング

ポート8000が使用中compose.yaml のマッピングのホスト側を変更し("127.0.0.1:8001:8000")、クライアントが新しいポートを指すようにしてください。

クライアントが接続できないcurl http://localhost:8000/health でサーバーが起動しているか確認し、次に docker compose logs -f を実行してください。

workspace/ への書き込みでパーミッションエラー(Linux)compose.yamluser: 行のコメントを解除して、コンテナユーザーではなく自分のユーザーとしてファイルが作成されるようにしてください。

source/ 内のファイルが一覧に表示されない — 検出されるのは .csv.tsv のみで、ドットファイルはスキップされます。

CSV編集後に「不明なテーブル」 — ファイルを変更するとハッシュが変わるため、import_source() を再実行してください。新しい内容には独自のテーブルが作成されます。


対象外

組み込みLLM、サーバー内での自然言語からSQLへの変換、任意のPythonコードの実行、pandas/NumPy/matplotlib、Excel、DuckDB、Polars、Parquet、埋め込みやベクトル検索、クラウドデプロイ、認証、マルチユーザー対応、バックグラウンドジョブはいずれも対象外です。AIクライアントがすでにインターフェースであり、推論レイヤーです。

ライセンス

MIT — 好きなように使って構いませんが、著作権表示は保持してください。

コントリビューションを歓迎します。同じライセンスの下で受け付けます(CLAなし、著作権の譲渡なし)。著作権がコードを書いた人に残るのは意図的で、このプロジェクトが誰かの製品になるのではなく、オープンソースプロジェクトであり続けることを目的としています。

A
license - permissive license
Not graded
quality - not tested
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

  • F
    license
    Not graded
    quality
    D
    maintenance
    AI-first CSV analysis tool that enables AI agents to analyze, query, and audit large CSV files directly within conversations, turning raw data into actionable insights.
    2
  • F
    license
    B
    quality
    D
    maintenance
    Enables Claude to directly access, query, and analyze local CSV files using natural language, keeping data private and local.
    4
    1
  • F
    license
    Not graded
    quality
    D
    maintenance
    Enables AI assistants to interact with local CSV and Parquet data files through natural language queries, facilitating tasks like summarizing datasets or retrieving specific information.
    5
  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables querying Excel and CSV files using SQL via natural language, allowing AI assistants to analyze data without manual SQL writing.
    1
    MIT

View all related MCP servers

Related MCP Connectors

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/davidmrguo/tabulite-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server