Skip to main content
Glama
davidmrguo

tabulite-mcp

by davidmrguo

Tabulite MCP

分析那些大得无法用电子表格打开、也大得无法粘贴到聊天窗口中的 CSV 文件——方法是给你的 AI 助手一个本地 SQLite 运行时,而不是数据本身。

Tabulite MCP(简称 Tabulite)是一个本地 MCP 服务器。把它指向一个 CSV 文件夹,你的桌面 AI 客户端就可以将文件导入 SQLite,检查各列实际包含的内容,并通过编写 SQL 来回答问题——你的数据不会有一行离开你的机器,或进入对话。

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

服务器内部没有 LLM。思考由你的 AI 客户端完成;Tabulite 为它提供可供思考的元数据、一个可探索的只读 SQL 接口,以及当答案是数据集而非一句话时直通磁盘的路径。


为什么

向 AI 询问一个 500 MB 的 CSV,你面临的选择都不怎么样:粘贴样本会丢失答案,上传整个文件会烧掉你的上下文窗口(还会把数据发到某个地方),或者自己动手写脚本。

单台机器加单个 SQLite 文件应付这个体量绰绰有余。Tabulite 把这个运行时放在数据旁边,并通过 MCP 暴露出来。你的助手读取几百个 token 的列概要,编写 SQL,然后拿回聚合结果。数据行始终留在磁盘上。

适合的场景: 在自己的笔记本上对 CSV 导出、日志转储和提取数据进行一次性分析——这些文件已经超出了 Excel 的承受范围,但仍应放在一台机器上。 不适合的场景: 生产管道、定时 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

仓库自带两个小样本 CSV(source/sales.csvsource/customers.csv),你可以立即试用。先连接你的 AI 客户端(见下文),然后提问:

“分析 sales.csv。哪个渠道带来的收入最高?”

你的助手会调用 list_sources()import_source("sales.csv")profile_table("sales"),然后写出类似这样的内容:

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 可用的工具

工具

作用

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)

单列的完整详情,含示例

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"]

推断是保守的——只有当 ≥99% 的非 NULL 值都能按某种类型解析时,才会指派该类型。概要只是给 AI 的证据,绝不是对存储层的指示:你导入的数据绝不会为了迎合某个猜测而被重写。

使用 TRY_* 函数而非 CAST

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.csv 改名为 sales_FINAL_v2.csv 再导入一次,Tabulite 会识别出相同内容,并复用已有表,而不是再复制一份。文件身份由 SHA-256 决定,该哈希在导入过程中计算,而不是单独读一遍。只要改动一个字节,它就会成为一个带有新表的新数据源。

所有导入的内容都存放在同一个数据库(workspace/databases/main.sqlite)中,因此你的助手可以用普通 SQL 跨文件联表。当两个文件要使用同一个表名时——例如两个不同文件夹里各有一个 sales.csv——第二个文件会获得源自自身内容哈希的后缀(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 生成的查询都是只读的,通过四层机制强制保证:

  1. 连接以 file:…?mode=ro 方式打开,因此操作系统会以只读方式持有该文件;

  2. PRAGMA query_only=ON 让 SQLite 本身拒绝在该句柄上的写入;

  3. 显式禁用扩展加载;

  4. set_authorizer() 回调只允许 SQLITE_SELECTSQLITE_READSQLITE_FUNCTION(将可触及文件系统的内置函数排除在外)和 SQLITE_RECURSIVE,拒绝其他一切操作——写入、schema 变更、ATTACH/DETACH、所有 PRAGMA、事务控制、维护操作。

第 4 层才是真正的机制:它在语句准备期间运行于 SQLite 内部,因此它判断的是查询实际做什么,而不是其文本如何拼写。一个 SQL 清洗器位于它前面,既作为纵深防御,也为了让模型看到可读的错误信息(only read-only statements are allowed; found 'DROP'),而不是光秃秃的 not authorized

这种区分是双向的,测试套件也对此做了固定: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

Origin 白名单(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 Python SDK,版本锁定为 mcp==2.1.1(v2 API:MCPServerrun() 上指定 host/port)。不使用 pandas、NumPy 或 ORM——核心是大家一看就熟的普通 Python:sqlite3.connect()conn.executemany()conn.create_function()cursor.fetchmany()

规模: 一个 133 MB / 2,000,000 行的 CSV 导入和剖析大约需要两分钟,容器内存稳定在 100 MB 左右;对其聚合操作只需几秒。导入受磁盘限制,而非内存。


故障排除

端口 8000 已被占用 — 修改 compose.yaml 中映射的主机侧("127.0.0.1:8001:8000"),并将客户端指向新端口。

客户端无法连接 — 先用 curl http://localhost:8000/health 检查服务是否已启动,然后运行 docker compose logs -f

workspace/ 写入时出现权限错误(Linux) — 取消注释 compose.yaml 中的 user: 行,以便文件以你的身份而不是容器用户身份创建。

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